File Coverage

blib/lib/Net/YMSG.pm
Criterion Covered Total %
statement 30 230 13.0
branch 0 40 0.0
condition 0 16 0.0
subroutine 10 42 23.8
pod 13 27 48.1
total 53 355 14.9


line stmt bran cond sub pod time code
1             package Net::YMSG;
2              
3             =head1 NAME
4              
5             Net::YMSG - Interface to the Yahoo! Messenger IM protocol
6              
7             =head1 SYNOPSIS
8              
9             use Net::YMSG;
10              
11             my $yahoo = Net::YMSG->new(
12             id => 'your_yahoo_id',
13             password => 'your_password',
14             );
15             $yahoo->login or die "Can't login Yahoo!Messenger";
16             $yahoo->send('recipient_yahoo_id', 'Hello World!');
17              
18             =head1 DESCRIPTION
19              
20             Net::YMSG is a client class for connecting with the Yahoo! Messenger server, and transmitting and receiving a message.
21              
22             Since implement of a protocol is the result of analyzing and investigating a packet, it has an inadequate place. However, it is working as expected usually.
23              
24             =cut
25              
26 1     1   6094 use Carp;
  1         2  
  1         59  
27 1     1   830 use IO::Socket;
  1         41659  
  1         7  
28 1     1   2602 use IO::Select;
  1         2347  
  1         79  
29 1     1   854 use Net::YMSG::Buddy;
  1         4  
  1         33  
30 1     1   6243 use Net::YMSG::CRAM;
  1         3  
  1         32  
31              
32 1     1   7 use constant YMSG_STD_HEADER => 'YMSG';
  1         2  
  1         56  
33 1     1   7 use constant YMSG_SEPARATER => "\xC0\x80";
  1         1  
  1         47  
34 1     1   5 use constant YMSG_SALT => '_2S43d5f';
  1         2  
  1         39  
35              
36 1     1   5 use strict;
  1         2  
  1         36  
37              
38 1     1   5 use vars qw($VERSION);
  1         23  
  1         3094  
39             $VERSION = '1.2';
40              
41             =head1 METHODS
42              
43             This section documents method of the Net::YMSG class.
44              
45             =head2 Net::YMSG->new()
46              
47             It should be called with following arguments (items with default value are optional):
48              
49             id => yahoo id
50             password => password
51             pre_login_url => url which refers to setting information.
52             (default http://msg.edit.yahoo.com/config/)
53             hostname => server hostname
54             (default 'scs.yahoo.com)
55              
56             Returns a blessed instantiation of Net::YMSG.
57              
58             Note: If you plan to connect with Yahoo!India (yahoo.co.in), it sets up as follows.
59              
60             my $yahoo_japan = Net::YMSG->new(
61             pre_login_url => 'http://edit.my.yahoo.co.in/config/',
62             hostname => 'cs.yahoo.co.in',
63             );
64              
65             I
66              
67             =cut
68              
69             sub new
70             {
71 0     0 1   my $class = shift;
72 0           my %args = @_;
73              
74 0   0       bless {
      0        
75             id => $args{id},
76             password => $args{password},
77             hostname => $args{hostname} || 'scs.yahoo.com',
78             pre_login_url => $args{pre_login_url} || 'http://msg.edit.yahoo.com/config/',
79             handle => undef,
80             _read => IO::Select->new,
81             _write => IO::Select->new,
82             _error => IO::Select->new,
83             event_handler => undef,
84             buddy_list => [],
85             }, $class;
86             }
87              
88              
89             =head2 $yahoo->id([$yahoo_id])
90              
91             This method gets or sets the present B.
92              
93             =cut
94              
95             sub id
96             {
97 0     0 1   my $self = shift;
98 0 0         $self->{id} = shift if @_;
99 0           $self->{id};
100             }
101              
102              
103             =head2 $yahoo->password([$password])
104              
105             This method gets or sets the present B.
106              
107             =cut
108              
109             sub password
110             {
111 0     0 1   my $self = shift;
112 0 0         $self->{password} = shift if @_;
113 0           $self->{password};
114             }
115              
116              
117             =head2 $yahoo->login()
118              
119             Call this after C to logon the Yahoo!Messenger service.
120              
121             =cut
122              
123             sub login
124             {
125 0     0 1   my $self = shift;
126              
127 0           my $server = $self->get_connection;
128 0           my $msg = $self->_create_message(
129             87, 0,
130             '1' => $self->id,
131             );
132 0           $server->send($msg, 0);
133 0           my $event = $self->recv();
134             # _dump_packet($event->source);
135 0           my $cram = Net::YMSG::CRAM->new;
136 0           $cram->set_id($self->id);
137 0           $cram->set_password($self->password);
138 0           $cram->set_challenge_string($event->body);
139 0           my ($response_password, $response_crypt) = $cram->get_response_strings();
140 0           my $auth = $self->_create_message(
141             84, 0,
142             '0' => $self->id,
143             '6' => $response_password,
144             '96' => $response_crypt,
145             '2' => '1',
146             '1' => $self->id,
147             );
148 0           $server->send($auth);
149 0           my $buddy_list = $self->recv();
150              
151 0           my $login = $self->recv();
152 0           my $handler = $self->get_event_handler();
153 0 0         $handler->accept($login) if $handler;
154              
155             $self->add_event_source($server, sub {
156 0     0     my $event = $self->recv;
157 0           my $handler = $self->get_event_handler;
158 0           $handler->accept($event);
159 0           } ,'r');
160              
161 0           return $login->is_enable();
162             }
163              
164              
165             sub _dump_packet
166             {
167 0     0     my $source = shift;
168 0 0         print join ' ', map {
169 0           sprintf '%02x(%s)', ord $_, (/^[\w\-_]$/) ? $_ : '.';
170             } split //, $source;
171 0           print "\n";
172             }
173              
174              
175             =head2 $yahoo->send($yahoo_id, $message)
176              
177             This method send an Instant-Message B<$message> to the user specified by B<$yahoo_id>.
178             =cut
179              
180             sub send
181             {
182 0     0 1   my $self = shift;
183 0           my $recipient = shift;
184 0           my $message = join '', @_;
185 0           my $server = $self->handle;
186 0           my $event = $self->create('SendMessage');
187 0           $event->from($self->id);
188 0           $event->to($recipient);
189 0           $event->body($message);
190 0           $event->option(1515563606); # in Buddy list then 1515563606 else 1515563605
191 0           $server->send($event->to_raw_string, 0);
192             }
193              
194             =head2 $yahoo->chatsend($chatroom, $message)
195              
196             This method send a Message B<$message> to the given B<$chatroom>.
197              
198             =cut
199              
200              
201             sub chatsend
202             {
203 0     0 1   my $self = shift;
204 0           my $login = $self->{id};
205 0           my ($roomname, $message) = @_;
206             #my $message = join '', @_;
207            
208 0           my $body="1".YMSG_SEPARATER.$login.YMSG_SEPARATER."104".YMSG_SEPARATER.$roomname.YMSG_SEPARATER."117".YMSG_SEPARATER.$message.YMSG_SEPARATER."124".YMSG_SEPARATER."1".YMSG_SEPARATER;
209            
210 0   0       my $header = pack "a4Cx3nnNN",
211             YMSG_STD_HEADER,
212             9,
213             length $body,
214             168,
215             0,
216             $self->identifier || 0;
217              
218 0           my $msg = $header.$body;
219 0 0         if(! defined $self->identifier) {
220 0           print STDERR "ERROR:Identifier Not Found";
221             }
222 0           my $server=$self->get_connection();
223 0           my $num=$server->send($msg,0);
224            
225            
226             }
227              
228              
229             =head2 $yahoo->change_state($busy, $status_message)
230              
231             This method sets the I for the current user. 'Status message' is set by C<$status_message>. 'Busy icon' is set by the numerical value of C<$busy>.
232              
233             The C<$busy> should be called with following arguments:
234              
235             0 - I'm Available
236             1 - Busy
237             2 - Sleep
238              
239             =cut
240              
241             sub change_state
242             {
243 0     0 1   my $self = shift;
244 0           my $busy = shift;
245 0           my $message = join '', @_;
246 0           my $server = $self->handle;
247              
248 0           my $event = $self->create('ChangeState');
249 0           $event->status_code(99); # 99 : Custom status
250 0           $event->busy($busy);
251 0           $event->body($message);
252              
253 0           $server->send($event->to_raw_string, 0);
254             }
255              
256              
257             sub change_status_by_code
258             {
259 0     0 0   my $self = shift;
260 0   0       my $status_code = shift || 0;
261 0           my $server = $self->handle;
262              
263 0           my $event = $self->create('ChangeState');
264 0           $event->status_code($status_code);
265 0           $event->busy(1);
266              
267 0           $server->send($event->to_raw_string, 0);
268             }
269              
270              
271             sub ping
272             {
273 0     0 0   my $self = shift;
274 0           my $server = $self->get_connection;
275 0           my $command = $self->_create_message(
276             76, 0, 0, ''
277             );
278 0           $server->send($command, 0);
279 0           my $pong = $self->recv();
280 0           return $pong->is_enable;
281             }
282              
283             =head2 $yahoo->recv()
284              
285             This method reads the message from a server socket and returns a corresponding B.
286             The B which will be returned is as follows:
287              
288             Net::YMSG::InvalidLogin - Invalid Login
289             Net::YMSG::Login - Succeeded in Login.
290             Net::YMSG::GoesOnline - Buddy has logged in.
291             Net::YMSG::ReceiveMessage - Message was received.
292             Net::YMSG::ChangeState - Buddy has change status.
293             Net::YMSG::GoesOffline - Buddy logged out.
294             Net::YMSG::NewFriendAlert - New Friend Alert.
295             Net::YMSG::ChatRoomLogon - Log in chat room
296             Net::YMSG::ChatRoomReceive- Log in chat room
297             Net::YMSG::ChatRoomLogoff - Log in chat room
298             Net::YMSG::UnImplementEvent - Un-implemented event was received.
299              
300             All event objects have the following attributes:
301              
302             =over 4
303              
304             =item $event->from
305              
306             B which invoked the event.
307              
308             =item $event->to
309              
310             B which should receive an event.
311              
312             =item $event->body
313              
314             The contents of an event. The message and state which were transmitted.
315              
316             =item $event->code
317              
318             The event number on Yahoo Messenger Protocol.
319              
320             =back
321              
322             =cut
323              
324             sub recv
325             {
326 0     0 1   my $self = shift;
327 0           require Net::YMSG::EventFactory;
328 0           my $event_factory = Net::YMSG::EventFactory->new($self);
329 0           return $event_factory->create_by_raw_data();
330             }
331              
332              
333             =head2 $yahoo->get_connection()
334              
335             This method returns a raw server socket. When connection has already ended, the socket is returned, and when not connecting, it connects newly.
336              
337             =cut
338              
339             sub get_connection
340             {
341 0     0 1   my $self = shift;
342 0 0         return $self->handle if $self->handle;
343              
344 0 0         my $server = IO::Socket::INET->new(
345             PeerAddr => $self->{hostname},
346             PeerPort => $self->get_port,
347             Proto => 'tcp',
348             Timeout => 30,
349             ) or die $!;
350 0           $server->autoflush(1);
351 0           return $self->handle($server);
352             }
353              
354              
355              
356             sub buddy_list
357             {
358 0     0 0   my $self = shift;
359 0 0         @{$self->{buddy_list}} = @_ if @_;
  0            
360 0           return @{$self->{buddy_list}};
  0            
361             }
362              
363              
364             sub get_buddy_by_name
365             {
366 0     0 0   my $self = shift;
367 0           my $name = shift;
368 0           my ($buddy) = grep { lc $_->name eq lc $name } $self->buddy_list;
  0            
369 0           return $buddy;
370             }
371              
372              
373             =head2 $yahoo->set_event_hander($event_handler)
374              
375             This method sets the Event handler for a specific Yahoo!Messenger server event. C<$event_handler> is the sub class of Net::YMSG::EventHandler.
376              
377             Note: The event which can be overwritten should look at the method signature of L.
378              
379             =cut
380              
381             sub set_event_handler
382             {
383 0     0 0   my $self = shift;
384 0           $self->{event_handler} = shift;
385             }
386              
387              
388             sub get_event_handler
389             {
390 0     0 0   my $self = shift;
391 0           return $self->{event_handler};
392             }
393              
394              
395             =head2 $yahoo->add_event_source($file_handle, $code_ref, $flag)
396              
397             This method adds the file handle (event sauce) to supervise. The file handle to add is specified by C<$file_handle>. The code reference to the processing to perform is specified by $code_ref.
398              
399             C<$flag> eq 'r' - set when the file handle to add is an object for read.
400             C<$flag> eq 'w' - set when the file handle to add is an object for write.
401              
402             By adding another handle (for example, STDIN), processing can be performed based on those inputs. Usually, the server socket of 'Yahoo!Messenger server' is set as a candidate for surveillance.
403              
404             ex:
405             # The input of STDIN is transmitted to 'EXAMPLE_YAHOO_ID'.
406             $yahoo->add_event_source(\*STDIN, sub {
407             my $message = scalar ;
408             chomp $message;
409             $yahoo->send('EXAMPLE_YAHOO_ID', $message);
410             }, 'r');
411              
412             =cut
413              
414             sub add_event_source
415             {
416 0     0 1   my $self = shift;
417 0           my ($handle, $code, $flag, $obj) = @_;
418              
419 0           foreach my $mode (split //, lc $flag) {
420 0 0         if ($mode eq 'r') {
    0          
421 0           $self->{_read}->add($handle);
422             }
423             elsif ($mode eq 'w') {
424 0           $self->{_write}->add($handle);
425             }
426             }
427 0           $self->{_connhash}->{$handle} = [ $code, $obj ];
428             }
429              
430              
431             =head2 $yahoo->start()
432              
433             If you're writing a fairly simple application that doesn't need to interface with other event-loop-based libraries, you can just call start() to begin communicating with the server.
434              
435             =cut
436              
437             sub start
438             {
439 0     0 1   my $self = shift;
440 0           while (1) {
441 0           $self->do_one_loop;
442             }
443             }
444              
445              
446             sub do_one_loop
447             {
448 0     0 0   my $self = shift;
449              
450 0           for my $ready (IO::Select->select(
451             $self->{_read}, $self->{_write}, $self->{_error}, 10
452             ))
453             {
454 0           for my $handle (@$ready) {
455 0           my $event = $self->{_connhash}->{$handle};
456             #$event->[0]->($event->[1] ? ($event->[1], $handle) : $handle);
457 0           $event->[0]();
458             }
459             }
460             }
461              
462             sub invisible {
463 0     0 0   my $self=shift;
464 0           my $msg = $self->_create_message(03,0,'');
465 0           my $server= $self->get_connection();
466 0           $server->send($msg,0);
467             #return $msg;
468             }
469             =head2 $yahoo->invisible()
470              
471             This method makes you B to other users..
472              
473             =cut
474              
475              
476             sub pre_join {
477 0     0 0   my $self = shift;
478 0           my $login = $self->{id};
479             #my ($login) = @_;
480             #print "recd : $login\n";
481 0           my $body="109".YMSG_SEPARATER.$login.YMSG_SEPARATER."1".YMSG_SEPARATER.$login.YMSG_SEPARATER."6".YMSG_SEPARATER."abcde".YMSG_SEPARATER;
482            
483 0   0       my $header = pack "a4Cx3nnNN",
484             YMSG_STD_HEADER,
485             9,
486             length $body,
487             150,
488             0,
489             $self->identifier || 0;
490              
491 0           my $msg = $header.$body;
492 0 0         if(! defined $self->identifier) {
493 0           print STDERR "ERROR:Identifier Not Found";
494             }
495 0           my $server=$self->get_connection();
496 0           my $num=$server->send($msg,0);
497             #print STDERR "Send $num bytes\n";
498 0           return $msg;
499              
500             }
501              
502             sub join_room {
503 0     0 1   my $self = shift;
504 0           my $login = $self->{id};
505 0           my ($roomname , $roomid)= @_;
506             #print "recd : $login $roomname $roomid\n";
507             # my $msg = $self->_create_message(98,0,
508             # '1' => $login,
509             # '104' => $roomname,
510             # '129' => $roomid,
511             # '62' => "2",
512             # );
513            
514 0           my $body="1".YMSG_SEPARATER.$login.YMSG_SEPARATER."104".YMSG_SEPARATER.$roomname.YMSG_SEPARATER."129".YMSG_SEPARATER.$roomid.YMSG_SEPARATER."62".YMSG_SEPARATER."2".YMSG_SEPARATER;
515            
516 0   0       my $header = pack "a4Cx3nnNN",
517             YMSG_STD_HEADER,
518             9,
519             length $body,
520             152,
521             0,
522             $self->identifier || 0;
523              
524 0           my $msg = $header.$body;
525 0 0         if(! defined $self->identifier) {
526 0           print STDERR "ERROR:Identifier Not Found";
527             }
528 0           my $server=$self->get_connection();
529 0           my $num=$server->send($msg,0);
530             #print STDERR "Send $num bytes\n";
531 0           return $msg;
532             }
533              
534             =head2 $yahoo->join_room($roomname,$roomid)
535              
536             This method logs you in B<$roomname>. You need to provide the B<$id> along with Roomname.
537             Check out http://www.cse.iitb.ac.in/varunk/YahooProtocol.php for the list of RoomIDs corresponding
538             to the Room you wish to join.[This is a comprehensive list and might not list all available rooms
539             at that moment; Follow instructions to get the roomid of the room you wish to join]
540              
541             =cut
542              
543              
544             sub logoffchat {
545 0     0 1   my $self=shift;
546 0           my $login=$self->{id};
547 0           my $body="1".YMSG_SEPARATER.$login.YMSG_SEPARATER;
548            
549 0   0       my $header = pack "a4Cx3nnNN",
550             YMSG_STD_HEADER,
551             9,
552             length $body,
553             160,
554             0,
555             $self->identifier || 0;
556              
557 0           my $msg = $header.$body;
558 0 0         if(! defined $self->identifier) {
559 0           print STDERR "ERROR:Identifier Not Found";
560             }
561 0           my $server=$self->get_connection();
562 0           my $num=$server->send($msg,0);
563             #print STDERR "Send $num bytes\n";
564 0           return $msg;
565             }
566              
567             =head2 $yahoo->logoffchat()
568              
569             This method logs you off any chat rooms you are currently logged into.
570              
571             =cut
572              
573             sub get_port
574             {
575 0     0 0   my $self = shift;
576 0 0         return $self->{port} if $self->{port};
577 0           return 5050;
578             }
579              
580              
581             sub _create_message
582             {
583 0     0     my $self = shift;
584 0           my $event_code = shift;
585 0           my $option = shift;
586 0           my %param = @_;
587              
588 0           my $body = join '', map {
589 0           $_. YMSG_SEPARATER. $param{$_}. YMSG_SEPARATER
590             } keys %param;
591              
592 0 0         if ($event_code == 6) {
593 0           my $buddy = $self->get_buddy_by_name($param{5});
594 0 0         if ($buddy) {
595 0           $option = 1515563606;
596             } else {
597 0           $option = 1515563605;
598             }
599             }
600 0 0         if ($event_code == 3) {
601 0           $body = "10".YMSG_SEPARATER."12".YMSG_SEPARATER;
602             }
603 0   0       my $header = pack "a4Cx3nnNN",
604             YMSG_STD_HEADER,
605             9,
606             length $body,
607             $event_code,
608             $option,
609             $self->identifier || 0;
610 0           return $header. $body;
611             }
612              
613              
614             sub create
615             {
616 0     0 0   my $self = shift;
617 0           my $event_name = shift;
618              
619 0           require Net::YMSG::EventFactory;
620 0           my $event_factory = Net::YMSG::EventFactory->new($self);
621 0           return $event_factory->create_by_name($event_name);
622             }
623              
624              
625             sub _create_login_command
626             {
627 0     0     my $self = shift;
628 0           my $event = $self->create('Login');
629 0           $event->id($self->id);
630 0           $event->password($self->password);
631 0           $event->from($self->id);
632 0           $event->hide(0);
633 0           return $event->to_raw_string;
634             }
635              
636              
637             sub handle
638             {
639 0     0 0   my $self = shift;
640 0 0         $self->{handle} = shift if @_;
641 0           $self->{handle};
642             }
643              
644              
645             sub identifier
646             {
647 0     0 0   my $self = shift;
648 0 0         $self->{identifier} = shift if @_;
649 0           $self->{identifier};
650             }
651              
652              
653             #
654              
655              
656             # my @buddy = $self->_get_buddy_list_by_array(
657             # $self->_get_list_by_name('BUDDYLIST', $response->content)
658             # );
659             # $self->buddy_list(@buddy);
660              
661              
662              
663              
664             sub _get_list_by_name
665             {
666 0     0     my $self = shift;
667 0           my $name = shift;
668 0           my $string = shift;
669              
670 0 0         if ($string =~ /BEGIN $name\r?\n(.*)\r?\nEND $name/s) {
671 0           my @list = split /\r?\n/, $1;
672 0           return @list;
673             }
674             }
675              
676              
677             sub add_buddy_by_name
678             {
679 0     0 0   my $self = shift;
680 0           my $group = shift;
681 0           my @buddy_name = @_;
682 0           my @buddy_list = $self->buddy_list();
683 0           for my $name (@buddy_name) {
684 0           my $buddy = Net::YMSG::Buddy->new;
685 0           $buddy->name($name);
686 0           push @buddy_list, $buddy;
687             }
688 0           $self->buddy_list(@buddy_list);
689             }
690              
691              
692             1;
693             __END__