root/POE-Component-Client-MSN/trunk/lib/POE/Component/Client/MSN.pm

Revision 926 (checked in by miyagawa, 17 years ago)

Initial revision

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 package POE::Component::Client::MSN;
2
3 use strict;
4 use vars qw($VERSION);
5 $VERSION = 0.01;
6
7 use vars qw($Default);
8 $Default = {
9     port => 1863,
10     hostname => 'messenger.hotmail.com',
11 };
12
13 use POE qw(Wheel::SocketFactory Wheel::ReadWrite Driver::SysRW Filter::Line Filter::Stream
14            Filter::MSN Component::Client::HTTP);
15 use POE::Component::Client::MSN::Command;
16 use HTTP::Request;
17 use Digest::MD5;
18 use Socket;
19 use URI::Escape ();
20
21 sub spawn {
22     my($class, %args) = @_;
23     $args{Alias} ||= 'msn';
24
25     # session for myself
26     POE::Session->create(
27         inline_states => {
28             _start => \&_start,
29             _stop  => \&_stop,
30
31             # internals
32             _sock_up   => \&_sock_up,
33             _sock_down => \&_sock_down,
34             _sb_sock_up => \&_sb_sock_up,
35             _unregister => \&_unregister,
36
37             # API
38             notify     => \&notify,
39             register   => \&register,
40             unregister => \&unregister,
41             connect    => \&connect,
42             login      => \&login,
43
44             handle_event => \&handle_event,
45
46             # commands
47             VER => \&got_version,
48             CVR => \&got_client_version,
49             CHG => \&got_change_status,
50             XFR => \&got_xfer,
51             USR => \&got_user,
52 #           LST => \&got_list,
53 #           ILN => \&got_goes_online,
54             NLN => \&handle_common,
55             FLN => \&handle_common,
56 #           RNG => \&got_ring,
57             MSG => \&handle_common,
58             CHL => \&got_challenge,
59             QRY => \&handle_common,
60             SYN => \&got_synchronization,
61             LSG => \&got_group,
62             LST => \&got_list,
63 #           OUT => \&got_banned,
64
65             # states
66             got_1st_response => \&got_1st_response,
67             passport_login   => \&passport_login,
68             got_2nd_response => \&got_2nd_response,
69         },
70         args => [ \%args ],
71     );
72
73     # HTTP cliens session
74     POE::Component::Client::HTTP->spawn(Agent => 'MSMSGS', Alias => 'ua');
75 }
76
77 sub _start {
78     $_[KERNEL]->alias_set($_[ARG0]->{Alias});
79     $_[HEAP]->{transaction} = 0;
80 }
81
82 sub _stop { }
83
84 sub register {
85     my($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
86     $kernel->refcount_increment($sender->ID, __PACKAGE__);
87     $heap->{listeners}->{$sender->ID} = 1;
88 }
89
90
91 sub unregister {
92     my($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
93     $kernel->yield(_unregister => $sender->ID);
94 }
95
96 sub _unregister {
97     my($kernel, $heap, $session) = @_[KERNEL, HEAP, ARG0];
98     $kernel->refcount_decrement($session, __PACKAGE__);
99     delete $heap->{listeners}->{$session};
100 }
101
102 sub notify {
103     my($kernel, $heap, $name, $event) = @_[KERNEL, HEAP, ARG0, ARG1];
104 #    $event ||= POE::Component::Client::MSN::Event::Null->new;
105     $kernel->post($_ => "msn_$name" => $event->args) for keys %{$heap->{listeners}};
106 }
107
108 sub connect {
109     my($kernel, $heap, $args) = @_[KERNEL, HEAP, ARG0];
110
111     # set up parameters
112     $heap->{$_} = $args->{$_}
113         for qw(username password);
114     $heap->{$_} = $args->{$_} || $Default->{$_}
115         for qw(hostname port);
116
117     return if $heap->{sock};
118     $heap->{sock} = POE::Wheel::SocketFactory->new(
119         SocketDomain => AF_INET,
120         SocketType => SOCK_STREAM,
121         SocketProtocol => 'tcp',
122         RemoteAddress => $heap->{hostname},
123         RemotePort => $heap->{port},
124         SuccessEvent => '_sock_up',
125         FailureEvent => '_sock_failed',
126     );
127 }
128
129 sub _sock_up {
130     my($kernel, $heap, $socket) = @_[KERNEL, HEAP, ARG0];
131
132     # new ReadWrite wheel for the socket
133     $heap->{sock} = POE::Wheel::ReadWrite->new(
134         Handle => $socket,
135         Driver => POE::Driver::SysRW->new,
136         Filter => POE::Filter::MSN->new,
137         ErrorEvent => '_sock_down',
138     );
139     $heap->{sock}->event(InputEvent => 'handle_event');
140     $heap->{sock}->put(
141         POE::Component::Client::MSN::Command->new(VER => "MSNP9 CVR0" => $heap),
142     );
143 }
144
145
146 sub _sock_failed {
147     my($kernel, $heap) = @_[KERNEL, HEAP];
148     $kernel->yield(notify => socket_error => ());
149     for my $session (keys %{$heap->{listeners}}) {
150         $kernel->yield(_unregister => $session);
151     }
152 }
153
154 sub _sock_down {
155     my($kernel, $heap) = @_[KERNEL, HEAP];
156     warn "sock is down";
157     delete $heap->{sock};
158 }
159
160 sub handle_event {
161     my($kernel, $heap, $command) = @_[KERNEL, HEAP, ARG0];
162     if ($command->errcode) {
163         warn "got error: ", $command->errcode;
164         $kernel->yield(notiy => got_error => $command);
165     } else {
166         $kernel->yield($command->name, $command);
167     }
168 }
169
170 sub got_version {
171     $_[HEAP]->{sock}->put(
172         POE::Component::Client::MSN::Command->new(CVR => "0x0409 winnt 5.1 i386 MSNMSGR 6.0.0602 MSMSGS $_[HEAP]->{username}" => $_[HEAP]),
173     );
174 }
175
176 sub got_client_version {
177     $_[HEAP]->{sock}->put(
178         POE::Component::Client::MSN::Command->new(USR => "TWN I $_[HEAP]->{username}" => $_[HEAP]),
179     );
180 }
181
182 sub got_xfer {
183     my($kernel, $heap, $command) = @_[KERNEL, HEAP, ARG0];
184     if ($command->args->[0] eq 'NS') {
185         @{$heap}{qw(hostname port)} = split /:/, $command->args->[1];
186         # switch to Notification Server
187         $_[HEAP]->{sock} = POE::Wheel::SocketFactory->new(
188             SocketDomain => AF_INET,
189             SocketType => SOCK_STREAM,
190             SocketProtocol => 'tcp',
191             RemoteAddress => $heap->{hostname},
192             RemotePort => $heap->{port},
193             SuccessEvent => '_sock_up',
194             FailureEvent => '_sock_failed',
195         );
196     }
197 }
198
199 sub got_user {
200     my $event = $_[ARG0];
201     if ($event->args->[1] eq 'S') {
202         $_[HEAP]->{cookie} = $event->args->[2];
203         my $request = HTTP::Request->new(GET => 'https://nexus.passport.com/rdr/pprdr.asp');
204         $_[KERNEL]->post(ua => request => got_1st_response => $request);
205     }
206     elsif ($event->args->[0] eq 'OK') {
207         $_[KERNEL]->yield(notify => signin => $event);
208         # set initial status
209         $_[HEAP]->{sock}->put(
210             POE::Component::Client::MSN::Command->new(CHG => "NLN" => $_[HEAP]),
211         );
212     }
213 }
214 #       $_[HEAP]->{sock}->put(
215 #           POE::Component::Client::MSN::Command->new(USR => "MD5 S $response" => $_[HEAP]),
216 #       );
217 #    }
218 #    elsif ($_[ARG0]->args->[0] eq 'OK') {
219 #       $_[HEAP]->{sock}->put(
220 #           POE::Component::Client::MSN::Command->new(CHG => 'NLN' => $_[HEAP]),
221 #       );
222 #       $_[HEAP]->{sock}->put(
223 #           POE::Component::Client::MSN::Command->new(SYN => 0 => $_[HEAP]),
224 #       );
225 #    }
226
227 sub got_1st_response {
228     my($request_packet, $response_packet) = @_[ARG0, ARG1];
229     my $response = $response_packet->[0];
230
231     my $passport_url = (_fake_header($response, 'PassportURLs') =~ /DALogin=(.*?),/)[0]
232         or warn $response->as_string;
233     if ($passport_url) {
234         $_[KERNEL]->yield(passport_login => "https://$passport_url");
235     }
236 }
237
238 sub passport_login {
239     my $passport_url = $_[ARG0];
240     my $request = HTTP::Request->new(GET => $passport_url);
241     my $sign_in  = URI::Escape::uri_escape($_[HEAP]->{username});
242     my $password = URI::Escape::uri_escape($_[HEAP]->{password});
243     $request->header(Authorization => "Passport1.4 OrgVerb=GET,OrgURL=http%3A%2F%2Fmessenger%2Emsn%2Ecom,sign-in=$sign_in,pwd=$password,$_[HEAP]->{cookie}");
244     $_[KERNEL]->post(ua => request => got_2nd_response => $request);
245 }
246
247 sub got_2nd_response {
248     my($request_packet, $response_packet) = @_[ARG0, ARG1];
249     my $response = $response_packet->[0];
250 #    my $auth_info = $response->header('Authentication-Info');
251     my $auth_info = _fake_header($response, 'Authentication-Info');
252     if ($auth_info =~ /da-status=redir/) {
253         my $new_location = _fake_header($response, 'Location');
254         $_[KERNEL]->yield(passport_login => $new_location);
255     }
256     elsif ($auth_info =~ /PP='(.*?)'/) {
257         my $credential = $1;
258         $_[HEAP]->{sock}->put(
259             POE::Component::Client::MSN::Command->new(USR => "TWN S $credential" => $_[HEAP]),
260         );
261     }
262 }
263
264 sub _fake_header {
265     my($response, $key) = @_;
266     # seems to be a bug. it's in body
267     return $response->header($key) || ($response->content =~ /^$key: (.*)$/m)[0];
268 }
269
270 sub handle_common {
271     my $event = $_[ARG0]->name;
272     $_[KERNEL]->yield(notify => "got_$event", $_[ARG0]);
273 }
274
275 sub got_challenge {
276     my $challenge = $_[ARG0]->args->[0];
277     my $response = sprintf "%s %d\r\n%s",
278         'msmsgs@msnmsgr.com', 32, Digest::MD5::md5_hex($challenge . "Q1P7W2E4J9R8U3S5");
279     $_[HEAP]->{sock}->put(
280         POE::Component::Client::MSN::Command->new(QRY => $response => $_[HEAP], 1),
281     );
282 }
283
284 sub got_change_status {
285     if ($_[ARG0]->args->[0] eq 'NLN') {
286         # normal status
287         my $cl_version = $_[HEAP]->{CL_version} || 0;
288         $_[HEAP]->{sock}->put(
289             POE::Component::Client::MSN::Command->new(SYN => $cl_version => $_[HEAP]),
290         );
291     }
292 }
293
294 sub got_synchronization {
295     my($version, $lst_num, $lsg_num) = $_[ARG0]->args;
296     if (!$_[HEAP]->{CL_version} || $version > $_[HEAP]->{CL_version}) {
297         warn "synchronize CL version to $version";
298         $_[HEAP]->{CL_version} = $version;
299         $_[KERNEL]->yield(notify => 'got_synchronization' => $_[ARG0]);
300     }
301 }
302
303 sub got_list {
304     my($account, $screen_name, $listmask, $groups) = $_[ARG0]->args;
305     my @groups = split /,/, $groups;
306     $_[HEAP]->{buddies}->{$account} = {
307         screen_name => $screen_name,
308         listmask    => $listmask,
309         groups      => $groups,
310     };
311     $_[KERNEL]->yield(notify => 'got_list' => $_[ARG0]);
312 }
313
314 sub got_group {
315     my($group, $gid) = $_[ARG0]->args;
316     $_[HEAP]->{groups}->{$gid} = $group;
317     $_[KERNEL]->yield(notify => 'got_group' => $_[ARG0]);
318 }
319
320
321
322 =pod
323
324 sub got_list {
325     my($kernel, $heap, $command) = @_[KERNEL, HEAP, ARG0];
326     my($type, $buddy, $nickname) = ($command->args)[0,4,5];
327     if ($type eq 'FL') {        # Forward List
328         $heap->{buddies}->{$buddy} = $nickname;
329     }
330     $kernel->yield(notify => buddy_list => $command);
331 }
332
333 sub got_ring {
334     my($kernel, $heap, $session, $command) = @_[KERNEL, HEAP, SESSION, ARG0];
335     POE::Session->create(
336         inline_states => {
337             _start => \&sb_start,
338             _sb_sock_up => \&sb_sock_up,
339             _sb_sock_down => \&sb_sock_down,
340             handle_event => \&sb_handle_event,
341             MSG => \&sb_got_message,
342             send_message => \&sb_send_message,
343         },
344         args => [ $command, $session->ID, $heap ],
345     );
346 }
347
348 sub sb_start {
349     my($kernel, $heap, $command, $parent, $old_heap) = @_[KERNEL, HEAP, ARG0, ARG1, ARG2];
350     $heap->{parent} = $parent;
351     $heap->{session} = $command->transaction;
352     $heap->{transaction} = $old_heap->{transaction} + 1;
353     $heap->{username} = $old_heap->{username};
354     @{$heap}{qw(hostname port)} = split /:/, $command->args->[0];
355     $heap->{key} = $command->args->[2];
356     $heap->{buddy} = $command->args->[3];
357     $heap->{sock} = POE::Wheel::SocketFactory->new(
358         SocketDomain => AF_INET,
359         SocketType => SOCK_STREAM,
360         SocketProtocol => 'tcp',
361         RemoteAddress => $heap->{hostname},
362         RemotePort => $heap->{port},
363         SuccessEvent => '_sb_sock_up',
364         FailureEvent => '_sb_sock_failed',
365     );
366 }
367
368 sub sb_sock_up {
369     my($kernel, $heap, $socket) = @_[KERNEL, HEAP, ARG0];
370     $heap->{sock} = POE::Wheel::ReadWrite->new(
371         Handle => $socket,
372         Driver => POE::Driver::SysRW->new,
373         Filter => POE::Filter::MSN->new,
374         ErrorEvent => '_sb_sock_down',
375     );
376     $heap->{sock}->event(InputEvent => 'handle_event');
377     $heap->{sock}->put(
378         POE::Component::Client::MSN::Command->new(
379             ANS => "$heap->{username} $heap->{key} $heap->{session}" => $heap,
380         ),
381     );
382 }
383
384 sub sb_sock_down {
385     delete $_[HEAP]->{sock};
386 }
387
388 sub sb_handle_event {
389     my($kernel, $heap, $command) = @_[KERNEL, HEAP, ARG0];
390     $kernel->yield($command->name, $command);
391 }
392
393 sub sb_got_message {
394     my($kernel, $heap, $command) = @_[KERNEL, HEAP, ARG0];
395     $kernel->post($heap->{parent} => notify => got_message => $command);
396 }
397
398 sub sb_send_message {
399     my($kernel, $heap, $args) = @_[KERNEL, HEAP, ARG0];
400    
401 }
402 =cut
403
404 1;
405 __END__
406
407 =head1 NAME
408
409 POE::Component::Client::MSN - POE Component for MSN Messenger
410
411 =head1 SYNOPSIS
412
413   use POE qw(Component::Client::MSN);
414
415   # spawn MSN session
416   POE::Component::Client::MSN->spawn(Alias => 'msn');
417
418   # register your session as MSN observer
419   $kernel->post(msn => 'register');
420   # tell MSN how to connect servers
421   $kernel->post(msn => connect => {
422       username => 'yourname',
423       password => 'xxxxxxxx',
424   });
425
426   sub msn_goes_online {
427       my $event = $_[ARG0];
428       print $event->username, " goes online.\n";
429   }
430
431   $poe_kernel->run;
432
433 =head1 DESCRIPTION
434
435 POE::Component::Client::MSN is a POE component to connect MSN Messenger server.
436
437 =head1 AUTHOR
438
439 Tatsuhiko Miyagawa E<lt>miyagawa@bulknews.netE<gt>
440
441 This library is free software; you can redistribute it and/or modify
442 it under the same terms as Perl itself.
443
444 =head1 SEE ALSO
445
446 L<POE>, L<POE::Component::YahooMessenger>
447
448 http://www.hypothetic.org/docs/msn/research/msnp9.php
449
450 http://www.chat.solidhouse.com/
451
452 =cut
Note: See TracBrowser for help on using the browser.