File Coverage

blib/lib/Net/Jabber/Bot.pm
Criterion Covered Total %
statement 205 351 58.4
branch 42 102 41.1
condition 12 37 32.4
subroutine 28 38 73.6
pod 19 20 95.0
total 306 548 55.8


line stmt bran cond sub pod time code
1             package Net::Jabber::Bot;
2              
3 3     3   177554 use Moose;
  3         1230858  
  3         17  
4             use MooseX::Types
5 3     3   21904 -declare => [qw( JabberClientObject PosInt PosNum HundredInt )];
  3         117943  
  3         18  
6              
7             # import builtin types
8 3     3   18499 use MooseX::Types::Moose qw/Int HashRef Str Maybe ArrayRef Bool CodeRef Object Num/;
  3         46906  
  3         20  
9              
10 3     3   18269 use version;
  3         5100  
  3         17  
11 3     3   1487 use Net::Jabber;
  3         451069  
  3         156  
12 3     3   1331 use Time::HiRes;
  3         3686  
  3         13  
13 3     3   302 use Sys::Hostname;
  3         6  
  3         146  
14 3     3   2254 use Log::Log4perl qw(:easy);
  3         113254  
  3         15  
15 3     3   3218 use Mozilla::CA;
  3         666  
  3         12489  
16              
17             coerce Bool, from Str,
18             via {($_ =~ m/(^on$)|(^true$)/i) + 0}; # True if it's on or true. Otherwise false.
19              
20             subtype JabberClientObject, as Object, where { $_->isa('Net::Jabber::Client') };
21              
22             subtype PosInt, as Int, where { $_ > 0 };
23             subtype PosNum, as Num, where { $_ > 0 };
24             subtype HundredInt, as Num, where { $_ > 100 };
25              
26             has jabber_client => (isa => Maybe[JabberClientObject],
27             is => 'rw',
28             default => sub {Net::Jabber::Client->new});
29             #my %connection_hash : ATTR; # Keep track of connection options fed to client.
30              
31             has 'client_session_id' => (isa => Str, is => 'rw');
32             has 'connect_time' => (isa => PosInt, is => 'rw', default => 9_999_999_999);
33             has 'forum_join_grace' => (isa => PosNum, is => 'rw', default => 10);
34             has 'server_host' => (isa => Str, is => 'rw', lazy => 1, default => sub{shift->server });
35             has 'server' => (isa => Str, is => 'rw');
36             has 'port' => (isa => PosInt, is => 'rw', default => 5222);
37             has 'tls' => (isa => Bool, is => 'rw', default => '0');
38             has 'ssl_ca_path' => (isa => Str, is => 'rw', default => Mozilla::CA::SSL_ca_file());
39             has 'ssl_verify' => (isa => Bool, is => 'rw', default => '1');
40             has 'connection_type' => (isa => Str, is => 'rw', default => 'tcpip');
41             has 'conference_server' => (isa => Str, is => 'rw');
42             has 'username' => (isa => Str, is => 'rw');
43             has 'password' => (isa => Str, is => 'rw');
44             has 'alias' => (isa => Str, lazy => 1, is => 'rw', default => 'net_jabber_bot');
45             # Resource defaults to alias_hostname_pid
46             has 'resource' => (isa => Str, lazy => 1, is => 'rw', default => sub{shift->alias . "_" . hostname . "_" . $$});
47             has 'message_function' => (isa => Maybe[CodeRef], is => 'rw', default => sub{undef});
48             has 'background_function' => (isa => Maybe[CodeRef], is => 'rw', default => sub{undef});
49             has 'loop_sleep_time' => (isa => PosNum, is => 'rw', default => 5);
50             has 'process_timeout' => (isa => PosNum, is => 'rw', default => 5);
51             has 'from_full' => (isa => Str, lazy => 1, is => 'rw', default => sub{my $self = shift;
52             $self->username || '' .
53             '@' .
54             $self->server || '' .
55             '/' .
56             $self->alias || '' });
57              
58             has 'safety_mode' => (isa => Bool, is => 'rw', default => 1, coerce => 1);
59             has 'ignore_server_messages' => (isa => Bool, is => 'rw', default => 1, coerce => 1);
60             has 'ignore_self_messages' => (isa => Bool, is => 'rw', default => 1, coerce => 1);
61             has 'forums_and_responses' => (isa => HashRef[ArrayRef[Str]], is => 'rw'); # List of forums we're in and the strings we monitor for.
62             has 'forum_join_time' => (isa => HashRef[Int], is => 'rw', default => sub{{}}); # List of when we joined each forum
63             has 'out_messages_per_second' => (isa => PosNum, is => 'rw', default => sub{5});
64             has 'message_delay' => (isa => PosNum, is => 'rw', default => sub {1/5});
65              
66             has 'max_message_size' => (isa => HundredInt, is => 'rw', default => 1000000);
67             has 'max_messages_per_hour' => (isa => PosInt, is => 'rw', default => 1000000);
68              
69             # Initialize this hour's message count.
70             has 'messages_sent_today' => (isa => 'HashRef', is => 'ro', default => sub{{(localtime)[7] => {(localtime)[2] => 0}}});
71              
72              
73             #my %message_function : ATTR; # What is called if we are fed a new message once we are logged in.
74             #my %bot_background_function : ATTR; # What is called if we are fed a new message once we are logged in.
75             #my %forum_join_time : ATTR; # Tells us if we've parsed historical messages yet.
76             #my %client_start_time :ATTR; # Track when we came online. Also used to determine if we're online.
77             #my %process_timeout : ATTR; # Time to take in process loop if no messages found
78             #my %loop_sleep_time : ATTR; # Time to sleep each time we go through a Start() loop.
79             #my %ignore_messages : ATTR; # Messages to ignore if we recieve them.
80             #my %forums_and_responses: ATTR; # List of forums we have joined and who we respond to in each forum
81             #my %message_delay: ATTR; # Allows us to limit Messages per second
82             #my %max_message_size: ATTR; # Maximum allowed message size before we chunk them.
83             #my %forum_join_grace: ATTR; # Time before we start responding to forum messages.
84             #my %messages_sent_today: ATTR; # Tracks messages sent in 2 dimentional hash by day/hour
85             #my %max_messages_per_hour: ATTR; # Limits the number of messages per hour.
86             #my %safety_mode: ATTR; # Tracks if we are in safety mode.
87              
88             =head1 NAME
89              
90             Net::Jabber::Bot - Automated Bot creation with safeties
91              
92             =head1 VERSION
93              
94             Version 2.1.6
95              
96             =cut
97              
98             our $VERSION = '2.1.6';
99              
100             =head1 SYNOPSIS
101              
102             Program design:
103             This is a Moose based Class.
104              
105             The idea behind the module is that someone creating a bot should not really have to know a whole lot about how the Jabber protocol works in order to use it. It also allows us to abstract away all the things that can get a bot maker into trouble. Essentially the object helps protect the coders from their own mistakes.
106              
107             All someone should have to know and define in the program away from the object is:
108              
109             =over
110              
111             =item 1. Config - Where to connect, how often to do things, timers, etc
112              
113             =item 2. A subroutine to be called by the bot object when a new message comes in.
114              
115             =item 3. A subroutine to be called by the bot object every so often that lets the user do background activities (check logs, monitor web pages, etc.),
116              
117             =back
118              
119             The object at present has the following enforced safeties as long as you do not override safety mode:
120              
121             =over
122              
123             =item 1. Limits messages per second, configurable at start up, (Max is 5 per second) by requiring a sleep timer in the message sending subroutine each time one is sent.
124              
125             =item 2. Endless loops of responding to self prevented by now allowing the bot message processing subroutine to know about messages from self
126              
127             =item 3. Forum join grace period to prevent bot from reacting to historical messages
128              
129             =item 4. Configurable aliases the bot will respond to per forum
130              
131             =item 5. Limits maximum message size, preventing messages that are too large from being sent (largest configurable message size limit is 1000).
132              
133             =item 6. Automatic chunking of messages to split up large messages in message sending subroutine
134              
135             =item 7. Limit on messages per hour. (max configurable limit of 125) Messages are visible via log4perl, but not ever be sent once the message limit is reached for that hour.
136              
137             =back
138              
139             =head1 FUNCTIONS
140              
141             =over 4
142              
143             =item B<new>
144              
145             Minimal:
146              
147             my $bot = Net::Jabber::Bot->new(
148             server => 'host.domain.com', # Name of server when sending messages internally.
149             conference_server => 'conference.host.domain.com',
150             port => 522,
151             username => 'username',
152             password => 'pasword',
153             safety_mode => 1,
154             message_function => \&new_bot_message,
155             background_function => \&background_checks,
156             forums_and_responses => \%forum_list
157             );
158              
159             All options:
160              
161             my $bot = Net::Jabber::Bot->new(
162             server => 'host.domain.com', # Name of server when sending messages internally.
163             conference_server => 'conference.host.domain.com',
164             server_host => 'talk.domain.com', # used to specify what jabber server to connect to on connect?
165             tls => 0, # set to 1 for google
166             ssl_ca_path => '', # path to your CA cert bundle
167             ssl_verify => 0, # for testing and for self-signed certificates
168             connection_type => 'tcpip',
169             port => 522,
170             username => 'username',
171             password => 'pasword',
172             alias => 'cpan_bot',
173             message_function => \&new_bot_message,
174             background_function => \&background_checks,
175             loop_sleep_time => 15,
176             process_timeout => 5,
177             forums_and_responses => \%forum_list,
178             ignore_server_messages => 1,
179             ignore_self_messages => 1,
180             out_messages_per_second => 4,
181             max_message_size => 1000,
182             max_messages_per_hour => 100
183             );
184              
185              
186             Set up the object and connect to the server. Hash values are passed to new as a hash.
187              
188             The following initialization variables can be passed. Only marked variables are required (TODO)
189              
190             =over 5
191              
192             =item B<safety_mode>
193              
194             safety_mode = (1,0)
195              
196             Determines if the bot safety features are turned on and enforced. This mode is on by default. Many of the safety features are here to assure you do not crash your favorite jabber server with floods, etc. DO NOT turn it off unless you're sure you know what you're doing (not just Sledge Hammer ceratin)
197              
198             =item B<server>
199              
200             Jabber server name
201              
202             =item B<server_host>
203              
204             Defaults to the same value set for 'server' above.
205             This is where the bot initially connects. For google for instance, you should set this to 'gmail.com'
206              
207             =item B<conference_server>
208              
209             conferencee server (usually conference.$server_name)
210              
211             =item B<port>
212              
213             Defaults to 5222
214              
215             =item B<tls>
216              
217             Boolean value. defaults to 0. for google, it is know that this value must be 1 to work.
218              
219             =item B<ssl_ca_path>
220              
221             The path to your CA cert bundle. This is passed on to XML::Stream eventually.
222              
223             =item B<ssl_verify>
224              
225             Enable or disable server certificate validity check when connecting to server. This is passed on to XML::Stream eventually.
226              
227             =item B<connection_type>
228              
229             defaults to 'tcpip' also takes 'http'
230              
231             =item B<username>
232              
233             The user you authenticate with to access the server. Not full name, just the stuff to the left of the @...
234              
235             =item B<password>
236              
237             password to get into the server
238              
239             =item B<alias>
240              
241             This will be your nickname in rooms, as well as the login resource (which can't have duplicates). I couldn't come up with any reason these should not be the same so hardcoded them to be the same.
242              
243             =item B<forums_and_responses>
244              
245             A hash ref which lists the forum names to join as the keys and the values are an array reference to a list of strings they are supposed to be responsive to.
246             The array is order sensitive and an empty string means it is going to respond to all messages in this forum. Make sure you list this last.
247              
248             The found 'response string' is assumed to be at the beginning of the message. The message_funtion function will be called with the modified string.
249              
250             alias = jbot:, attention:
251              
252             example1:
253              
254             message: 'jbot: help'
255              
256             passed to callback: 'help'
257              
258             =item B<message_function>
259              
260             The subroutine the bot will call when a new message is recieved by the bot. Only called if the bot's logic decides it's something you need to know about.
261              
262             =item B<background_function>
263              
264             The subroutine the bot will call when every so often (loop_sleep_time) to allow you to do background activities outside jabber stuff (check logs, web pages, etc.)
265              
266             =item B<loop_sleep_time>
267              
268             Frequency background function is called.
269              
270             =item B<process_timeout>
271              
272             Time Process() will wait if no new activity is received from the server
273              
274             =item B<ignore_server_messages>
275              
276             Boolean value as to whether we should ignore messages sent to us from the jabber server (addresses can be a little cryptic and hard to process)
277              
278             =item B<ignore_self_messages>
279              
280             Boolean value as to whether we should ignore messages sent by us.
281              
282             BE CAREFUL if you turn this on!!! Turning this on risks potentially endless loops. If you're going to do this, please be sure safety is turned on at least initially.
283              
284             =item B<out_messages_per_second>
285              
286             Limits the number of messages per second. Number must be <gt> 0
287              
288             default: 5
289              
290             safety: 5
291              
292             =item B<max_message_size>
293              
294             Specify maximimum size a message can be before it's split and sent in pieces.
295              
296             default: 1,000,000
297              
298             safety: 1,000
299              
300             =item B<max_messages_per_hour>
301              
302             Limits the number of messages per hour before we refuse to send them
303              
304             default: 125
305              
306             safety: 166
307              
308             =back
309              
310             =cut
311              
312             # Handle initialization of objects of this class...
313             sub BUILD {
314 2     2 0 6 my ($self, $params) = @_;
315              
316             # Deal with legacy bug
317 2 50 33     12 if($params->{background_activity} || $params->{message_callback}) {
318 0         0 my $warn_message = "\n\n"
319             . "*" x 70 . "\n"
320             . "WARNING!!! You're using old parameters for your bot initialization\n"
321             . "'message_callback' should be changed to 'message_function'\n"
322             . "'background_activity' should be changed to 'background_function'\n"
323             . "I'm correcting this, but you should fix your code\n"
324             . "*" x 70 . "\n"
325             . "\n\n";
326 0         0 warn($warn_message);
327 0         0 WARN($warn_message);
328              
329             $self->background_function($params->{background_activity})
330 0 0 0     0 if(!$self->background_function && $params->{background_activity});
331             $self->message_function($params->{message_callback})
332 0 0 0     0 if(!$self->message_function && $params->{message_callback});
333 0         0 sleep 30;
334             }
335            
336             # Message delay is inverse of out_messages_per_second
337 2         66 $self->message_delay(1/$self->out_messages_per_second);
338              
339             # Enforce all our safety restrictions here.
340 2 50       50 if($self->safety_mode) {
341             # more than 5 messages per second risks server flooding.
342 2 50       57 $self->message_delay(1/5) if($self->message_delay < 1/5);
343              
344             # Messages should be small to not overwhelm rooms/people/server
345 2 50       65 $self->max_message_size(1000) if($self->max_message_size > 1000);
346              
347             # More than 4,000 messages a day is a little excessive.
348 2 50       68 $self->max_messages_per_hour(125) if($self->max_messages_per_hour > 166);
349              
350             # Should not be responding to self messages to prevent loops.
351 2         52 $self->ignore_self_messages(1);
352             }
353              
354             #Initialize the connection.
355 2         10 $self->_init_jabber;
356             }
357              
358             # Return a code reference that will pass self in addition to arguements passed to callback code ref.
359             sub _callback_maker {
360 6     6   12 my $self = shift;
361 6         9 my $Function = shift;
362              
363             # return sub {return $code_ref->($self, @_);};
364 6     178   41 return sub {return $Function->($self, @_);};
  178         1728  
365             }
366              
367             # Creates client object and manages connection. Called on new but also called by re-connect
368             sub _init_jabber {
369 2     2   5 my $self = shift;
370              
371             # Autocreate the jabber object (see has jabber_client)
372 2         59 my $connection = $self->jabber_client;
373              
374 2         11 DEBUG("Set the call backs.");
375 2         40 $connection->PresenceDB(); # Init presence DB.
376 2         19 $connection->RosterDB(); # Init Roster DB.
377 2         123 $connection->SetCallBacks( 'message' => $self->_callback_maker(\&_process_jabber_message)
378             ,'presence' => $self->_callback_maker(\&_jabber_presence_message)
379             ,'iq' => $self->_callback_maker(\&_jabber_in_iq_message)
380             );
381              
382 2         81 DEBUG("Connect. hostname => " . $self->server . ", port => " . $self->port);
383 2         60 my %client_connect_hash = (
384             hostname => $self->server,
385             port => $self->port,
386             tls => $self->tls,
387             ssl_ca_path => $self->ssl_ca_path,
388             ssl_verify => $self->ssl_verify,
389             connectiontype => $self->connection_type,
390             componentname => $self->server_host,
391             );
392              
393 2         18 my $status = $connection->Connect(%client_connect_hash);
394              
395 2 50       29 if(!defined $status) {
396 0         0 ERROR("ERROR: Jabber server is down or connection was not allowed: $!");
397 0         0 die("Jabber server is down or connection was not allowed: $!");
398             }
399              
400 2         57 DEBUG("Logging in... as user " . $self->username . " / " . $self->resource);
401 2         68 DEBUG("PW: " . $self->password);
402              
403             # Moved into connect hash via 'componentname'
404             # my $sid = $connection->{SESSION}->{id};
405             # $connection->{STREAM}->{SIDS}->{$sid}->{hostname} = $self->server_host;
406              
407              
408 2         68 my @auth_result = $connection->AuthSend(username => $self->username,
409             password => $self->password,
410             resource => $self->resource,
411             );
412              
413 2 50 33     49 if(!defined $auth_result[0] || $auth_result[0] ne "ok") {
414 0         0 ERROR("Authorization failed: for " . $self->username . " / " . $self->resource);
415 0         0 foreach my $result (@auth_result) {
416 0         0 ERROR("$result");
417             }
418 0         0 die("Failed to re-connect: " . join("\n", @auth_result));
419             }
420              
421 2         29 $connection->RosterRequest();
422              
423 2         6540 $self->client_session_id($connection->{SESSION}->{id});
424              
425 2         8 DEBUG("Sending presence to tell world that we are logged in");
426 2         21 $connection->PresenceSend();
427 2         10 $self->Process(5);
428              
429 2         26 DEBUG("Getting Roster to tell server to send presence info");
430 2         58 $connection->RosterGet();
431 2         16 $self->Process(5);
432              
433 2         16 foreach my $forum (keys %{$self->forums_and_responses}) {
  2         354  
434 4         71 $self->JoinForum($forum);
435             }
436              
437 2         269 INFO("Connected to server '" . $self->server . "' successfully");
438 2         102 $self->connect_time(time); # Track when we came online.
439 2         77 return 1;
440             }
441              
442             =item B<JoinForum>
443              
444             Joins a jabber forum and sleeps safety time. Also prevents the object
445             from responding to messages for a grace period in efforts to get it to
446             not respond to historical messages. This has failed sometimes.
447              
448             NOTE: No error detection for join failure is present at the moment. (TODO)
449              
450             =cut
451              
452             sub JoinForum {
453 4     4 1 19 my $self = shift;
454 4         13 my $forum_name = shift;
455              
456 4         293 DEBUG("Joining $forum_name on " . $self->conference_server . " as " . $self->alias);
457              
458 4         196 $self->jabber_client->MUCJoin(room => $forum_name,
459             server => $self->conference_server,
460             nick => $self->alias,
461             );
462              
463 4         195 $self->forum_join_time->{$forum_name} = time;
464 4         108 DEBUG("Sleeping " . $self->message_delay . " seconds");
465 4         118 Time::HiRes::sleep $self->message_delay;
466             }
467              
468             =item B<Process>
469              
470             Mostly calls it's client connection's "Process" call.
471             Also assures a timeout is enforced if not fed to the subroutine
472             You really should not have to call this very often.
473             You should mostly be calling Start() and just let the Bot kernel handle all this.
474              
475             =cut
476              
477             sub Process { # Call connection process.
478 18     18 1 8004041 my $self = shift;
479 18         41 my $timeout_seconds = shift;
480              
481             #If not passed explicitly
482 18 50       127 $timeout_seconds = $self->process_timeout if(!defined $timeout_seconds);
483              
484 18         1271 my $process_return = $self->jabber_client->Process($timeout_seconds);
485 18         40004722 return $process_return;
486             }
487              
488             =item B<Start>
489              
490             Primary subroutine save new called by the program. Does an endless loop of:
491              
492             =over
493              
494             =item 1. Process
495              
496             =item 2. If Process failed, Reconnect to server over larger and larger timeout
497              
498             =item 3. run background process fed from new, telling it who I am and how many loops we have been through.
499              
500             =item 4. Enforce a sleep to prevent server floods.
501              
502             =back
503              
504             =cut
505              
506             sub Start {
507 0     0 1 0 my $self = shift;
508              
509 0         0 my $time_between_background_routines = $self->loop_sleep_time;
510 0         0 my $process_timeout = $self->process_timeout;
511 0         0 my $background_subroutine = $self->background_function;
512 0         0 my $message_delay = $self->message_delay;
513              
514 0         0 my $last_background = time - $time_between_background_routines - 1; # Call background process every so often...
515 0         0 my $counter = 0; # Keep track of how many times we've looped. Not sure if we'll use this long term.
516              
517 0         0 while(1) { # Loop for ever!
518             # Process and re-connect if you have to.
519 0         0 my $reconnect_timeout = 1;
520 0         0 eval {$self->Process($process_timeout)};
  0         0  
521              
522 0 0       0 if($@) { #Assume the connection is down...
523 0         0 ERROR("Server error: $@");
524 0         0 my $message = "Disconnected from " . $self->server . ":" . $self->port
525             . " as " . $self->username;
526              
527 0         0 ERROR("$message Reconnecting...");
528 0         0 sleep 5; # TODO: Make re-connect time flexible somehow
529 0         0 $self->ReconnectToServer();
530             }
531              
532             # Call background function
533 0 0 0     0 if(defined $background_subroutine && $last_background + $time_between_background_routines < time) {
534 0         0 &$background_subroutine($self, ++$counter);
535 0         0 $last_background = time;
536             }
537 0         0 Time::HiRes::sleep $message_delay;
538             }
539             }
540              
541             =item B<ReconnectToServer>
542              
543             You should not ever need to use this. the Start() kernel usually figures this out and calls it.
544              
545             Internal process:
546              
547             1. Disconnects
548             3. Re-initializes
549              
550             =cut
551              
552             sub ReconnectToServer {
553 0     0 1 0 my $self = shift;
554              
555 0         0 my $background_subroutine = $self->background_function;
556              
557 0         0 $self->Disconnect();
558              
559 0         0 my $sleep_time = 5;
560 0         0 while (!$self->IsConnected()) { # jabber_client variable defines if we're connected.
561 0         0 INFO("Sleeping $sleep_time before attempting re-connect");
562 0         0 sleep $sleep_time;
563 0 0       0 $sleep_time *= 2 if($sleep_time < 300);
564 0         0 $self->InitJabber();
565 0         0 INFO("Running background routine.");
566 0         0 &$background_subroutine($self, 0); # call background proc so we can check for errors while down.
567             }
568             }
569              
570             =item B<Disconnect>
571              
572             Disconnects from server if client object is defined. Assures the client object is deleted.
573              
574             =cut
575              
576              
577             sub Disconnect {
578 0     0 1 0 my $self = shift;
579              
580 0         0 $self->connect_time('9' x 10); # Way in the future
581              
582 0         0 INFO("Disconnecting from server");
583 0 0       0 return if(!defined $self->jabber_client); # do not proceed, no object.
584              
585 0         0 $self->jabber_client->Disconnect();
586 0         0 my $old_client = $self->jabber_client;
587 0         0 $self->jabber_client(undef);
588              
589 0         0 DEBUG("Disconnected.");
590 0         0 return 1;
591             }
592              
593             =item B<IsConnected>
594              
595             Reports connect state (true/false) based on the status of client_start_time.
596              
597             =cut
598              
599             sub IsConnected {
600 176     176 1 427 my $self = shift;
601              
602 176         5159 DEBUG("REF = " . ref($self->jabber_client));
603 176         7620 return $self->connect_time;
604             }
605              
606             # TODO: ***NEED VERY GOOD DOCUMENTATION HERE*****
607             =item B<_process_jabber_message> - DO NOT CALL
608              
609             Handles incoming messages.
610              
611             =cut
612              
613             sub _process_jabber_message {
614 178     178   340 my $self = shift;
615 178         466 DEBUG("_process_jabber_message called");
616              
617 178         1071 my $session_id = shift;
618 178         274 my $message = shift;
619              
620 178         1231 my $type = $message->GetType();
621 178         175663 my $fromJID = $message->GetFrom("jid");
622 178         178316 my $from_full = $message->GetFrom();
623              
624 178         169229 my $from = $fromJID->GetUserID();
625 178         1613 my $resource = $fromJID->GetResource();
626 178         1399 my $subject = $message->GetSubject();
627 178         274596 my $body = $message->GetBody();
628              
629 178         287071 my $reply_to = $from_full;
630 178 100       1094 $reply_to =~ s/\/.*$// if($type eq 'groupchat');
631              
632             # TODO:
633             # Don't know exactly why but when a message comes from gtalk-web-interface, it works well, but if the message comes from Gtalk client, bot dies
634             # my $message_date_text; eval { $message_date_text = $message->GetTimeStamp(); } ; # Eval is a really bad idea. we need to understand why this is failing.
635              
636             # my $message_date_text = $message->GetTimeStamp(); # Since we're not using the data, we'll turn this off since it crashes gtalk clients aparently?
637             # my $message_date = UnixDate($message_date_text, "%s") - 1*60*60; # Convert to EST from CST;
638              
639             # Ignore any messages within 'forum_join_grace' seconds of start or join of that forum
640 178         5985 my $grace_period = $self->forum_join_grace;
641 178         403 my $time_now = time;
642 178 50 66     4629 if($self->connect_time > $time_now - $grace_period
      33        
643             || (defined $self->forum_join_time->{$from} && $self->forum_join_time->{$from} > $time_now - $grace_period)) {
644 0         0 my $cond1 = $self->connect_time . " > $time_now - $grace_period";
645 0   0     0 my $cond2 = $self->forum_join_time->{$from} || 'undef'
646             . " > $time_now - $grace_period";
647 0         0 DEBUG("Ignoring messages cause I'm in startup for forum $from\n$cond1\n$cond2");
648 0         0 return; # Ignore messages the first few seconds.
649             }
650              
651             # Ignore Group messages with no resource on them. (Server Messages?)
652 178 50       4705 if($self->ignore_server_messages) {
653 178 50       900 if($from_full !~ m/^([^\@]+)\@([^\/]+)\/(.+)$/) {
654 0         0 DEBUG("Server message? ($from_full) - $message");
655 0 0       0 return if($from_full !~ m/^([^\@]+)\@([^\/]+)\//);
656 0         0 ERROR("Couldn't recognize from_full ($from_full). Ignoring message: $body");
657 0         0 return;
658             }
659             }
660              
661             # Are these my own messages?
662 178 100       4757 if($self->ignore_self_messages ) { # TODO: || $self->safety_mode (this breaks tests in 06?)
663            
664 160 50 33     4094 if(defined $resource && $resource eq $self->resource) { # Ignore my own messages.
665 160         634 DEBUG("Ignoring message from self...\n");
666 160         1982 return;
667             }
668             }
669              
670             # Determine if this message was addressed to me. (groupchat only)
671 18         35 my $bot_address_from;
672 18         59 my @aliases_to_respond_to = $self->get_responses($from);
673              
674 18 100 66     89 if($#aliases_to_respond_to >= 0 and $type eq 'groupchat') {
675 12         23 my $request;
676 12         28 foreach my $address_type (@aliases_to_respond_to) {
677 24         57 my $qm_address_type = quotemeta($address_type);
678 24 100       580 next if($body !~ m/^\s*$qm_address_type\s*(\S.*)$/ms);
679 12         45 $request = $1;
680 12         25 $bot_address_from = $address_type;
681 12         25 last; # do not need to loop any more.
682             }
683 12         57 DEBUG("Message not relevant to bot");
684 12 50       120 return if(!defined $request);
685 12         22 $body = $request;
686             }
687              
688             # Call the message callback if it's defined.
689 18 50       543 if( defined $self->message_function) {
690 18         442 $self->message_function->(bot_object => $self,
691             from_full => $from_full,
692             body => $body,
693             type => $type,
694             reply_to => $reply_to,
695             bot_address_from => $bot_address_from,
696             message => $message
697             );
698 18         135 return;
699             } else {
700 0         0 WARN("No handler for messages!");
701 0         0 INFO("New Message: $type from $from ($resource). sub=$subject -- $body");
702             }
703             }
704              
705             =item B<get_responses>
706              
707             $bot->get_ident($forum_name);
708              
709             Returns the array of messages we are monitoring for in supplied forum or replies with undef.
710              
711             =cut
712              
713             sub get_responses {
714 18     18 1 36 my $self = shift;
715              
716 18         40 my $forum = shift;
717              
718 18 50       53 if(!defined $forum) {
719 0         0 WARN("No forum supplied for get_responses()");
720 0         0 return;
721             }
722              
723 18         30 my @aliases_to_respond_to;
724 18 100       464 if(defined $self->forums_and_responses->{$forum}) {
725 12         24 @aliases_to_respond_to = @{$self->forums_and_responses->{$forum}};
  12         295  
726             }
727              
728 18         51 return @aliases_to_respond_to;
729             }
730              
731              
732             =item B<_jabber_in_iq_message> - DO NOT CALL
733              
734             Called when the client receives new messages during Process of this type.
735              
736             =cut
737              
738             sub _jabber_in_iq_message {
739 0     0   0 my $self = shift;
740              
741 0         0 my $session_id = shift;
742 0         0 my $iq = shift;
743              
744 0         0 DEBUG("IQ Message:" . $iq->GetXML());
745 0         0 my $from = $iq->GetFrom();
746             # my $type = $iq->GetType();DEBUG("Type=$type");
747 0         0 my $query = $iq->GetQuery();#DEBUG("query=" . Dumper($query));
748              
749 0 0       0 if (!$query) {
750 0         0 DEBUG("iq->GetQuery() returned undef.");
751 0         0 return;
752             }
753              
754 0         0 my $xmlns = $query->GetXMLNS();DEBUG("xmlns=$xmlns");
  0         0  
755 0         0 my $iqReply;
756              
757             # Respond to version requests with information about myself.
758 0 0       0 if($xmlns eq "jabber:iq:version") {
759             # convert 5.010000 to 5.10.0
760 0         0 my $perl_version = $];
761 0         0 $perl_version =~ s/(\d{3})(?=\d)/$1./g;
762 0         0 $perl_version =~ s/\.0+(\d)/.$1/;
763            
764 0         0 $self->jabber_client
765             ->VersionSend(to=> $from,
766             name=>__PACKAGE__,
767             ver=> $VERSION,
768             os=> "Perl v$perl_version");
769             } else { # Unknown request. Just ignore it.
770 0         0 return;
771             }
772              
773 0 0       0 if($iqReply) {
774 0         0 DEBUG("Reply: ", $iqReply->GetXML());
775 0         0 $self->jabber_client->Send($iqReply);
776             }
777              
778             # INFO("IQ from $from ($type). XMLNS: $xmlns");
779             }
780              
781             =item B<_jabber_presence_message> - DO NOT CALL
782              
783             Called when the client receives new presence messages during Process.
784             Mostly we are just pushing the data down into the client DB for later processing.
785              
786             =cut
787              
788             sub _jabber_presence_message {
789 0     0   0 my $self = shift;
790              
791 0         0 my $session_id = shift;
792 0         0 my $presence = shift;
793              
794 0         0 my $type = $presence->GetType();
795 0 0       0 if($type eq 'subscribe') { # Always allow people to subscribe to us. Why wouldn't we?
    0          
796 0         0 my $from = $presence->GetFrom();
797 0         0 $self->jabber_client->Subscription(type=>"subscribe",
798             to=>$from);
799 0         0 $self->jabber_client->Subscription(type=>"subscribed",to=>$from);
800 0         0 INFO("Processed subscription request from $from");
801 0         0 return;
802             } elsif($type eq 'unsubscribe') { # Always allow people to subscribe to us. Why wouldn't we?
803 0         0 my $from = $presence->GetFrom();
804 0         0 $self->jabber_client->Subscription(type=>"unsubscribed",
805             to=>$from);
806 0         0 INFO("Processed unsubscribe request from $from");
807 0         0 return;
808             }
809            
810             # Without explicitly setting a priority, XMPP::Protocol will store all JIDs with an empty
811             # priority under the same key rather than in an array.
812 0 0       0 $presence->SetPriority(0) unless $presence->GetPriority();
813              
814 0         0 $self->jabber_client->PresenceDBParse($presence); # Since we are always an object just throw it into the db.
815              
816 0         0 my $from = $presence->GetFrom();
817 0 0       0 $from = "." if(!defined $from);
818              
819 0         0 my $status = $presence->GetStatus();
820 0 0       0 $status = "." if(!defined $status);
821              
822 0         0 DEBUG("Presence From $from t=$type s=$status");
823 0         0 DEBUG("Presence XML: " . $presence->GetXML());
824             }
825              
826             =item B<respond_to_self_messages>
827              
828             $bot->respond_to_self_messages($value = 1);
829              
830              
831             Tells the bot to start reacting to it\'s own messages if non-zero is passed. Default is 1.
832              
833             =cut
834              
835              
836             sub respond_to_self_messages {
837 8     8 1 1963 my $self = shift;
838              
839 8         15 my $setting = shift;
840 8 100       27 $setting = 1 if(!defined $setting);
841              
842 8         258 $self->ignore_self_messages(!$setting);
843 8         36 return !!$setting;
844             }
845              
846             =item B<get_messages_this_hour>
847              
848             $bot->get_messages_this_hour();
849              
850             replys with number of messages sent so far this hour.
851              
852             =cut
853              
854             sub get_messages_this_hour {
855 24     24 1 3288 my $self = shift;
856              
857 24         930 my $yday = (localtime)[7];
858 24         367 my $hour = (localtime)[2];
859 24         1022 my $messages_this_hour = $self->messages_sent_today->{$yday}->{$hour};
860 24   100     117 return $messages_this_hour || 0; # Assure it's not undef to avoid math warnings
861             }
862              
863             =item B<get_safety_mode>
864              
865             Validates that we are in safety mode. Returns a bool as long as we are an object, otherwise returns undef
866              
867             =cut
868              
869             sub get_safety_mode {
870 1     1 1 3 my $self = shift;
871              
872             # Must be in safety mode and all thresholds met.
873 1   33     38 my $mode = $self->safety_mode
874             && $self->message_delay >= 1/5
875             && $self->max_message_size <= 1000
876             && $self->max_messages_per_hour <= 166
877             && $self->ignore_self_messages
878             ;
879            
880 1   50     47 return $mode || 0;
881             }
882              
883             =item B<SendGroupMessage>
884              
885             $bot->SendGroupMessage($name, $message);
886              
887             Tells the bot to send a message to the recipient room name
888              
889             =cut
890              
891             sub SendGroupMessage {
892 82     82 1 51227 my $self = shift;
893 82         264 my $recipient = shift;
894 82         191 my $message = shift;
895              
896 82 50       3882 $recipient .= '@' . $self->conference_server if($recipient !~ m{\@});
897              
898 82         388 return $self->SendJabberMessage($recipient, $message, 'groupchat');
899             }
900              
901             =item B<SendPersonalMessage>
902              
903             $bot->SendPersonalMessage($recipient, $message);
904              
905             How to send an individual message to someone.
906              
907             $recipient must read as user@server/Resource or it will not send.
908              
909             =cut
910              
911             sub SendPersonalMessage {
912 86     86 1 45330 my $self = shift;
913 86         239 my $recipient = shift;
914 86         256 my $message = shift;
915              
916 86         375 return $self->SendJabberMessage($recipient, $message, 'chat');
917             }
918              
919             =item B<SendJabberMessage>
920              
921             $bot->SendJabberMessage($recipient, $message, $message_type, $subject);
922              
923             The master subroutine to send a message. Called either by the user, SendPersonalMessage, or SendGroupMessage. Sometimes there
924             is call to call it directly when you do not feel like figuring you messaged you.
925             Assures message size does not exceed a limit and chops it into pieces if need be.
926              
927             NOTE: non-printable characters (unicode included) will be stripped before sending to the server via:
928             s/[^[:print:]]+/./xmsg
929              
930             =cut
931              
932             sub SendJabberMessage {
933 168     168 1 414 my $self = shift;
934              
935 168         406 my $recipient = shift;
936 168         414 my $message = shift;
937 168         350 my $message_type = shift;
938 168         317 my $subject = shift;
939              
940 168         5890 my $max_size = $self->max_message_size;
941              
942             # Split the message into no more than max_message_size so that we do not piss off jabber.
943             # Split on new line. Space if you have to or just chop at max size.
944 168         1641 my @message_chunks = ( $message =~ /.{1,$max_size}$|.{1,$max_size}\n|.{1,$max_size}\s|.{1,$max_size}/gs );
945              
946              
947 168 100       677 DEBUG("Max message = $max_size. Splitting...") if($#message_chunks > 0);
948 168         393 my $return_value;
949 168         388 foreach my $message_chunk (@message_chunks) {
950 178         719 my $msg_return = $self->_send_individual_message($recipient, $message_chunk, $message_type, $subject);
951 178 100       1158 if(defined $msg_return) {
952 2         8 $return_value .= $msg_return;
953             }
954             }
955 168         1607 return $return_value;
956             }
957              
958             # $self->_send_individual_message($recipient, $message_chunk, $message_type, $subject);
959             # Private subroutine only called directly by SetForumSubject and SendJabberMessage.
960             # There are a bunch of fancy things this does, but the important things are:
961             # 1. sleep a minimum of .2 seconds every message
962             # 2. Make sure we have not sent too many messages this hour and block sends if they are attempted over a certain limit (max limit is 125)
963             # 3. Strip out special characters that will get us booted from the server.
964              
965             sub _send_individual_message {
966 178     178   448 my $self = shift;
967              
968 178         455 my $recipient = shift;
969 178         348 my $message_chunk = shift;
970 178         485 my $message_type = shift;
971 178         366 my $subject = shift;
972              
973 178 50       595 if(!defined $message_type) {
974 0         0 ERROR("Undefined \$message_type");
975 0         0 return "No message type!\n";
976             }
977              
978 178 50       547 if(!defined $recipient) {
979 0         0 ERROR('$recipient not defined!');
980 0         0 return "No recipient!\n";
981             }
982              
983 178         10381 my $yday = (localtime)[7];
984 178         2739 my $hour = (localtime)[2];
985 178         7214 my $messages_this_hour = $self->messages_sent_today->{$yday}->{$hour} += 1;
986              
987 178 100       5003 if($messages_this_hour > $self->max_messages_per_hour) {
988 2 50       13 $subject = "" if(!defined $subject); # Keep warning messages quiet.
989 2 50       11 $message_chunk = "" if(!defined $message_chunk); # Keep warning messages quiet.
990              
991 2         32 ERROR("Can't Send message because we've already tried to send $messages_this_hour of $self->max_messages_per_hour messages this hour.\n"
992             . "To: $recipient\n"
993             . "Subject: $subject\n"
994             . "Type: $message_type\n"
995             . "Message sent:\n"
996             . "$message_chunk"
997             );
998              
999             # Send 1 panic message out to jabber if this is our last message before quieting down.
1000 2         25 return "Too many messages ($messages_this_hour)\n";
1001             }
1002              
1003 176 50       1021 if(!$self->IsConnected) {
1004 0 0       0 $subject = "" if(!defined $subject); # Keep warning messages quiet.
1005 0 0       0 $message_chunk = "" if(!defined $message_chunk); # Keep warning messages quiet.
1006              
1007 0         0 ERROR("Can't Jabber server is down. Tried to send: \n"
1008             . "To: $recipient\n"
1009             . "Subject: $subject\n"
1010             . "Type: $message_type\n"
1011             . "Message sent:\n"
1012             . "$message_chunk"
1013             );
1014              
1015             # Send 1 panic message out to jabber if this is our last message before quieting down.
1016 0         0 return "Server is down.\n";
1017             }
1018              
1019             # Strip out anything that's not a printable character
1020             # Now with unicode support?
1021 176         701 $message_chunk =~ s/[^[:print:]]+/./xmsg;
1022              
1023 176         541 my $message_length = length($message_chunk);
1024 176         1195 DEBUG("Sending message $yday-$hour-$messages_this_hour $message_length bytes to $recipient");
1025 176         5648 $self->jabber_client->MessageSend(to => $recipient
1026             , body => $message_chunk
1027             , type => $message_type
1028             # , from => $connection_hash{$obj_ID}{'from_full'}
1029             , subject => $subject
1030             );
1031              
1032 176         730739 DEBUG("Sleeping " . $self->message_delay . " after sending message.");
1033 176         6661 Time::HiRes::sleep $self->message_delay; #Throttle messages.
1034              
1035 176 100       22767 if($messages_this_hour == $self->max_messages_per_hour) {
1036 2         119 $self->jabber_client->MessageSend(to => $recipient
1037             , body => "Cannot send more messages this hour. "
1038             . "$messages_this_hour of " . $self->max_messages_per_hour . " already sent."
1039             , type => $message_type
1040             );
1041             }
1042 176         8606 return; # Means we succeeded!
1043             }
1044              
1045             =item B<SetForumSubject>
1046              
1047             $bot->SetForumSubject($recipient, $subject);
1048              
1049             Sets the subject of a forum
1050              
1051             =cut
1052              
1053             sub SetForumSubject {
1054 2     2 1 21 my $self = shift;
1055              
1056 2         5 my $recipient = shift;
1057 2         6 my $subject = shift;
1058              
1059 2 50       58 if(length $subject > $self->max_message_size) {
1060 2         6 my $subject_len = length($subject);
1061 2         18 ERROR("Someone tried to send a subject message $subject_len bytes long!");
1062 2         64 my $subject = substr($subject, 0, $self->max_message_size);
1063 2         14 DEBUG("Truncated subject: $subject");
1064 2         17 return "Subject is too long!";
1065             }
1066 0           $self->_send_individual_message($recipient, "Setting subject to $subject", 'groupchat', $subject);
1067              
1068 0           return;
1069             }
1070              
1071             =item B<ChangeStatus>
1072              
1073             $bot->ChangeStatus($presence_mode, $status_string);
1074              
1075             Sets the Bot's presence status.
1076             $presence mode could be something like: (Chat, Available, Away, Ext. Away, Do Not Disturb).
1077             $status_string is an optional comment to go with your presence mode. It is not required.
1078              
1079             =cut
1080              
1081             sub ChangeStatus {
1082 0     0 1   my $self = shift;
1083 0           my $presence_mode = shift;
1084 0           my $status_string = shift; # (optional)
1085              
1086 0           $self->jabber_client->PresenceSend(show=>$presence_mode, status=>$status_string);
1087              
1088 0           return 1;
1089             }
1090              
1091             =item B<GetRoster>
1092              
1093             $bot->GetRoster();
1094              
1095             Returns a list of the people logged into the server.
1096             I suspect we really want to know who is in a paticular forum right?
1097             In which case we need another sub for this.
1098             =cut
1099              
1100             sub GetRoster {
1101 0     0 1   my $self = shift;
1102              
1103 0           my @rosterlist;
1104 0           foreach my $jid ($self->jabber_client->RosterDBJIDs()) {
1105 0           my $username =$jid->GetJID();
1106 0           push(@rosterlist, $username) ;
1107             }
1108 0           return @rosterlist;
1109             }
1110              
1111             =item B<GetStatus>
1112              
1113             Need documentation from Yago on this sub.
1114              
1115             =cut
1116              
1117             sub GetStatus {
1118              
1119 0     0 1   my $self = shift;
1120 0           my ($jid) = shift;
1121              
1122 0           my $Pres = $self->jabber_client->PresenceDBQuery($jid);
1123              
1124 0 0         if (!(defined($Pres))) {
1125              
1126 0           return "unavailable" ;
1127             }
1128              
1129 0           my $show = $Pres->GetShow();
1130 0 0         if ($show) {
1131              
1132 0           return $show;
1133             }
1134              
1135 0           return "available";
1136              
1137             }
1138              
1139             =item B<AddUser>
1140              
1141             Need documentation from Yago on this sub.
1142              
1143             =cut
1144              
1145             sub AddUser {
1146 0     0 1   my $self = shift;
1147 0           my $user = shift;
1148              
1149 0           $self->jabber_client->Subscription(type=>"subscribe", to=>$user);
1150 0           $self->jabber_client->Subscription(type=>"subscribed",to=>$user);
1151             }
1152              
1153             =item B<RmUser>
1154              
1155             Need documentation from Yago on this sub.
1156              
1157             =cut
1158              
1159             sub RmUser {
1160 0     0 1   my $self = shift;
1161 0           my $user = shift;
1162              
1163 0           $self->jabber_client->Subscription(type=>"unsubscribe", to=>$user);
1164 0           $self->jabber_client->Subscription(type=>"unsubscribed",to=>$user);
1165             }
1166             =back
1167              
1168             =head1 AUTHOR
1169              
1170             Todd Rinaldo C<< <perl-net-jabber-bot@googlegroups.com) > >>
1171              
1172             =head1 BUGS
1173              
1174             Please report any bugs or feature requests to
1175             C<perl-net-jabber-bot@googlegroups.com>, or through the web interface at
1176             L<http://code.google.com/p/perl-net-jabber-bot/issues/entry>.
1177             I will be notified, and then you'll automatically be notified of progress on
1178             your bug as I make changes.
1179              
1180             =head1 SUPPORT
1181              
1182             You can find documentation for this module with the perldoc command.
1183              
1184             perldoc Net::Jabber::Bot
1185              
1186             You can also look for information at:
1187              
1188             =over 4
1189              
1190             =item * AnnoCPAN: Annotated CPAN documentation
1191              
1192             L<http://annocpan.org/dist/Net-Jabber-Bot>
1193              
1194             =item * CPAN Ratings
1195              
1196             L<http://cpanratings.perl.org/d/Net-Jabber-Bot>
1197              
1198             =item * Metacpan
1199              
1200             L<https://metacpan.org/pod/Net::Jabber::Bot>
1201              
1202             =item * Github
1203              
1204             L<https://github.com/toddr/perl-net-jabber-bot>
1205              
1206             =back
1207              
1208             =head1 ACKNOWLEDGEMENTS
1209              
1210             =head1 COPYRIGHT & LICENSE
1211              
1212             Copyright 2007 Todd E Rinaldo, all rights reserved.
1213              
1214             This program is free software; you can redistribute it and/or modify it
1215             under the same terms as Perl itself.
1216              
1217             =cut
1218              
1219             __PACKAGE__->meta->make_immutable;
1220 3     3   35 no Moose;
  3         16  
  3         35  
1221 3     3   792 no MooseX::Types;
  3         20  
  3         248  
1222             1; # End of Net::Jabber::Bot