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