File Coverage

blib/lib/Net/OICQ/ServerEvent.pm
Criterion Covered Total %
statement 18 331 5.4
branch 0 116 0.0
condition 0 24 0.0
subroutine 6 21 28.5
pod 0 15 0.0
total 24 507 4.7


line stmt bran cond sub pod time code
1             package Net::OICQ::ServerEvent;
2              
3             # $Id: ServerEvent.pm,v 1.4 2007/06/15 18:09:53 tans Exp $
4              
5             # Copyright (c) 2003 - 2006 Shufeng Tan. All rights reserved.
6             #
7             # This package is free software and is provided "as is" without express
8             # or implied warranty. It may be used, redistributed and/or modified
9             # under the terms of the Perl Artistic License (see
10             # http://www.perl.com/perl/misc/Artistic.html)
11              
12 1     1   6830 use strict;
  1         3  
  1         42  
13 1     1   8 use warnings;
  1         2  
  1         52  
14              
15 1     1   9 eval "no encoding; use bytes;" if $] >= 5.008;
  1     1   2  
  1         10  
  1         59  
  1         1  
  1         6  
16              
17 1     1   6 use Crypt::OICQ qw(encrypt decrypt);
  1         3  
  1         74  
18 1     1   7 use Net::OICQ::ClientEvent;
  1         2  
  1         4837  
19             our @ISA = qw(Net::OICQ::Event);
20              
21             my $InfoHeader = \@Net::OICQ::InfoHeader;
22              
23             sub new {
24 0     0 0   my ($class, $data, $oicq) = @_;
25 0 0 0       unless (defined $data and length($data) > 8) {
26 0 0         $oicq->log_t("Discard data from server:\n", unpack("H*", $data)) if $oicq->{Debug};
27 0           return;
28             }
29 0           my $time = time;
30 0           $oicq->{LastSvrAck} = $time;
31 0           my $self = {
32             Time => $time,
33             OICQ => $oicq,
34             Header => substr($data, 0, 7),
35             };
36 0           bless $self, $class;
37 0           my $cmdcode = $self->cmdcode;
38 0           my $cmd = $self->cmd;
39 0 0 0       if ($cmd eq 'login' || $cmd =~ /^reg_new_id_/) {
40 0           $oicq->log_t("Cmd $cmd ($cmdcode):\n", $oicq->hexdump($data));
41 0           return undef;
42             }
43 0 0         if ($self->process) {
44 0           my $crypt = substr($data, 7, -1);
45 0           my $plain;
46 0           eval { $plain = decrypt(undef, $crypt, $oicq->{Key}) };
  0            
47 0 0         $oicq->log_t("Error in new ServerEvent:", unpack("H*", $self->{Header}), "$cmd\n", $@) if $@;
48 0 0         return undef unless defined $plain;
49 0           $self->{Data} = $plain;
50 0 0         $oicq->log_t("Server mesg header: ", unpack("H*", $self->{Header}),
51             " $cmd: ", unpack("H*", $self->{Data})) if $oicq->{Debug};
52             }
53 0           return $self;
54             }
55              
56             # Server replies with user info
57             sub get_user_info {
58 0     0 0   my ($self) = @_;
59 0           my $oicq = $self->{OICQ};
60 0           my $plain = $self->{Data};
61 0           my @field = split(/$Net::OICQ::FS/, $plain);
62              
63 0 0         return unless defined $field[0];
64 0           $self->{Info} = \@field;
65 0 0         return if $field[0] =~ /^-/;
66              
67             # If the info is about myself, update MyInfo field.
68 0 0         $oicq->{MyInfo} = [@field] if $field[0] == $oicq->{Id};
69              
70             # Initialize Info for the QQ id
71 0 0         $oicq->{Info}->{$field[0]} = {} unless defined $oicq->{Info}->{$field[0]};
72             # Update nickname, age, sex, and face(or avatar)
73 0           my $hashref = $oicq->{Info}->{$field[0]};
74 0           $hashref->{Nickname} = $field[1];
75 0           $hashref->{Age} = $field[7];
76 0           $hashref->{Sex} = $field[8];
77 0           $hashref->{Face} = $oicq->get_face($field[21]);
78              
79             # Update user info file
80 0           my $datfile = "$oicq->{Dir}/$oicq->{Id}/$field[0].dat";
81 0           my $dat = new FileHandle(">$datfile");
82 0 0         if (defined $dat) {
83 0           print $dat "\$_ = {\n";
84 0           for(my $j = 0; $j<=$#field; $j++) {
85 0           printf $dat "%-15s => '%s',\n", $InfoHeader->[$j], $field[$j];
86             }
87 0           print $dat "};\n";
88 0           $dat->close;
89             } else {
90 0           $oicq->log_t("Failed to open user info file >$datfile");
91             }
92              
93 0           return 1;
94             }
95              
96             # Server return code is stored in $event->{ReturnCode}
97             sub send_msg {
98 0     0 0   my ($self) = @_;
99 0           my $oicq = $self->{OICQ};
100 0           my $hex = unpack("H*", $self->{Data});
101 0           $self->{ReturnCode} = $hex;
102 0           return 1;
103             }
104              
105             # recv_msg handles messages from QQ servers, other users, or groups
106             # These event attributes will be set: SrcId, DstId, MsgType, MsgTime, Mesg,
107             # SrcId2, DstId2, $oicq->{Info}->{$srcid}->{Client}, MsgSubtype, MsgSeq for user messages
108             # GrpId, GrpType, SrcId2 for group messages
109             # BotError if a chat bot is defined
110              
111             sub recv_msg {
112 0     0 0   my ($self) = @_;
113 0           my $oicq = $self->{OICQ};
114 0           my $plain = $self->{Data};
115 0           my ($srcid, $dstid, $x) = unpack('NNN', substr($plain, 0, 12));
116 0           $self->{SrcId} = $srcid;
117 0           $self->{DstId} = $dstid;
118 0           $self->{N8_12} = $x;
119 0           my $srcaddr = $oicq->show_address(substr($plain, 12, 6));
120 0           my $msg_type = unpack('n', substr($plain, 18, 2));
121 0           $self->{SrcAddr} = $srcaddr;
122 0           $self->{MsgType} = $msg_type;
123              
124 0 0 0       if ($srcid != 10000 and !defined($oicq->{Info}->{$srcid})) {
125 0           $oicq->{Info}->{$srcid} = {};
126             }
127 0           my $mesg;
128 0 0 0       if (grep {$msg_type == $_} 0x09, 0x0a, 0x84, 0x85) {
  0 0          
129 0           my ($client, $srcid2, $dstid2, $x, $subtype, $seq, $time) =
130             unpack('H4NNH32nnN', substr($plain, 20, 34));
131 0           $oicq->{Info}->{$srcid}->{Client} = $client;
132 0           $self->{SrcId2} = $srcid2;
133 0           $self->{DstId2} = $dstid2;
134 0           $self->{H30_46} = $x;
135 0           $self->{Subtype} = $subtype;
136 0           $self->{MsgSeq} = $seq;
137 0           $self->{MsgTime} = $time;
138 0 0         if ($subtype == 0x81) { # Request for file transfer, voice or video
    0          
    0          
    0          
139             #$mesg = unpack('H*', substr($plain, 54));
140 0           $self->{RequestId} = unpack('H*', substr($plain, 94, 2));
141 0           $self->{RequestIP} = $oicq->show_address(substr($plain, 96, 4));
142 0 0         if ($plain =~ /([^\x1f]+?)\x1f(\d+) \xd7\xd6\xbd\xda$/s) {
    0          
    0          
143 0           $self->{FileName} = $1;
144 0           $self->{FileSize} = $2;
145             } elsif ($plain =~ /(\xd3\xef\xd2\xf4\xc1\xc4\xcc\xec)/s) {
146 0           $self->{VoiceChat} = $1;
147             } elsif ($plain =~ /(\xd3\xef\xd2\xf4\xca\xd3\xc6\xb5\xc1\xc4\xcc\xec)/s) {
148 0           $self->{VideoChat} = $1;
149             } else {
150 0           $self->{Ignore} = 1;
151             }
152             } elsif ($subtype == 0x85) { # Cancel
153             #$mesg = unpack('H*', substr($plain, 54));
154 0           $self->{RequestCancelled} = unpack('H*', substr($plain, 84, 2));
155             } elsif ($subtype == 0x35) {
156 0           $self->{Ignore} = 1;
157             #$mesg = unpack('H*', substr($plain, 54));
158             } elsif ($subtype == 0x0b) {
159 0           $mesg = substr($plain, 73);
160             } else {
161 0           $mesg = substr($plain, 54);
162             }
163             } elsif ($msg_type == 0x20 or $msg_type == 0x2b) { # Group message
164 0           my ($gid, $gtype, $srcid2, $x1, $seq, $time, $x2, $len, $x3) =
165             unpack('NH2NH4nNH8nH20', substr($plain, 20, 33));
166 0           $self->{GrpId} = $gid;
167 0           $self->{GrpType} = $gtype;
168 0           $self->{SrcId2} = $srcid2;
169 0           $self->{H9_10} = $x1;
170 0           $self->{MsgSeq} = $seq;
171 0           $self->{MsgTime} = $time;
172 0           $self->{H17_20} = $x2;
173 0           $self->{MsgLen} = $len;
174 0           $self->{MsgHead} = $x3;
175 0           $mesg = substr($plain, 53);
176             }
177             # Let's process the message tail
178 0 0         if ($mesg) {
    0          
    0          
    0          
    0          
    0          
    0          
179 0           my $tail_len = ord(substr($mesg, -1, 1));
180 0           my $tail = substr($mesg, -1-$tail_len);
181 0 0         if ($tail =~ /^ \0/) {
182             # get rid of tail from $mesg
183 0           substr($mesg, -1-$tail_len) = "";
184             # don't care about bold, italic, or underscore
185 0           $self->{FontSize} = ord(substr($tail, 2, 1)) & 0x1f;
186 0           $self->{FontColor} = unpack('H*', substr($tail, 3, 3));
187 0           $tail =~ s/.$//;
188 0           $self->{FontName} = substr($tail, 9);
189             }
190 0 0         if ($oicq->{LogChat}) {
191 0 0         my $grpid = exists($self->{GrpId}) ? "(Group $self->{GrpId})" : "";
192 0           my $time = substr(localtime($self->{MsgTime}), 4, 16);
193 0           $oicq->log_t("$time received message from $srcid$grpid:\n$mesg");
194             }
195             } elsif ($msg_type == 0x18) {
196 0           $self->{MsgHeader} = unpack("H*", substr($plain, 20, 5));
197 0           $mesg = substr($plain, 25);
198             } elsif ($msg_type == 0x30) {
199 0           $self->{MsgHeader} = unpack("H*", substr($plain, 20, 1));
200 0           $mesg = substr($plain, 21);
201             } elsif ($msg_type == 0x34) { # Backdrop
202 0           $self->{MsgTime} = unpack('N', substr($plain, -4));
203 0 0         if (length($plain) <= 30) {
204 0           $self->{BackdropCancelled} = 1;
205 0           $mesg = "";
206             } else {
207 0           my $len = ord(substr($plain, 27, 1));
208 0           $self->{Backdrop} = substr($plain, 28, $len);
209 0           $mesg = substr($plain, 20);
210             }
211             } elsif ($msg_type == 0x41) {
212 0           $self->{MsgHeader} = unpack("H*", substr($plain, 20, 9));
213 0           $mesg = substr($plain, 29);
214             } elsif ($msg_type == 0x4c) {
215 0           $self->{MsgHeader} = unpack("H*", substr($plain, 20, 7));
216 0           $mesg = substr($plain, 27);
217             } elsif ($oicq->{Debug}) {
218 0           $mesg = unpack('H*', substr($plain, 20));
219 0           $oicq->log_t("Unknown message type $msg_type from $srcid, $srcaddr:\n$mesg");
220             }
221 0           $self->{Mesg} = $mesg;
222              
223 0 0 0       if (defined $oicq->{Socket} and defined $mesg and ! $self->{Ignore}) {
      0        
224 0           $oicq->ack_msg($self->seq, $plain);
225             }
226 0           return 1;
227             }
228              
229             # Response to get_online_friends is a list of fixed length (38 bytes)
230             # records, will update $oicq->{Info}, $event->{OnlineFriends}
231              
232             sub get_online_friends {
233 0     0 0   my ($self) = @_;
234 0           my $plain = $self->{Data};
235 0           my $oicq = $self->{OICQ};
236 0           my @list = ();
237 0           my $info = $oicq->{Info};
238 0           for(my $i = 1; $i
239 0           my $fid = unpack('N', substr($plain, $i, 4));
240 0           my $addr = $oicq->show_address(substr($plain, $i+5, 6));
241 0           my $mode = ord(substr($plain, $i+12, 1));
242 0           my $key = substr($plain, $i+13, 20);
243 0 0         defined $info->{$fid} or $info->{$fid} = {};
244 0           $info->{$fid}->{Key} = $key;
245 0           $info->{$fid}->{Mode} = $mode;
246 0 0         $info->{$fid}->{Addr} = $addr if $addr =~/[1-9]/;
247 0           push @list, $fid;
248             }
249 0           $self->{OnlineFriends} = \@list;
250 0           return 1;
251             }
252              
253             sub recv_service_msg {
254 0     0 0   my ($self) = @_;
255 0           my $oicq = $self->{OICQ};
256 0           my ($code, $srcid, $myid, $mesg) = split(/$Net::OICQ::RS/, $self->{Data});
257 0           $self->{ServerCode} = $code;
258 0           $self->{SrcId} = $srcid;
259 0           $self->{DstId} = $myid;
260 0           $self->{Mesg} = $mesg;
261 0 0         if (defined $oicq->{Socket}) {
262 0           $oicq->ack_service_msg($code, $srcid, $self->seq);
263             }
264 0           my $comment;
265 0 0 0       if ($code eq "02" or $code eq "41") {
    0          
    0          
    0          
266 0           $comment = "$srcid asked to add $myid";
267             } elsif ($code eq "03") {
268 0           $comment = "$srcid accepted $myid";
269             } elsif ($code eq "04") {
270 0           $comment = "$srcid rejected $myid";
271             } elsif ($srcid == 10000) {
272 0           $comment = "garbage from $srcid";
273             } else {
274 0           $comment = "unknown";
275             }
276 0           $self->{Comment} = $comment;
277 0           $oicq->log_t("$comment:\n$mesg");
278 0           return 1;
279             }
280              
281             # List of lists is stored in $event->{UserList}
282              
283             sub search_users {
284 0     0 0   my ($self) = @_;
285 0           my $plain = $self->{Data};
286 0           my $oicq = $self->{OICQ};
287 0           my @list;
288 0           foreach my $line (split(/$Net::OICQ::RS/, $plain)) {
289 0           my @f = split(/$Net::OICQ::FS/, $line);
290 0 0         next unless defined $f[3];
291 0           $f[3] = $oicq->get_face($f[3]);
292 0           push @list, \@f;
293             }
294 0           $self->{UserList} = \@list;
295 0           return 1;
296             }
297              
298             sub keep_alive {
299 0     0 0   my $self = shift;
300 0           my $oicq = $self->{OICQ};
301 0           my $plain = $self->{Data};
302             #my @field = split($Net::OICQ::RS, $plain);
303             #$oicq->{UserCount} = $field[2];
304             #$self->{ServerInfo} = \@field;
305 0           return 1;
306             }
307              
308             sub get_contact_id {
309 0     0 0   my ($self, $seq) = @_;
310 0           my $event;
311 0           foreach my $e (@{$self->{OICQ}->{EventQueue}}) {
  0            
312 0 0         next unless ref($e) =~ /Client/;
313 0 0         if ($e->seq eq $seq) {
314 0           $event = $e;
315 0           last;
316             }
317             }
318 0 0         return 'Someone' unless defined $event;
319 0           my ($id) = $event->{Data} =~ /^(\d+)/;
320 0           return $id;
321             }
322              
323             sub add_contact_1 {
324 0     0 0   my ($self) = @_;
325 0           my $plain = $self->{Data};
326 0           my ($id, $reply) = split(/$Net::OICQ::RS/, $plain);
327 0           $self->{Id} = $id;
328 0           $self->{Reply} = $reply;
329 0           my $srcid = $self->get_contact_id($self->seq);
330 0 0         if ($reply =~ /^\d+$/) {
331 0 0         if ($reply > 0) {
    0          
332 0           $self->{Comment} = "$srcid requires authentication message.";
333 0           return 0;
334             } elsif ($reply == 0) {
335 0           $self->{Comment} = "$srcid has accepted your request.";
336 0           return 1;
337             }
338             }
339 0           $self->{Comment} = "Unknown reply from add_contact_1 $srcid: $reply";
340 0           return;
341             }
342              
343 0     0 0   sub add_contact_2 {
344             }
345              
346             # get_friends_list provided by Chen Peng
347              
348             sub get_friends_list {
349 0     0 0   my ($self) = @_;
350 0           my $plain = $self->{Data};
351 0           my $oicq = $self->{OICQ};
352 0           my $flag = substr($plain, 0, 2);
353 0           $self->{Flag} = unpack("H*", $flag);
354 0           my $p = 2;
355 0           my $len = length($plain);
356 0           while ($p < $len) {
357 0           my $fid = unpack('N', substr($plain, $p, 4));
358 0           $p += 4; # one 0x00 to seperate
359 0           my $face = $oicq->get_face(ord(substr($plain, $p+1, 1))); $p += 2;
  0            
360 0           my $age = ord(substr($plain, $p, 1)); $p += 1;
  0            
361 0           my $sex = ord(substr($plain, $p, 1)); $p += 1;
  0            
362 0           my $name_len = ord(substr($plain, $p, 1)); $p += 1;
  0            
363 0           my $nickname = substr($plain, $p, $name_len); $p += $name_len;
  0            
364 0           my $unknown = unpack("H*", substr($plain, $p, 4)); $p += 4;
  0            
365 0 0         $oicq->{Info}->{$fid} = {} unless defined $oicq->{Info}->{$fid};
366 0           my $info = $oicq->{Info}->{$fid};
367 0           $info->{Sex} = $sex;
368 0           $info->{Age} = $age;
369 0           $info->{Face} = $face;
370 0           $info->{Nickname} = $nickname;
371 0           $info->{Friend} = 1;
372 0           $info->{Unknown} = $unknown;
373             }
374 0 0         if ($flag ne "\xff\xff") {
375 0           $oicq->get_friends_list($flag);
376             }
377 0           return 1;
378             }
379              
380             sub recv_friend_status {
381 0     0 0   my ($self) = @_;
382 0           my $plain = $self->{Data};
383 0           my $oicq = $self->{OICQ};
384 0           my $srcid = unpack('N', substr($plain, 0, 4));
385 0           my $addr = $oicq->show_address(substr($plain, 5, 6));
386 0           $self->{Mode} = ord(substr($plain, 12, 1));
387 0           $self->{H13_33} = unpack("H*", substr($plain, 13, 20));
388 0           $self->{DstId} = unpack('N', substr($plain, 35, 4));
389 0           $self->{SrcId} = $srcid;
390 0 0         $oicq->{Info}->{$srcid} = {} unless defined $oicq->{Info}->{$srcid};
391 0           my $info = $oicq->{Info}->{$srcid};
392 0 0         if ($addr =~ /[1-9]/) {
393 0           $self->{Addr} = $addr;
394 0           $info->{Addr} = $addr;
395             }
396 0           $info->{Mode} = $self->{Mode};
397 0           return 1;
398             }
399              
400             sub do_group {
401 0     0 0   my ($self) = @_;
402 0           my $plain = $self->{Data};
403 0           my $oicq = $self->{OICQ};
404 0           my ($sub_cmd, $reply) = unpack('H2H2', substr($plain, 0, 2));
405 0           $self->{SubCmd} = $sub_cmd;
406 0           $self->{Reply} = $reply;
407 0 0         if ($reply ne '00'){
408 0           $self->{Error} = substr($plain, 2);
409 0           return;
410             }
411 0 0         if ($sub_cmd eq '06') { # search group
412 0           my ($search_type, $int_gid, $ext_gid, $gtype, $x, $owner_id) =
413             unpack('H2NNH2H8N', substr($plain, 2, 18));
414 0           my $gname_len = ord(substr($plain, 30, 1));
415 0           my $gname = substr($plain, 31, $gname_len);
416 0           my $gauth_type = unpack('H*', substr($plain, 33+$gname_len, 1));
417 0           my $gdesc_len = ord(substr($plain, 34+$gname_len, 1));
418 0           my $gdesc = substr($plain, 35+$gname_len, $gdesc_len);
419 0           $oicq->log_t("S_DO_GROUP $sub_cmd code $reply:\n", $oicq->hexdump($plain));
420 0           $self->{GrpIntId} = $int_gid;
421 0           $self->{GrpExtId} = $ext_gid;
422 0           $self->{GrpOwner} = $owner_id;
423 0           $self->{GrpName} = $gname;
424 0           $self->{GrpDesc} = $gdesc;
425 0           return 1;
426             }
427 0 0         if ($sub_cmd eq '04') { # group info
428 0           my ($int_gid, $ext_gid, $gtype, $owner_id, $gauth_type) =
429             unpack('NNH2NH2', substr($plain, 2, 14));
430 0           my $cat = unpack("n",substr($plain, 18, 2));
431 0           my $gname_len = ord(substr($plain, 24, 1));
432 0           my $gname = substr($plain, 25, $gname_len);
433 0           my $gnotice_len = ord(substr($plain, 27+$gname_len, 1));
434 0           my $gnotice = substr($plain, 28+$gname_len, $gnotice_len);
435 0           my $gdesc_len = ord(substr($plain, 28+$gname_len+$gnotice_len, 1));
436 0           my $gdesc = substr($plain, 29+$gname_len+$gnotice_len, $gdesc_len);
437 0           $self->{GrpIntId} = $int_gid;
438 0           $self->{GrpName} = $gname;
439 0           $self->{GrpNotice} = $gnotice;
440 0           $self->{GrpDesc} = $gdesc;
441 0           return 1;
442             }
443 0 0         if ($sub_cmd eq '0b') { # online group members
444 0           $self->{GrpIntId} = unpack('N', substr($plain, 2, 4));
445 0 0         my @online_members = length($plain) >= 11 ? unpack('N*', substr($plain, 7)) : ();
446 0           $self->{OnlineMembers} = \@online_members;
447 0           return 1;
448             }
449 0           $self->{Unknown} = unpack("H*", substr($plain, 2));
450 0           return;
451             }
452              
453             sub req_file_key {
454 0     0 0   my ($self) = @_;
455 0           my $plain = $self->{Data};
456 0           my $oicq = $self->{OICQ};
457 0 0 0       unless (unpack('H4', $plain) eq '0400' and length($plain) > 18) {
458 0           $oicq->log_t("Svr response to req_file_key:\n", $oicq->hexdump($plain));
459 0           return;
460             }
461 0           my $file_key = substr($plain, 2, 16);
462 0           $self->{FileKey} = $file_key;
463 0           $oicq->log_t("Received file transfer key from server: $file_key");
464             }
465              
466             1;