File Coverage

blib/lib/POE/Component/Client/MSN.pm
Criterion Covered Total %
statement 15 15 100.0
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 20 20 100.0


line stmt bran cond sub pod time code
1             package POE::Component::Client::MSN;
2              
3             # vim:set ts=4
4              
5 1     1   38758 use strict;
  1         3  
  1         44  
6 1     1   6 use vars qw($VERSION);
  1         1  
  1         68  
7             $VERSION = '0.03';
8              
9 1     1   5 use vars qw($Default);
  1         6  
  1         51  
10             $Default = {
11             port => 1863,
12             hostname => 'messenger.hotmail.com',
13             };
14 1     1   2996 use Time::HiRes;
  1         2707  
  1         6  
15 1         10 use POE qw(Wheel::SocketFactory Wheel::ReadWrite Driver::SysRW Filter::Line Filter::Stream
16 1     1   5376 Filter::MSN Component::Client::HTTP Component::Server::TCP);
  1         101443  
17             use Symbol qw(gensym);
18              
19             use POE::Component::Client::MSN::Command;
20             use HTTP::Request;
21             use Digest::MD5;
22             use Socket;
23             use URI::Escape ();
24              
25             sub spawn {
26             my($class, %args) = @_;
27             $args{Alias} ||= 'msn';
28              
29             # session for myself
30             POE::Session->create(
31             inline_states => {
32             _start => \&_start,
33             _stop => \&_stop,
34            
35             # internals
36             _sock_up => \&_sock_up,
37             _sock_down => \&_sock_down,
38             _sb_sock_up => \&_sb_sock_up,
39             _unregister => \&_unregister,
40            
41             # API
42             notify => \¬ify,
43             register => \®ister,
44             unregister => \&unregister,
45             connect => \&connect,
46             login => \&login,
47             put => \&put,
48            
49             filter_debug => sub {
50             my $arg = $_[ARG0];
51             $POE::Filter::MSN::Debug = $arg;
52             },
53              
54             handle_event => \&handle_event,
55            
56             # commands
57             VER => \&got_version,
58             CVR => \&got_client_version,
59             CHG => \&got_change_status,
60             XFR => \&got_xfer,
61             USR => \&got_user,
62             # ILN => \&got_goes_online,
63             # # user change status to online
64             NLN => \&handle_common,
65             FLN => \&handle_common,
66             # start chat session
67             RNG => \&got_ring,
68             # got a message
69             MSG => \&handle_common,
70             # challenge
71             CHL => \&got_challenge,
72             QRY => \&handle_common,
73             # sync
74             SYN => \&got_synchronization,
75             # group list
76             LSG => \&got_group,
77             # buddy list
78             LST => \&got_list,
79             # buddy added you to thier list
80             ADD => \&handle_common,
81             # buddy removed you from thier list
82             REM => \&handle_common,
83             # signed in at other location, or going down for maintenance
84             # UOT => \&got_kicked,
85             # OUT => \&got_kicked,
86            
87             # states
88             got_1st_response => \&got_1st_response,
89             passport_login => \&passport_login,
90             got_2nd_response => \&got_2nd_response,
91             accept_call => \&accept_call,
92             talk_user => \&talk_user,
93             },
94             args => [ \%args ],
95             );
96              
97             # HTTP cliens session
98             POE::Component::Client::HTTP->spawn(Protocol => 'HTTP/1.1', Agent => 'MSN Session', Alias => 'ua');
99             }
100              
101             sub _start {
102             $_[KERNEL]->alias_set($_[ARG0]->{Alias});
103             $_[HEAP]->{transaction} = 0;
104             }
105              
106             sub _stop { }
107              
108             sub register {
109             my($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
110             $kernel->refcount_increment($sender->ID, __PACKAGE__);
111             $heap->{listeners}->{$sender->ID} = 1;
112             $kernel->post($sender->ID => "registered" => $_[SESSION]->ID);
113             }
114              
115              
116             sub unregister {
117             my($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
118             $kernel->yield(_unregister => $sender->ID);
119             }
120              
121             sub _unregister {
122             my($kernel, $heap, $session) = @_[KERNEL, HEAP, ARG0];
123             $kernel->refcount_decrement($session, __PACKAGE__);
124             delete $heap->{listeners}->{$session};
125             }
126              
127             sub notify {
128             my($kernel, $heap, $name, $data) = @_[KERNEL, HEAP, ARG0, ARG1];
129             # $data ||= POE::Component::Client::MSN::Event::Null->new;
130             # $kernel->post($_ => "msn_$name" => $data->args) for keys %{$heap->{listeners}};
131             $kernel->post($_ => "msn_$name" => $data) for keys %{$heap->{listeners}};
132             }
133              
134             sub connect {
135             my($kernel, $heap, $args) = @_[KERNEL, HEAP, ARG0];
136             # set up parameters
137             $heap->{$_} = $args->{$_} for qw(username password);
138             $heap->{$_} = $args->{$_} || $Default->{$_} for qw(hostname port);
139              
140             return if $heap->{sock};
141             $heap->{sock} = POE::Wheel::SocketFactory->new(
142             SocketDomain => AF_INET,
143             SocketType => SOCK_STREAM,
144             SocketProtocol => 'tcp',
145             RemoteAddress => $heap->{hostname},
146             RemotePort => $heap->{port},
147             SuccessEvent => '_sock_up',
148             FailureEvent => '_sock_failed',
149             );
150             }
151              
152             sub _sock_up {
153             my($kernel, $heap, $socket) = @_[KERNEL, HEAP, ARG0];
154             # new ReadWrite wheel for the socket
155             $heap->{sock} = POE::Wheel::ReadWrite->new(
156             Handle => $socket,
157             Driver => POE::Driver::SysRW->new,
158             Filter => POE::Filter::MSN->new,
159             ErrorEvent => '_sock_down',
160             );
161             $heap->{sock}->event(InputEvent => 'handle_event');
162             $heap->{sock}->put(
163             POE::Component::Client::MSN::Command->new(VER => "MSNP9 CVR0" => $heap),
164             );
165             $kernel->yield(notify => 'connected');
166             }
167              
168             sub _sock_failed {
169             my($kernel, $heap) = @_[KERNEL, HEAP];
170             $kernel->yield(notify => socket_error => ());
171             for my $session (keys %{$heap->{listeners}}) {
172             $kernel->yield(_unregister => $session);
173             }
174             }
175              
176             sub _sock_down {
177             my($kernel, $heap) = @_[KERNEL, HEAP];
178             warn "sock is down\n";
179             delete $heap->{sock};
180             $kernel->yield(notify => 'disconnected');
181             }
182              
183             sub handle_event {
184             my($kernel, $heap, $command) = @_[KERNEL, HEAP, ARG0];
185             if ($command->errcode) {
186             warn "got error: ", $command->errcode;
187             $kernel->yield(notify => got_error => $command);
188             } else {
189             $kernel->yield($command->name, $command);
190             }
191             }
192              
193             sub handle_common {
194             my $event = $_[ARG0]->name;
195             $_[KERNEL]->yield(notify => "got_$event" => $_[ARG0]);
196             }
197              
198              
199             sub got_version {
200             $_[HEAP]->{sock}->put(
201             POE::Component::Client::MSN::Command->new(CVR => "0x0409 winnt 5.1 i386 MSNMSGR 6.0.0602 MSMSGS $_[HEAP]->{username}" => $_[HEAP]),
202             );
203             }
204              
205             sub got_client_version {
206             $_[HEAP]->{sock}->put(
207             POE::Component::Client::MSN::Command->new(USR => "TWN I $_[HEAP]->{username}" => $_[HEAP]),
208             );
209             }
210              
211             sub got_xfer {
212             my($kernel, $heap, $session, $command) = @_[KERNEL, HEAP, SESSION, ARG0];
213             if ($command->args->[0] eq 'NS') {
214             @{$heap}{qw(hostname port)} = split /:/, $command->args->[1];
215             # switch to Notification Server
216             $_[HEAP]->{sock} = POE::Wheel::SocketFactory->new(
217             SocketDomain => AF_INET,
218             SocketType => SOCK_STREAM,
219             SocketProtocol => 'tcp',
220             RemoteAddress => $heap->{hostname},
221             RemotePort => $heap->{port},
222             SuccessEvent => '_sock_up',
223             FailureEvent => '_sock_failed',
224             );
225             } elsif ($command->args->[0] eq 'SB') {
226             POE::Session->create(
227             inline_states => {
228             _start => \&sb_start_xfr,
229             _sb_sock_up => \&sb_sock_up_xfr,
230             _sb_sock_down => \&sb_sock_down,
231             _default => \&sb_chat_debug,
232             handle_event => \&sb_handle_event,
233             MSG => \&sb_got_message,
234             USR => \&sb_got_user,
235             send_message => \&sb_send_message,
236             accept_file => \&sb_accept_file,
237             send_file => \&sb_send_file,
238             accept_send => \&sb_accept_send,
239             typing_user => \&sb_send_typing_user,
240             send_reject_invite => \&sb_send_reject_invite,
241             send_cancel_invite => \&sb_send_cancel_invite,
242             invite_user => \&sb_send_invite_user,
243             disconnect => sub {
244             my($kernel, $heap, $session, $command) = @_[KERNEL, HEAP, SESSION, ARG0];
245             my $cmd = POE::Component::Client::MSN::Command->new(OUT => "");
246             $cmd->{name_only} = 1;
247             $heap->{sock}->put($cmd);
248             $kernel->yield("BYE", $cmd);
249             },
250             put => \&put,
251             _stop => \&sb_sock_down,
252             BYE => sub {
253             my($kernel, $heap, $session, $command) = @_[KERNEL, HEAP, SESSION, ARG0];
254             $kernel->post($heap->{parent} => notify => chat_bye => { command => $command, session_id => $session->ID } );
255             },
256             NAK => sub {
257             my($kernel, $heap, $session, $command) = @_[KERNEL, HEAP, SESSION, ARG0];
258             $kernel->post($heap->{parent} => notify => chat_nak => { command => $command, session_id => $session->ID } );
259             },
260             JOI => sub {
261             my($kernel, $heap, $session, $command) = @_[KERNEL, HEAP, SESSION, ARG0];
262             $kernel->post($heap->{parent} => notify => chat_join => { command => $command, session_id => $session->ID } );
263             },
264             IRO => sub {
265             my($kernel, $heap, $session, $command) = @_[KERNEL, HEAP, SESSION, ARG0];
266             $kernel->post($heap->{parent} => notify => chat_start => { command => $command, session_id => $session->ID } );
267             },
268             _default => sub {
269             my $arg = $_[ARG0];
270             # my $name = $arg->name;
271             # if ($arg->errname) {
272             # $name .= " ".$arg->errname;
273             # }
274             print STDERR "XCHAT\:\:$arg\n";
275             return undef;
276             },
277             },
278             args => [ $command, $session->ID, $heap ],
279             );
280             }
281             }
282              
283             sub got_user {
284             my $event = $_[ARG0];
285             if ($event->args->[1] eq 'S') {
286             $_[HEAP]->{cookie} = $event->args->[2];
287             my $request = HTTP::Request->new(GET => 'https://nexus.passport.com/rdr/pprdr.asp');
288             $_[KERNEL]->post(ua => request => got_1st_response => $request);
289             print STDERR "getting passport\n";
290             } elsif ($event->args->[0] eq 'OK') {
291             $_[KERNEL]->yield(notify => signin => $event);
292             # set initial status
293             $_[HEAP]->{sock}->put(
294             POE::Component::Client::MSN::Command->new(CHG => "NLN" => $_[HEAP]),
295             );
296             }
297             }
298              
299             sub got_1st_response {
300             my($request_packet, $response_packet) = @_[ARG0, ARG1];
301             my $response = $response_packet->[0];
302             # require Data::Dumper;
303             # print Data::Dumper->Dump([$response]);
304             # print STDERR "got passport 1st response\n";
305             unless ($response->header('PassportURLs')) {
306             print STDERR "failed to find correct header, your POE::Component::Client::HTTP is broken\n";
307             return;
308             }
309             my $passport_url = (_fake_header($response, 'PassportURLs') =~ /DALogin=(.*?),/)[0]
310             or warn $response->as_string;
311             if ($passport_url) {
312             print STDERR "getting https://$passport_url\n";
313             $_[KERNEL]->yield(passport_login => "https://$passport_url");
314             }
315             }
316              
317             sub passport_login {
318             my $passport_url = $_[ARG0];
319             my $request = HTTP::Request->new(GET => $passport_url);
320             my $sign_in = URI::Escape::uri_escape($_[HEAP]->{username});
321             my $password = URI::Escape::uri_escape($_[HEAP]->{password});
322             $request->header(Authorization => "Passport1.4 OrgVerb=GET,OrgURL=http%3A%2F%2Fmessenger%2Emsn%2Ecom,sign-in=$sign_in,pwd=$password,$_[HEAP]->{cookie}");
323             $_[KERNEL]->post(ua => request => got_2nd_response => $request);
324             }
325              
326             sub got_2nd_response {
327             my($request_packet, $response_packet) = @_[ARG0, ARG1];
328             my $response = $response_packet->[0];
329             # my $auth_info = $response->header('Authentication-Info');
330             my $auth_info = _fake_header($response, 'Authentication-Info');
331             print STDERR "got 2nd response: $auth_info $response->{_content} \n";
332             if ($auth_info =~ /da-status=redir/) {
333             my $new_location = _fake_header($response, 'Location');
334             print STDERR "redirecting to $new_location\n";
335             $_[KERNEL]->yield(passport_login => $new_location);
336             } elsif ($auth_info =~ /PP='(.*?)'/) {
337             my $credential = $1;
338             $_[HEAP]->{sock}->put(
339             POE::Component::Client::MSN::Command->new(USR => "TWN S $credential" => $_[HEAP]),
340             );
341             }
342             }
343              
344             sub _fake_header {
345             my($response, $key) = @_;
346             # seems to be a bug. it's in body
347             return $response->header($key) || ($response->content =~ /^$key: (.*)$/m)[0];
348             }
349              
350             sub got_challenge {
351             my $challenge = $_[ARG0]->args->[0];
352             my $response = sprintf "%s %d\r\n%s",'msmsgs@msnmsgr.com', 32, Digest::MD5::md5_hex($challenge . "Q1P7W2E4J9R8U3S5");
353             $_[HEAP]->{sock}->put(
354             POE::Component::Client::MSN::Command->new(QRY => $response => $_[HEAP], 1),
355             );
356             }
357              
358             sub got_change_status {
359             if ($_[ARG0]->args->[0] eq 'NLN') {
360             # normal status
361             my $cl_version = $_[HEAP]->{CL_version} || 0;
362             $_[HEAP]->{sock}->put(
363             POE::Component::Client::MSN::Command->new(SYN => $cl_version => $_[HEAP]),
364             );
365             }
366             }
367              
368             sub got_synchronization {
369             my($version, $lst_num, $lsg_num) = $_[ARG0]->args;
370             if (!$_[HEAP]->{CL_version} || $version > $_[HEAP]->{CL_version}) {
371             # warn "synchronize CL version to $version";
372             $_[HEAP]->{CL_version} = $version;
373             $_[KERNEL]->yield(notify => 'got_synchronization' => $_[ARG0]);
374             }
375             }
376              
377             sub got_list {
378             my($account, $screen_name, $listmask, $groups) = $_[ARG0]->args;
379             my @groups = split /,/, $groups;
380             $_[HEAP]->{buddies}->{$account} = {
381             screen_name => $screen_name,
382             listmask => $listmask,
383             groups => @groups,
384             };
385             $_[KERNEL]->yield(notify => 'got_list' => $_[ARG0]);
386             }
387              
388             sub got_group {
389             my($group, $gid) = $_[ARG0]->args;
390             $_[HEAP]->{groups}->{$gid} = $group;
391             $_[KERNEL]->yield(notify => 'got_group' => $_[ARG0]);
392             }
393              
394             sub put {
395             $_[HEAP]->{sock}->put(
396             POE::Component::Client::MSN::Command->new(@_[ARG0, ARG1], $_[HEAP]),
397             );
398             }
399              
400             sub got_ring {
401             my($kernel, $heap, $session, $command) = @_[KERNEL, HEAP, SESSION, ARG0];
402              
403             $kernel->yield(notify => chat_ring => $command );
404             }
405              
406             sub talk_user {
407             my($kernel, $heap, $session, $email) = @_[KERNEL, HEAP, SESSION, ARG0];
408            
409             $heap->{buddy_email} = $email;
410              
411             # need a postback or something here
412             $heap->{sock}->put(
413             POE::Component::Client::MSN::Command->new("XFR" => "SB", $heap),
414             );
415             }
416              
417             sub accept_call {
418             my($kernel, $heap, $session, $command) = @_[KERNEL, HEAP, SESSION, ARG0];
419              
420             POE::Session->create(
421             inline_states => {
422             _start => \&sb_start,
423             _sb_sock_up => \&sb_sock_up,
424             _sb_sock_down => \&sb_sock_down,
425             _default => \&sb_chat_debug,
426             handle_event => \&sb_handle_event,
427             MSG => \&sb_got_message,
428             send_message => \&sb_send_message,
429             accept_file => \&sb_accept_file,
430             send_file => \&sb_send_file,
431             accept_send => \&sb_accept_send,
432             typing_user => \&sb_send_typing_user,
433             send_reject_invite => \&sb_send_reject_invite,
434             send_cancel_invite => \&sb_send_cancel_invite,
435             invite_user => \&sb_send_invite_user,
436             disconnect => sub {
437             my($kernel, $heap, $session, $command) = @_[KERNEL, HEAP, SESSION, ARG0];
438             my $cmd = POE::Component::Client::MSN::Command->new(OUT => "");
439             $cmd->{name_only} = 1;
440             $heap->{sock}->put($cmd);
441             },
442             put => \&put,
443             _stop => \&sb_sock_down,
444             BYE => sub {
445             my($kernel, $heap, $session, $command) = @_[KERNEL, HEAP, SESSION, ARG0];
446             # someone left conversation
447             $kernel->post($heap->{parent} => notify => chat_bye => { command => $command, session_id => $_[SESSION]->ID } );
448             },
449             NAK => sub {
450             my($kernel, $heap, $session, $command) = @_[KERNEL, HEAP, SESSION, ARG0];
451             $kernel->post($heap->{parent} => notify => chat_nak => { command => $command, session_id => $_[SESSION]->ID } );
452             },
453             JOI => sub {
454             my($kernel, $heap, $session, $command) = @_[KERNEL, HEAP, SESSION, ARG0];
455             $kernel->post($heap->{parent} => notify => chat_join => { command => $command, session_id => $_[SESSION]->ID } );
456             },
457             IRO => sub {
458             my($kernel, $heap, $session, $command) = @_[KERNEL, HEAP, SESSION, ARG0];
459             $kernel->post($heap->{parent} => notify => chat_start => { command => $command, session_id => $_[SESSION]->ID } );
460             },
461             _default => sub {
462             my $arg = $_[ARG0];
463             print STDERR "CHAT\:\:$arg\n";
464             return undef;
465             },
466             },
467             args => [ $command, $session->ID, $heap ],
468             );
469             }
470              
471             sub sb_start {
472             my($kernel, $heap, $command, $parent, $old_heap) = @_[KERNEL, HEAP, ARG0, ARG1, ARG2];
473             $heap->{parent} = $parent;
474             $heap->{session} = $command->transaction;
475             $heap->{transaction} = $old_heap->{transaction} + 1;
476             $heap->{username} = $old_heap->{username};
477             @{$heap}{qw(hostname port)} = split /:/, $command->args->[0];
478             $heap->{key} = $command->args->[2];
479             $heap->{buddy_email} = $command->args->[3];
480             $heap->{old_heap} = $old_heap; # might need this
481             $heap->{sock} = POE::Wheel::SocketFactory->new(
482             SocketDomain => AF_INET,
483             SocketType => SOCK_STREAM,
484             SocketProtocol => 'tcp',
485             RemoteAddress => $heap->{hostname},
486             RemotePort => $heap->{port},
487             SuccessEvent => '_sb_sock_up',
488             FailureEvent => '_sb_sock_failed',
489             );
490             }
491              
492             sub sb_start_xfr {
493             my($kernel, $heap, $command, $parent, $old_heap) = @_[KERNEL, HEAP, ARG0, ARG1, ARG2];
494             eval {
495             $heap->{parent} = $parent;
496             $heap->{session} = $command->transaction;
497             $heap->{transaction} = $old_heap->{transaction} + 1;
498             $heap->{username} = $old_heap->{username};
499             $heap->{buddy_email} = delete $old_heap->{buddy_email};
500             @{$heap}{qw(hostname port)} = split /:/, $command->args->[1];
501             $heap->{key} = $command->args->[3];
502             $heap->{sock} = POE::Wheel::SocketFactory->new(
503             SocketDomain => AF_INET,
504             SocketType => SOCK_STREAM,
505             SocketProtocol => 'tcp',
506             RemoteAddress => $heap->{hostname},
507             RemotePort => $heap->{port},
508             SuccessEvent => '_sb_sock_up',
509             FailureEvent => '_sb_sock_failed',
510             );
511             };
512             if ($@) {
513             print STDERR "$@\n";
514             }
515             }
516              
517             sub sb_sock_up {
518             my($kernel, $heap, $socket) = @_[KERNEL, HEAP, ARG0];
519             $heap->{sock} = POE::Wheel::ReadWrite->new(
520             Handle => $socket,
521             Driver => POE::Driver::SysRW->new,
522             Filter => POE::Filter::MSN->new,
523             ErrorEvent => '_sb_sock_down',
524             InputEvent => 'handle_event',
525             );
526             $heap->{sock}->put(
527             POE::Component::Client::MSN::Command->new(
528             ANS => "$heap->{username} $heap->{key} $heap->{session}" => $heap,
529             ),
530             );
531             $kernel->post($heap->{parent} => notify => chat_socket_opened => { buddy_email => $heap->{buddy_email}, buddy_nick => $heap->{buddy_nick}, session_id => $_[SESSION]->ID } );
532             }
533              
534             sub sb_sock_up_xfr {
535             my($kernel, $heap, $socket) = @_[KERNEL, HEAP, ARG0];
536             $heap->{sock} = POE::Wheel::ReadWrite->new(
537             Handle => $socket,
538             Driver => POE::Driver::SysRW->new,
539             Filter => POE::Filter::MSN->new,
540             ErrorEvent => '_sb_sock_down',
541             InputEvent => 'handle_event',
542             );
543             $heap->{sock}->put(
544             POE::Component::Client::MSN::Command->new(
545             USR => "$heap->{username} $heap->{key}" => $heap,
546             ),
547             );
548             # we don't have nick yet, so use thier email as buddy_nick
549             $kernel->post($heap->{parent} => notify => chat_socket_opened => { buddy_email => $heap->{buddy_email}, buddy_nick => $heap->{buddy_nick}, session_id => $_[SESSION]->ID } );
550             }
551              
552             sub sb_sock_down {
553             my($kernel, $heap, $session) = @_[KERNEL, HEAP, SESSION];
554             delete $heap->{sock};
555             # if (exists($heap->{parent})) {
556             eval {
557             $kernel->post($heap->{parent} => notify => chat_socket_closed => { session_id => $session->ID } );
558             };
559             print STDERR "$@\n" if ($@);
560             # }
561             }
562              
563             sub sb_handle_event {
564             my($kernel, $heap, $command) = @_[KERNEL, HEAP, ARG0];
565             $kernel->yield($command->name, $command);
566             }
567              
568             sub sb_send_invite_user {
569             my($kernel, $heap, $user) = @_[KERNEL, HEAP, ARG0];
570              
571             return unless ($user);
572            
573             $heap->{sock}->put(
574             POE::Component::Client::MSN::Command->new(
575             CAL => "$user" => $heap,
576             ),
577             );
578             }
579              
580             sub sb_got_user {
581             my($kernel, $heap, $command) = @_[KERNEL, HEAP, ARG0];
582            
583             #OK xantus@teknikill.net Telstron(6)
584             $kernel->post($heap->{parent} => notify => out_chat_opened =>
585             { command => $command, session_id => $_[SESSION]->ID });
586              
587             $kernel->yield(invite_user => $heap->{buddy_email});
588             }
589              
590             sub sb_chat_debug {
591             my($kernel, $heap, $command) = @_[KERNEL, HEAP, ARG0];
592              
593             $kernel->post($heap->{parent} => notify => chat_debug => { command => $command, session_id => $_[SESSION]->ID });
594             }
595              
596             sub sb_got_message {
597             my($kernel, $heap, $session, $command) = @_[KERNEL, HEAP, SESSION, ARG0];
598            
599             if ($command->{'message'} && $command->{'message'}->{'mail_inet_head'}) {
600             my $ct;
601            
602             eval { $ct = ${$command->{'message'}->{'mail_inet_head'}->{'mail_hdr_hash'}->{'Content-Type'}->[0]}; };
603            
604             if ($@) {
605             print STDERR "$@";
606             return;
607             }
608            
609             if ($ct =~ m#^Content-Type: text/x-clientcaps#) {
610             # Gaim and others send this
611             $kernel->post($heap->{parent} => notify => got_clientcaps => { command => $command, session_id => $session->ID });
612             } elsif ($ct =~ m#^Content-Type: application/x-msnmsgrp2p#) {
613             # p2p stuff
614             # TODO
615             require Data::Dumper;
616             print STDERR Data::Dumper->Dump([$command]);
617             } elsif ($ct =~ m#^Content-Type: text/x-mms-emoticon#) {
618             # emote icon
619             # TODO
620             print STDERR "received emoticon, nothing done\n";
621             } elsif ($ct =~ m#^Content-Type: text/plain#) {
622             # regular text message
623             $kernel->post($heap->{parent} => notify => got_message => { command => $command, session_id => $session->ID });
624             } elsif ($ct =~ m#^Content-Type: text/x-msmsgscontrol#) {
625             # typing
626             eval {
627             my $typing = ${$command->{'message'}->{'mail_inet_head'}->{'mail_hdr_hash'}->{'Typinguser'}->[0]};
628             $typing =~ s/^Typinguser: //i;
629             $kernel->post($heap->{parent} => notify => typing_user => { command => $command, typing_user => $typing, session_id => $session->ID });
630             };
631             if ($@) { print "$@\n"; }
632             } elsif ($ct =~ m#^Content-Type: text/x-msmsgsinvite#) {
633             # Invitations
634              
635             # we have header type fields in the body, parse those
636             my %fields;
637             foreach (@{$command->{'message'}->{'mail_inet_body'}}) {
638             my ($k,$v) = split(/:\s/);
639             $fields{$k} = $v;
640             }
641              
642             # TODO better handling of CANCEL's Invitation-Command: CANCEL and Cancel-Code: FTTIMEOUT ect, ect
643             if ($heap->{inv_cookie} && $fields{'Invitation-Cookie'} && ($heap->{inv_cookie} eq $fields{'Invitation-Cookie'})) {
644             # valid invitation!
645             # is it a cancel?
646             if ($fields{'Invitation-Command'} eq 'CANCEL') {
647             $kernel->post($heap->{parent} => notify => file_cancel =>
648             { command => $command, session_id => $session->ID, fields => { %fields } });
649             } elsif ($fields{'Invitation-Command'} eq 'ACCEPT') {
650             # elimitated a whole step here
651             # $kernel->post($heap->{parent} => notify => file_send =>
652             # { command => $command, session_id => $session->ID, fields => { %fields } });
653             $kernel->yield(accept_send => { command => $command, session_id => $session->ID, fields => { %fields } });
654             } else {
655             print STDERR "Unknown invite: ".$fields{'Invitation-Command'}."\n";
656             }
657             return;
658             }
659            
660             # file transfer? notify listeners about it or deny it
661             if ($fields{'Application-GUID'} =~ m#5D3E02AB-6190-11d3-BBBB-00C04F795683#) {
662             # this was an interesting delve into the kernel and session modules to find this
663             # We look at our listening sessions and check if they have the msn_file_request state registered
664             # meaning if they do, then pass
665             eval {
666             my $found = 0;
667             foreach my $sid (keys %{$heap->{old_heap}->{listeners}}) {
668             my $sess = $kernel->ID_id_to_session($sid);
669             if ($sess->[POE::Session::SE_STATES]->{msn_file_request}) {
670             $kernel->post($sid => msn_file_request =>
671             { command => $command, session_id => $session->ID, fields => { %fields } });
672             $found = 1;
673             }
674             }
675             unless ($found == 1) {
676             # no listening sessions have file handlers, so reject thier request
677             $kernel->yield("send_reject_invite" => $command);
678             }
679             };
680             if ($@) {
681             print STDERR "$@\n";
682             }
683             #$kernel->post($heap->{parent} => notify => file_request =>
684             # { command => $command, session_id => $session->ID, fields => { %fields } });
685             } else {
686             $kernel->yield("send_reject_invite" => $command);
687             }
688             return;
689             } else {
690             # unknown catch
691             require Data::Dumper;
692             print STDERR "Unknown message type: ".Data::Dumper->Dump([$command]);
693             }
694             } else {
695             print STDERR "Invalid message?\n";
696             }
697             }
698              
699             sub sb_send_reject_invite {
700             my($kernel, $heap, $command) = @_[KERNEL, HEAP, ARG0];
701            
702             my %fields;
703             foreach (@{$command->{'message'}->{'mail_inet_body'}}) {
704             my ($k,$v) = split(/:\s/);
705             $fields{$k} = $v;
706             }
707             my $msg = qq(MIME-Version: 1.0\r\nContent-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\nInvitation-Command: CANCEL\r\nInvitation-Cookie: $fields{'Invitation-Cookie'}\r\nCancel-Code: REJECT_NOT_INSTALLED\r\n\r\n);
708             my $cmd = POE::Component::Client::MSN::Command->new(
709             MSG => "N ".length($msg)."\r\n$msg" => $msg, 1
710             );
711             $cmd->{transaction}++;
712             $heap->{sock}->put($cmd);
713             }
714              
715             sub sb_send_cancel_invite {
716             my($kernel, $heap, $command) = @_[KERNEL, HEAP, ARG0];
717            
718             my %fields;
719             foreach (@{$command->{'message'}->{'mail_inet_body'}}) {
720             my ($k,$v) = split(/:\s/);
721             $fields{$k} = $v;
722             }
723             # TODO add FAIL, FTTIMEOUT, OUTBANDCANCEL, TIMEOUT
724             my $msg = qq(MIME-Version: 1.0\r\nContent-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\nInvitation-Command: CANCEL\r\nInvitation-Cookie: $fields{'Invitation-Cookie'}\r\nCancel-Code: REJECT\r\n\r\n);
725             my $cmd = POE::Component::Client::MSN::Command->new(
726             MSG => "N ".length($msg)."\r\n$msg" => $msg, 1
727             );
728             $cmd->{transaction}++;
729             $heap->{sock}->put($cmd);
730             }
731              
732             sub sb_send_message {
733             my($kernel, $heap, $args, $seconds) = @_[KERNEL, HEAP, ARG0, ARG1];
734             # MSG 4 N ###\r\n
735             # MIME-Version: 1.0\r\n
736             # Content-Type: text/plain; charset=UTF-8\r\n
737             # X-MMS-IM-Format: FN=Arial; EF=I; CO=0; CS=0; PF=22\r\n
738             # \r\n
739             # Hello! How are you?
740             #
741             # Try this too:
742             # if (alarm_is_outstanding()) { $buffer .= $new_message; } else { send($new_message); $kernel->delay(waiting => 2); $alarms++ }
743             # sub alarm_is_outstanding { return !!$alarms }
744             # sub alarm_handler { $alarms--; if (length $buffer) { send($buffer); $kernel->delay(waiting => 2); $alarms++ } }
745             # Oh, and $buffer = ""; after sending it from alarm_handler()
746             eval {
747             my $head = qq(MIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nX-MMS-IM-Format: FN=Verdana; EF=; CO=0; CS=0; PF=22\r\n\r\n);
748             if ($seconds) {
749             $kernel->delay_set('send_message' => $seconds => $args );
750             return;
751             } else {
752             # # there's GOT to be a better way to do this
753             # my @removed_alarms = $kernel->alarm_remove_all();
754             # foreach my $alarm (reverse @removed_alarms) {
755             # # print STDERR "-----\n";
756             # # print STDERR "Removed alarm event name: $alarm->[0]\n";
757             # # print STDERR "Removed alarm time : $alarm->[1]\n";
758             # #print STDERR "Removed alarm parameters: @{$alarm->[2]}\n";
759             # if ($alarm->[0] eq 'send_message' && length("$head$args\r\n$alarm->[2]") < 1664) {
760             # # print STDERR "Combining message $alarm->[2]\n";
761             # $args .= "\r\n$alarm->[2]";
762             # } else {
763             # # print STDERR "Putting alarm back: $alarm->[0]\n";
764             # if (ref($alarm->[2]) eq 'ARRAY') {
765             # $kernel->alarm_add($alarm->[0],$alarm->[1],@{$alarm->[2]});
766             # } else {
767             # $kernel->alarm_add($alarm->[0],$alarm->[1],$alarm->[2]);
768             # }
769             # }
770             # }
771              
772             # here's where i can check if there are any other messsages on the delay stack
773             # and combine the messages into 1 send
774             my $msg = "$head$args";
775             my $cmd = POE::Component::Client::MSN::Command->new(
776             MSG => "N ".length($msg)."\r\n$msg" => $msg, 1
777             );
778             $cmd->{transaction}++;
779             $heap->{sock}->put($cmd);
780             }
781             };
782             print STDERR "$@\n" if ($@);
783             }
784              
785             sub sb_send_typing_user {
786             my($kernel, $heap, $username, $seconds) = @_[KERNEL, HEAP, ARG0, ARG1];
787             $username = ($username) ? $username : $heap->{username};
788             # MSG 5 U ###\r\n
789             # MIME-Version: 1.0\r\n
790             # Content-Type: text/x-msmsgscontrol\r\n
791             # TypingUser: example@passport.com\r\n
792             # \r\n
793             # \r\n
794             if (($heap->{typing_time} && (time() - $heap->{typing_time} > 10)) || !exists($heap->{typing_time})) {
795             my $msg = qq(MIME-Version: 1.0\r\nContent-Type: text/x-msmsgscontrol\r\nTypingUser: $username\r\n\r\n);
796             my $cmd = POE::Component::Client::MSN::Command->new(
797             MSG => "U ".length($msg)."\r\n$msg" => $msg, 1
798             );
799             $cmd->{transaction}++;
800             $heap->{sock}->put($cmd);
801             $heap->{typing_time} = time();
802             }
803             }
804              
805             sub sb_send_file {
806             my($kernel, $heap, $session, $data) = @_[KERNEL, HEAP, SESSION, ARG0];
807            
808             # sequence:
809             # client -> send_file -> request sent to person
810             # person accepts -> accept_send
811             # sb_file_send_start -> connect to other -> sb_file_sock_up
812             # get VER
813             # send VER
814             # get USR
815             # send FIL
816             # get TFR
817             # msn_file_stream is called after every flush
818             # msn_file_stream puts the data directy to the socket handle of the connection
819             # the filter does the headers for each packet
820             # at eof then the client sends no stream data, and just a eof => 1 in the refhash
821             $heap->{inv_cookie} = int rand(2**32); # no zeros
822              
823             my $msg = qq(MIME-Version: 1.0\r\nContent-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\nApplication-Name: File Transfer\r\nApplication-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\nInvitation-Command: INVITE\r\nInvitation-Cookie: $heap->{inv_cookie}\r\nApplication-File: $data->{file_name}\r\nApplication-FileSize: $data->{file_size}\r\nConnectivity: N\r\n\r\n);
824             my $cmd = POE::Component::Client::MSN::Command->new(
825             MSG => "N ".length($msg)."\r\n$msg" => $msg, 1
826             );
827             $cmd->{transaction}++;
828             $heap->{sock}->put($cmd);
829             # TODO should we store this ourselves or allow the accept_send call to specify it?
830             $heap->{file_name} = $data->{file_name};
831             $heap->{file_size} = $data->{file_size};
832             }
833              
834             sub sb_accept_send {
835             my($kernel, $heap, $session, $data) = @_[KERNEL, HEAP, SESSION, ARG0];
836            
837             #####################
838             # FOR SENDING FILE
839             #####################
840              
841             POE::Session->create(
842             inline_states => {
843             _start => \&sb_file_send_start,
844             _sb_file_sock_up => \&sb_file_sock_up,
845             _sb_file_sock_down => \&sb_file_sock_down,
846             _sb_file_sock_flushed => sub {
847             my ($heap, $session, $command) = @_[HEAP, SESSION, ARG0];
848            
849             unless ($heap->{canceled}) {
850             $kernel->post($heap->{parent} => notify => file_stream => { command => $command, session_id => $session->ID,
851             for_session_id => $heap->{chat_parent}, sock => $heap->{sock} });
852             }
853             },
854             _default => sub {
855             my $arg = $_[ARG0];
856             print STDERR "MSNFTP-send\:\:$arg\n";
857             return undef;
858             },
859             handle_event => \&sb_file_handle_event,
860             VER => sub {
861             my $heap = $_[HEAP];
862            
863             # TODO 3rd party FTP protocol support, if any
864             $heap->{sock}->put(
865             POE::Component::Client::MSN::Command->new(VER => "MSNFTP"),
866             );
867             },
868             _VER_NOT_USED => sub {
869             my $heap = $_[HEAP];
870            
871             $heap->{sock}->put(
872             POE::Component::Client::MSN::Command->new(USR => "$heap->{username} $heap->{cookie}"),
873             );
874             },
875             USR => sub {
876             my $heap = $_[HEAP];
877            
878             $heap->{sock}->put(
879             POE::Component::Client::MSN::Command->new(FIL => "$heap->{file_size}"),
880             );
881             },
882             TFR => sub {
883             my ($heap, $kernel, $session, $command) = @_[HEAP, KERNEL, SESSION, ARG0];
884            
885             delete $heap->{canceled};
886             $heap->{sock}->event(FlushedEvent => '_sb_file_sock_flushed');
887             $kernel->yield("_sb_file_sock_flushed",splice(@_,ARG0));
888             #$kernel->post($heap->{parent} => notify => file_stream => { command => $command, session_id => $session->ID,
889             # for_session_id => $heap->{chat_parent}, sock => $heap->{sock} });
890             },
891             CCL => sub {
892             my ($heap, $session, $command) = @_[HEAP, SESSION, ARG0];
893            
894             $heap->{canceled} = 1;
895            
896             # canceled!
897             # TODO maybe this should be file_send_cancel
898             $kernel->post($heap->{parent} => notify => file_cancel =>
899             { command => $command, session_id => $session->ID, for_session_id => $heap->{chat_parent}, sock => $heap->{sock} });
900            
901             },
902             BYE => sub {
903             my ($heap, $session, $command) = @_[HEAP, SESSION, ARG0];
904            
905             my %codes = (
906             2147942405 => 'Failure: receiver is out of disk space',
907             2164261682 => 'Failure: receiver canceled the transfer',
908             2164261683 => 'Failure: sender has canceled the transfer',
909             2164261694 => 'Failure: connection is blocked',
910             16777987 => 'Success',
911             16777989 => 'Success',
912             );
913            
914             print STDERR "File transfer: ".$codes{$command->args->[0]}."\n";
915            
916             $kernel->yield("_sb_file_sock_down");
917             },
918             send_bye => sub {
919             my $heap = $_[HEAP];
920            
921             my $cmd = POE::Component::Client::MSN::Command->new(BYE => "");
922             $cmd->{name_only} = 1;
923             $heap->{sock}->put($cmd);
924             },
925             },
926             args => [ $data, $session->ID, $heap ],
927             );
928             }
929              
930             sub sb_file_send_start {
931             my($kernel, $heap, $data, $parent, $old_heap) = @_[KERNEL, HEAP, ARG0, ARG1, ARG2];
932            
933             eval {
934              
935             # we get this back after acceptance
936              
937             #'IP-Address: 216.254.16.46',
938             #'IP-Address-Internal: 10.0.2.3',
939             #'Port: 6892',
940             #'PortX: 11181',
941             #'AuthCookie: 1251249521',
942             #'Sender-Connect: TRUE',
943             #'Invitation-Command: ACCEPT',
944             #'Invitation-Cookie: 25',
945             #'Launch-Application: FALSE',
946             #'Request-Data: IP-Address:'
947             my $command = $data->{command};
948             $heap->{username} = $old_heap->{username};
949             # we want the parents parent instead
950             $heap->{parent} = $old_heap->{parent};
951             $heap->{file_name} = delete $old_heap->{file_name};
952             $heap->{file_size} = delete $old_heap->{file_size};
953             $heap->{chat_parent} = $parent;
954             # $heap->{file_name} = $data->{fields}->{'Application-File'};
955             $heap->{session} = $command->transaction;
956             $heap->{transaction} = $old_heap->{transaction} + 1;
957             my %fields;
958             foreach (@{$command->{'message'}->{'mail_inet_body'}}) {
959             my ($k,$v) = split(/:\s/);
960             $fields{$k} = $v;
961             }
962             $heap->{cookie} = $fields{'AuthCookie'};
963              
964             # connect to both ips and ports at the same time
965             # first one wins
966             $heap->{sock} = POE::Wheel::SocketFactory->new(
967             SocketDomain => AF_INET,
968             SocketType => SOCK_STREAM,
969             SocketProtocol => 'tcp',
970             RemoteAddress => $fields{'IP-Address'},
971             RemotePort => $fields{'Port'},
972             SuccessEvent => '_sb_file_sock_up',
973             FailureEvent => '_sb_file_sock_down',
974             );
975            
976             $heap->{sock2} = POE::Wheel::SocketFactory->new(
977             SocketDomain => AF_INET,
978             SocketType => SOCK_STREAM,
979             SocketProtocol => 'tcp',
980             RemoteAddress => $fields{'IP-Address-Internal'},
981             RemotePort => $fields{'Port'},
982             SuccessEvent => '_sb_file_sock_up',
983             FailureEvent => '_sb_file_sock_down2',
984             );
985            
986             # setup the listening socket
987             # $heap->{sock} = POE::Wheel::SocketFactory->new(
988             # BindAddress => INADDR_ANY, # Sets the bind() address
989             # BindPort => 6891, # Sets the bind() port
990             # SuccessEvent => '_sb_file_got_connection', # Event to emit upon accept()
991             # FailureEvent => '_sb_file_sock_down', # Event to emit upon error
992             # SocketDomain => AF_INET, # Sets the socket() domain
993             # SocketType => SOCK_STREAM, # Sets the socket() type
994             # SocketProtocol => 'tcp', # Sets the socket() protocol
995             # # maybe set this to 1? therefore only allowing 1 connection i think
996             # # ListenQueue => SOMAXCONN, # The listen() queue length
997             # Reuse => 'on', # Lets the port be reused
998             # );
999            
1000             };
1001             print STDERR "$@\n" if ($@);
1002             }
1003              
1004             sub sb_file_sock_up {
1005             my($kernel, $heap, $socket) = @_[KERNEL, HEAP, ARG0];
1006             # new ReadWrite wheel for the socket
1007            
1008             $heap->{sock} = POE::Wheel::ReadWrite->new(
1009             Handle => $socket,
1010             Driver => POE::Driver::SysRW->new,
1011             Filter => POE::Filter::MSN->new(ftp => 1, file_size => $heap->{file_size}),
1012             ErrorEvent => '_sb_file_sock_down',
1013             InputEvent => 'handle_event',
1014             );
1015             delete $heap->{sock2};
1016             }
1017              
1018             sub sb_accept_file {
1019             my($kernel, $heap, $session, $data) = @_[KERNEL, HEAP, SESSION, ARG0];
1020              
1021             #####################
1022             # FOR RECEIVING FILE
1023             #####################
1024            
1025             POE::Session->create(
1026             inline_states => {
1027             _start => \&sb_file_start,
1028             _sb_file_got_connection => \&sb_file_got_connection,
1029             _sb_file_sock_down => \&sb_file_sock_down,
1030             handle_event => \&sb_file_handle_event,
1031             _default => sub {
1032             my $arg = $_[ARG0];
1033             print STDERR "MSNFTP-get\:\:$arg\n";
1034             return undef;
1035             },
1036             VER => sub {
1037             my $heap = $_[HEAP];
1038            
1039             $heap->{sock}->put(
1040             POE::Component::Client::MSN::Command->new(USR => "$heap->{username} $heap->{cookie}"),
1041             );
1042             },
1043             FIL => sub {
1044             my $heap = $_[HEAP];
1045            
1046             eval {
1047             my $cmd = POE::Component::Client::MSN::Command->new(TFR => "");
1048             $cmd->{name_only} = 1;
1049             $heap->{sock}->put($cmd);
1050             };
1051             if ($@) {
1052             print STDERR "TFR error:$@\n";
1053             }
1054             },
1055             BYE => \&sb_file_sock_down,
1056             },
1057             args => [ $data, $session->ID, $heap ],
1058             );
1059             }
1060              
1061             sub sb_file_start {
1062             my($kernel, $heap, $data, $parent, $old_heap) = @_[KERNEL, HEAP, ARG0, ARG1, ARG2];
1063            
1064             eval {
1065             my $command = $data->{command};
1066             $heap->{old_heap} = $old_heap;
1067             $heap->{username} = $old_heap->{username};
1068             # we want the parents parent instead
1069             $heap->{parent} = $old_heap->{parent};
1070             # $heap->{parent} = $parent;
1071             $heap->{file_name} = $data->{fields}->{'Application-File'};
1072             $heap->{session} = $command->transaction;
1073             $heap->{transaction} = $old_heap->{transaction} + 1;
1074             $heap->{auth_cookie} = int rand(2**32); # no zeros
1075             my %fields;
1076             foreach (@{$command->{'message'}->{'mail_inet_body'}}) {
1077             my ($k,$v) = split(/:\s/);
1078             $fields{$k} = $v;
1079             }
1080              
1081             # setup the listening sockets
1082             $heap->{sock} = POE::Wheel::SocketFactory->new(
1083             BindAddress => INADDR_ANY, # Sets the bind() address
1084             BindPort => 6891, # Sets the bind() port
1085             SuccessEvent => '_sb_file_got_connection', # Event to emit upon accept()
1086             FailureEvent => '_sb_file_sock_down', # Event to emit upon error
1087             SocketDomain => AF_INET, # Sets the socket() domain
1088             SocketType => SOCK_STREAM, # Sets the socket() type
1089             SocketProtocol => 'tcp', # Sets the socket() protocol
1090             # maybe set this to 1? therefore only allowing 1 connection i think
1091             # ListenQueue => SOMAXCONN, # The listen() queue length
1092             Reuse => 'on', # Lets the port be reused
1093             );
1094              
1095             # TODO: use getsockname to get the address that we bound to put in the msg
1096             #PortX: 11178\r\n
1097             #$heap->{cookie} = $fields{'Invitation-Cookie'};
1098             $heap->{cookie} = $heap->{auth_cookie};
1099              
1100             # TODO get real ip addresses in here!
1101             my $msg = qq(MIME-Version: 1.0\r\nContent-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\nIP-Address: 216.254.16.46\r\nIP-Address-Internal: 10.0.2.11\r\nPort: 6891\r\nAuthCookie: $heap->{auth_cookie}\r\nSender-Connect: TRUE\r\nInvitation-Command: ACCEPT\r\nInvitation-Cookie: $fields{'Invitation-Cookie'}\r\nLaunch-Application: FALSE\r\nRequest-Data: IP-Address:\r\n\r\n);
1102             my $cmd = POE::Component::Client::MSN::Command->new(
1103             MSG => "N ".length($msg)."\r\n$msg" => $msg, 1
1104             );
1105             $cmd->{transaction}++;
1106             $heap->{old_heap}->{sock}->put($cmd);
1107             };
1108             print STDERR "$@\n" if ($@);
1109             }
1110              
1111             sub sb_file_got_connection {
1112             my($kernel, $heap, $socket) = @_[KERNEL, HEAP, ARG0];
1113             # eval {
1114             $heap->{sock} = POE::Wheel::ReadWrite->new(
1115             Handle => $socket,
1116             Driver => POE::Driver::SysRW->new,
1117             Filter => POE::Filter::MSN->new(ftp => 1),
1118             ErrorEvent => '_sb_file_sock_down',
1119             InputEvent => 'handle_event',
1120             );
1121             $heap->{sock}->put(
1122             POE::Component::Client::MSN::Command->new(VER => "MSNFTP"),
1123             );
1124             }
1125              
1126             sub sb_file_sock_down {
1127             print STDERR "sb_file_sock_down\n";
1128             delete $_[HEAP]->{sock};
1129             }
1130              
1131             sub sb_file_sock_down2 {
1132             # for our dual connect sending
1133             print STDERR "sb_file_sock_down2\n";
1134             delete $_[HEAP]->{sock2};
1135             }
1136              
1137             sub sb_file_handle_event {
1138             my($kernel, $heap, $session, $command) = @_[KERNEL, HEAP, SESSION, ARG0];
1139             eval {
1140             if (exists($command->{stream})) {
1141             $command->{session_id} = $session->ID;
1142             $command->{file_name} = $heap->{file_name};
1143             if (exists($command->{eof})) {
1144             $kernel->post($heap->{parent} => notify => file_complete => $command);
1145             # send BYE
1146             $heap->{sock}->put(
1147             POE::Component::Client::MSN::Command->new(BYE => "16777989"),
1148             );
1149             } elsif (exists($command->{error_num})) {
1150             # TODO add error_num to the filter
1151             $kernel->post($heap->{parent} => notify => file_error => $command);
1152             $heap->{sock}->put(
1153             POE::Component::Client::MSN::Command->new(BYE => $command->{error_num}),
1154             );
1155             } else {
1156             $kernel->post($heap->{parent} => notify => file_data_stream => $command);
1157             }
1158             } else {
1159             $kernel->yield($command->name, $command);
1160             }
1161             };
1162             print STDERR "$@" if ($@);
1163             }
1164              
1165             sub sb_file_got_version {
1166             my($kernel, $heap, $command) = @_[KERNEL, HEAP, ARG0];
1167             # print STDERR "sb_file_sock_down";
1168             # $kernel->post($heap->{parent} => notify => got_version => { command => $command, session_id => $_[SESSION]->ID });
1169             }
1170              
1171              
1172             1;
1173             __END__