File Coverage

blib/lib/Net/YahooMessenger.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package Net::YahooMessenger;
2              
3             =head1 NAME
4              
5             Net::YahooMessenger - Interface to the Yahoo!Messenger IM protocol
6              
7             =head1 SYNOPSIS
8              
9             use Net::YahooMessenger;
10              
11             my $yahoo = Net::YahooMessenger->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 CAVEATS
19              
20             Module was changed to work with the latest version(16) of the Yahoo protocol. However, this might not work very well, your opinions and suggestions will be helpfull.
21              
22             =head1 DESCRIPTION
23              
24             Net::YahooMessenger is a client class for connecting with the Yahoo!Messenger server, and transmitting and receiving a message.
25              
26             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.
27              
28             =cut
29              
30 1     1   6386 use Carp;
  1         2  
  1         66  
31 1     1   1006 use IO::Socket;
  1         27661  
  1         6  
32 1     1   1644 use IO::Select;
  1         2269  
  1         61  
33 1     1   889 use Net::YahooMessenger::Buddy;
  1         3  
  1         28  
34 1     1   800 use Net::YahooMessenger::CRAM;
  1         2  
  1         24  
35 1     1   537 use Net::YahooMessenger::HTTPS;
  0            
  0            
36              
37             use constant YMSG_STD_HEADER => 'YMSG';
38             use constant YMSG_SEPARATER => "\xC0\x80";
39             use constant YMSG_SALT => '_2S43d5f';
40             use constant YMSG_PROTOCOL_VERSION => '16';
41              
42             use strict;
43              
44             use vars qw($VERSION);
45             $VERSION = '0.19';
46              
47             =head1 METHODS
48              
49             This section documents method of the Net::YahooMessenger class.
50              
51             =head2 Net::YahooMessenger->new()
52              
53             It should be called with following arguments (items with default value are optional):
54              
55             id => yahoo id
56             password => password
57             hostname => server hostname
58             (default 'cs.yahoo.com)
59              
60             Returns a blessed instantiation of Net::YahooMessenger.
61              
62             Note: If you plan to connect with Yahoo!Japan(yahoo.co.jp), it sets up as follows.
63              
64             my $yahoo_japan = Net::YahooMessenger->new(
65             hostname => 'cs.yahoo.co.jp',
66             );
67              
68             I
69              
70             =cut
71              
72             sub new {
73             my $class = shift;
74             my %args = @_;
75              
76             bless {
77             id => $args{id},
78             password => $args{password},
79             hostname => $args{hostname} || 'scsa.msg.yahoo.com',
80              
81             #this is probably not needed for the version 16 YM protocol
82             pre_login_url => $args{pre_login_url}
83             || 'http://msg.edit.yahoo.com/config/',
84             handle => undef,
85             _read => IO::Select->new,
86             _write => IO::Select->new,
87             _error => IO::Select->new,
88             event_handler => undef,
89             buddy_list => [],
90             }, $class;
91             }
92              
93             =head2 $yahoo->id([$yahoo_id])
94              
95             This method gets or sets the present B.
96              
97             =cut
98              
99             sub id {
100             my $self = shift;
101             $self->{id} = shift if @_;
102             $self->{id};
103             }
104              
105             =head2 $yahoo->password([$password])
106              
107             This method gets or sets the present B.
108              
109             =cut
110              
111             sub password {
112             my $self = shift;
113             $self->{password} = shift if @_;
114             $self->{password};
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             my $self = shift;
125              
126             my $server = $self->get_connection;
127              
128             my $msg = $self->_create_message( 87, 0, '1' => $self->id, );
129             $server->send( $msg, 0 );
130             my $event = $self->recv();
131             my $https = Net::YahooMessenger::HTTPS->new( $self->id, $self->password,
132             $event->body );
133             my $auth = $self->_create_message(
134             84, 0,
135             '1' => $self->id,
136             '0' => $self->id,
137             '277' => $https->y_string,
138             '278' => $https->t_string,
139             '307' => $https->md5_string,
140             '244' => '4194239',
141             '2' => $self->id,
142             '2' => $self->id,
143             '135' => '9.0.0.2152',
144             );
145             $server->send($auth);
146             my $user_info = $self->recv();
147             my $buddy_list = $self->recv();
148              
149             my $login = $self->recv();
150             my $handler = $self->get_event_handler();
151             $handler->accept($login) if $handler;
152              
153             $self->add_event_source(
154             $server,
155             sub {
156             my $event = $self->recv;
157             my $handler = $self->get_event_handler;
158             $handler->accept($event);
159             },
160             'r'
161             );
162              
163             return $login->is_enable();
164             }
165              
166             sub _dump_packet {
167             my $source = shift;
168             print join ' ',
169             map { sprintf '%02x(%s)', ord $_, (/^[\w\-_]$/) ? $_ : '.'; } split //,
170             $source;
171             print "\n";
172             }
173              
174             =head2 $yahoo->send($yahoo_id, $message)
175              
176             This method send an Instant-Message C<$message> to the user specified by C<$yahoo_id>. A kanji code is Shift_JIS when including Japanese in $message.
177              
178             =cut
179              
180             sub send {
181             my $self = shift;
182             my $recipient = shift;
183             my $message = join '', @_;
184             my $server = $self->handle;
185              
186             my $event = $self->create('SendMessage');
187             $event->from( $self->id );
188             $event->to($recipient);
189             $event->body($message);
190             $event->option(1515563606); # in Buddy list then 1515563606 else 1515563605
191             $server->send( $event->to_raw_string, 0 );
192             }
193              
194             =head2 $yahoo->change_state($busy, $status_message)
195              
196             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>.
197              
198             The C<$busy> should be called with following arguments:
199              
200             0 - I'm Available
201             1 - Busy
202             2 - Sleep
203              
204             =cut
205              
206             sub change_state {
207             my $self = shift;
208             my $busy = shift;
209             my $message = join '', @_;
210             my $server = $self->handle;
211              
212             my $event = $self->create('ChangeState');
213             $event->status_code(99); # 99 : Custom status
214             $event->busy($busy);
215             $event->body($message);
216              
217             $server->send( $event->to_raw_string, 0 );
218             }
219              
220             sub change_status_by_code {
221             my $self = shift;
222             my $status_code = shift || 0;
223             my $server = $self->handle;
224              
225             my $event = $self->create('ChangeState');
226             $event->status_code($status_code);
227             $event->busy(1);
228              
229             $server->send( $event->to_raw_string, 0 );
230             }
231              
232             sub ping {
233             my $self = shift;
234             my $server = $self->get_connection;
235             my $command = $self->_create_message( 76, 0, 0, '' );
236             $server->send( $command, 0 );
237             my $pong = $self->recv();
238             return $pong->is_enable;
239             }
240              
241             =head2 $yahoo->recv()
242              
243             This method reads the message from a server socket and returns a corresponding B.
244             The B which will be returned is as follows:
245              
246             Net::YahooMessenger::InvalidLogin - Invalid Login
247             Net::YahooMessenger::Login - Succeeded in Login.
248             Net::YahooMessenger::GoesOnline - Buddy has logged in.
249             Net::YahooMessenger::ReceiveMessage - Message was received.
250             Net::YahooMessenger::ChangeState - Buddy has change status.
251             Net::YahooMessenger::GoesOffline - Buddy logged out.
252             Net::YahooMessenger::NewFriendAlert - New Friend Alert.
253             Net::YahooMessenger::UnImplementEvent - Un-implemented event was received.
254              
255             All event objects have the following attributes:
256              
257             =over 4
258              
259             =item $event->from
260              
261             B which invoked the event.
262              
263             =item $event->to
264              
265             B which should receive an event.
266              
267             =item $event->body
268              
269             The contents of an event. The message and state which were transmitted.
270              
271             =item $event->code
272              
273             The event number on Yahoo Messenger Protocol.
274              
275             =back
276              
277             =cut
278              
279             sub recv {
280             my $self = shift;
281             require Net::YahooMessenger::EventFactory;
282             my $event_factory = Net::YahooMessenger::EventFactory->new($self);
283             return $event_factory->create_by_raw_data();
284             }
285              
286             =head2 $yahoo->get_connection()
287              
288             This method returns a raw server socket. When connection has already ended, the socket is returned, and when not connecting, it connects newly.
289              
290             =cut
291              
292             sub get_connection {
293             my $self = shift;
294             return $self->handle if $self->handle;
295              
296             my $server = IO::Socket::INET->new(
297             PeerAddr => $self->{hostname},
298             PeerPort => $self->get_port,
299             Proto => 'tcp',
300             Timeout => 30,
301             ) or die $!;
302             $server->autoflush(1);
303             return $self->handle($server);
304             }
305              
306             sub buddy_list {
307             my $self = shift;
308             @{ $self->{buddy_list} } = @_ if @_;
309             return @{ $self->{buddy_list} };
310             }
311              
312             sub get_buddy_by_name {
313             my $self = shift;
314             my $name = shift;
315             my ($buddy) = grep { lc $_->name eq lc $name } $self->buddy_list;
316             return $buddy;
317             }
318              
319             =head2 $yahoo->set_event_hander($event_handler)
320              
321             This method sets the Event handler for a specific Yahoo!Messenger server event. C<$event_handler> is the sub class of Net::YahooMessenger::EventHandler.
322              
323             Note: The event which can be overwritten should look at the method signature of L.
324              
325             =cut
326              
327             sub set_event_handler {
328             my $self = shift;
329             $self->{event_handler} = shift;
330             }
331              
332             sub get_event_handler {
333             my $self = shift;
334             return $self->{event_handler};
335             }
336              
337             =head2 $yahoo->add_event_source($file_handle, $code_ref, $flag)
338              
339             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.
340              
341             C<$flag> eq 'r' - set when the file handle to add is an object for read.
342             C<$flag> eq 'w' - set when the file handle to add is an object for write.
343              
344             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.
345              
346             ex:
347             # The input of STDIN is transmitted to 'EXAMPLE_YAHOO_ID'.
348             $yahoo->add_event_source(\*STDIN, sub {
349             my $message = scalar ;
350             chomp $message;
351             $yahoo->send('EXAMPLE_YAHOO_ID', $message);
352             }, 'r');
353              
354             =cut
355              
356             sub add_event_source {
357             my $self = shift;
358             my ( $handle, $code, $flag, $obj ) = @_;
359              
360             foreach my $mode ( split //, lc $flag ) {
361             if ( $mode eq 'r' ) {
362             $self->{_read}->add($handle);
363             }
364             elsif ( $mode eq 'w' ) {
365             $self->{_write}->add($handle);
366             }
367             }
368             $self->{_connhash}->{$handle} = [ $code, $obj ];
369             }
370              
371             =head2 $yahoo->start()
372              
373             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.
374              
375             =cut
376              
377             sub start {
378             my $self = shift;
379             while (1) {
380             $self->do_one_loop;
381             }
382             }
383              
384             sub do_one_loop {
385             my $self = shift;
386              
387             for my $ready (
388             IO::Select->select(
389             $self->{_read}, $self->{_write}, $self->{_error}, 10
390             )
391             )
392             {
393             for my $handle (@$ready) {
394             my $event = $self->{_connhash}->{$handle};
395             $event->[0]->( $event->[1] ? ( $event->[1], $handle ) : $handle );
396             }
397             }
398             }
399              
400             sub get_port {
401             my $self = shift;
402             return $self->{port} if $self->{port};
403             return 5050;
404             }
405              
406             sub _create_message {
407             my $self = shift;
408             my $event_code = shift;
409             my $option = shift;
410             my @param = @_;
411             my $body = '';
412              
413             while (@param) {
414             my $key = shift @param;
415             my $value = shift @param;
416             $body .= $key . YMSG_SEPARATER . $value . YMSG_SEPARATER;
417             }
418              
419             my $header = pack "a4xCx2nnNN",
420             YMSG_STD_HEADER,
421             YMSG_PROTOCOL_VERSION,
422             length $body,
423             $event_code,
424             $option,
425             $self->identifier || 0;
426             return $header . $body;
427             }
428              
429             sub create {
430             my $self = shift;
431             my $event_name = shift;
432              
433             require Net::YahooMessenger::EventFactory;
434             my $event_factory = Net::YahooMessenger::EventFactory->new($self);
435             return $event_factory->create_by_name($event_name);
436             }
437              
438             sub _create_login_command {
439             my $self = shift;
440             my $event = $self->create('Login');
441             $event->id( $self->id );
442             $event->password( $self->password );
443             $event->from( $self->id );
444             $event->hide(0);
445             return $event->to_raw_string;
446             }
447              
448             sub handle {
449             my $self = shift;
450             $self->{handle} = shift if @_;
451             $self->{handle};
452             }
453              
454             sub identifier {
455             my $self = shift;
456             $self->{identifier} = shift if @_;
457             $self->{identifier};
458             }
459              
460             #
461              
462             # my @buddy = $self->_get_buddy_list_by_array(
463             # $self->_get_list_by_name('BUDDYLIST', $response->content)
464             # );
465             # $self->buddy_list(@buddy);
466              
467             sub _get_list_by_name {
468             my $self = shift;
469             my $name = shift;
470             my $string = shift;
471              
472             if ( $string =~ /BEGIN $name\r?\n(.*)\r?\nEND $name/s ) {
473             my @list = split /\r?\n/, $1;
474             return @list;
475             }
476             }
477              
478             sub add_buddy_by_name {
479             my $self = shift;
480             my $group = shift;
481             my @buddy_name = @_;
482             my @buddy_list = $self->buddy_list();
483             for my $name (@buddy_name) {
484             my $buddy = Net::YahooMessenger::Buddy->new;
485             $buddy->name($name);
486             push @buddy_list, $buddy;
487             }
488             $self->buddy_list(@buddy_list);
489             }
490              
491             1;
492             __END__