ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-Knuddels/Net/Knuddels.pm
Revision: 1.35
Committed: Sun Dec 4 09:53:36 2005 UTC (18 years, 6 months ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.34: +1 -3 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.8 =head1 NAME
2    
3     Net::Knuddels - www.knuddels.de protocol implementation.
4    
5     =head1 SYNOPSIS
6    
7     use Net::Knuddels;
8    
9     =head1 DESCRIPTION
10    
11     RTSL.
12    
13     =cut
14    
15 root 1.31 #TODO: PING:
16 root 1.30 #0 4
17     #1 "elmecks|1106987572172"
18    
19 root 1.31 # TODO: anti-idle, immer oder optional
20    
21     # TODO: send_james for james-messages
22    
23 root 1.33 # TODO: Es wurde zu h\344ufig ein falsches Passwort beim Login verwendet.
24     # TODO: _Falsches Passwort_ f\374r ich bin suess verwendet.
25     # TODO: Dieser Channel ist auf _maximal 50_ Leute beschr\344nkt, bitte w\344hle einen anderen Channel.
26     # TODO: Sie sind mit ihrem Nick in zuvielen Channels gleichzeitig eingeloggt. Schlie\337en Sie zuerst einen anderen Channel
27    
28 root 1.1 package Net::Knuddels;
29    
30 root 1.3 use Net::Knuddels::Dictionary;
31    
32 root 1.2 use strict;
33     use utf8;
34    
35 root 1.4 use Carp;
36     use Math::BigInt;
37 root 1.27 use Time::HiRes;
38 root 1.4
39 root 1.14 sub _to32($) {
40     unpack "l", pack "L", (new Math::BigInt $_[0]) & 0xffffffff
41     }
42    
43 root 1.4 sub hash_pw($$) {
44     my ($challenge, $pw) = @_;
45    
46     my $l1 = length $pw;
47     my $l2 = length $challenge;
48    
49     my $k = chr ($l1 ^ ($l2 << 4));
50    
51     my $l = $l1 < $l2 ? $l2 : $l1;
52    
53     my $xor = substr +($pw x 100) ^ ($challenge x 100) ^ ($k x 100), 0, $l;
54    
55     my ($i, $j);
56    
57     --$l;
58    
59     if ($l <= 17) {
60 root 1.33 for my $x (0 .. $l) {
61     $i = _to32 $i * 3 + ord substr $xor, $l - $x;
62     $j = _to32 $j * 5 + ord substr $xor, $x;
63 root 1.4 }
64     } else {
65 root 1.33 for (my $x = $l; $x >= 0; $x -= int +($l+1)/19) {
66     $i = _to32 $i * 5 + ord substr $xor, $x;
67     $j = _to32 $j * 3 + ord substr $xor, $l - $x;
68 root 1.4 }
69     }
70    
71     $i ^= $j;
72 root 1.33
73     use integer; # force signed shift
74 root 1.14 _to32 (($i & 0xffffff) ^ ($i >> 24))
75 root 1.4 }
76    
77 root 1.10 my $RE_dec = join "|", keys %$Net::Knuddels::Dictionary;
78    
79     sub decode {
80     my $bin = unpack "b*", $_[0];
81     my $res = "";
82    
83     while ($bin =~ /\G($RE_dec)/cog) {
84     my $frag = $Net::Knuddels::Dictionary->{$1};
85 root 1.14 $frag = chr unpack "v", pack "b*", $bin =~ /\G(.{16})/cg && $1 if $frag eq "\\\\\\";
86 root 1.10 $res .= $frag;
87     }
88     $bin =~ /\G(.*[^0].*)$/ and die "Net::Knuddels::Receiver: undecodable message tail '$1'";
89    
90     $res
91     }
92    
93 root 1.9 my %encode = reverse %$Net::Knuddels::Dictionary;
94    
95 root 1.10 my $RE_enc = join "|", map quotemeta, sort { (length $b) <=> (length $a) } keys %encode;
96 root 1.9
97 root 1.10 sub encode($) {
98 root 1.9 my ($msg) = @_;
99    
100     my $data = "";
101    
102     while () {
103     $data .= $encode{$1} while $msg =~ /\G($RE_enc)/cog;
104    
105     $msg =~ /\G./csog
106     or last;
107    
108     $data .= $encode{"\\\\\\"} . unpack "b*", pack "v", ord $1;
109     }
110    
111     pack "b*", $data
112     }
113    
114 root 1.5 =head2 CLASS Net::Knuddels::Protocol
115    
116     You B<must> call the C<destroy> method of this class when you no longer
117     use it, as circular references will keep the object alive otherwise.
118    
119 root 1.6 =over 4
120    
121 root 1.8 =cut
122    
123     package Net::Knuddels::Protocol;
124    
125 root 1.6 =item new
126    
127     Create a new C<Net::Knuddels::Protocol> object.
128    
129 root 1.5 =cut
130 root 1.2
131     sub new {
132     my $class = shift;
133    
134 root 1.4 my %data;
135    
136     my $self = bless {
137     @_
138     }, $class;
139    
140     $self;
141 root 1.2 }
142    
143 root 1.6 =item $protocol->feed_data ($octets)
144    
145     Feed raw protocol data into the decoder.
146    
147     =cut
148    
149 root 1.2 sub feed_data($$) {
150     my ($self, $data) = @_;
151    
152     # split data stream into packets
153    
154     $data = "$self->{rbuf}$data";
155    
156     while () {
157     1 <= length $data or last;
158     my $len = ord substr $data, 0, 1;
159    
160     my $skip;
161     if ($len & 0x80) {
162     my $tail = (($len >> 5) & 3) - 1;
163     $len = ($len & 0x1f) + 1;
164    
165     $tail < length $data or last;
166     $len += (ord substr $data, $_ + 1, 1) << ($_ * 8 + 5)
167     for 0 .. $tail;
168    
169     $skip = 2 + $tail;
170     } else {
171     $skip = 1;
172     $len++;
173     }
174    
175     $len + $skip <= length $data or last;
176     substr $data, 0, $skip, "";
177     my $msg = substr $data, 0, $len, "";
178    
179     $self->feed_msg ($msg);
180     }
181    
182     $self->{rbuf} = $data;
183     }
184    
185     sub feed_msg($$) {
186     my ($self, $msg) = @_;
187 root 1.1
188 root 1.10 $self->feed_event (split /\0/, Net::Knuddels::decode $msg);
189 root 1.1 }
190    
191 root 1.2 sub feed_event($@) {
192 root 1.14 my ($self, @cmd) = @_;
193 root 1.1
194 root 1.14 my $ev = $self->{cb}{ALL};
195     $_->(@cmd) for values %$ev;
196    
197     unless ($self->{cb}{$cmd[0]}) {
198     my $ev = $self->{cb}{UNHANDLED};
199     $_->(@cmd) for values %$ev;
200 root 1.1 }
201 root 1.14
202     my $ev = $self->{cb}{shift @cmd};
203     $_->(@cmd) for values %$ev;
204 root 1.2 }
205 root 1.1
206 root 1.11 =item $msg = $protocol->encode_msg (@strings)
207    
208     Join the strings with C<\0>, encode the result into a protocol packet and
209     return it.
210    
211     =cut
212    
213     sub encode_msg($@) {
214     my ($self, @args) = @_;
215     my $msg = Net::Knuddels::encode join "\0", @args;
216 root 1.12 my $len = (length $msg) - 1;
217    
218     if ($len < 0x80) {
219     (chr $len) . $msg
220     } else {
221     (chr 0x80 | 0x40 | ($len & 0x1f))
222     . (chr +($len >> 5) % 0xff)
223     . (chr +($len >> 13) % 0xff)
224     . $msg
225     }
226 root 1.11 }
227    
228 root 1.6 =item $protocol->register ($type => $callback)
229    
230     Register a callback for events of type C<$type>, which is either the name
231     of a low-level event sent by the server (such as "k" for dialog box) or
232 root 1.16 the name of a generated event, such as C<login>.
233 root 1.6
234     =cut
235    
236 root 1.2 sub register {
237     my ($self, $type, $cb) = @_;
238 root 1.1
239 root 1.2 $self->{cb}{$type}{$cb} = $cb;
240 root 1.1 }
241    
242 root 1.8 =item $protocol->destroy
243    
244 root 1.9 I<MUST> be called to destroy the object, otherwise it will leak (no automatic cleanup).
245 root 1.8
246     =cut
247    
248 root 1.5 sub destroy {
249     my ($self) = @_;
250    
251     delete $self->{cb};
252     }
253    
254 root 1.6 =back
255    
256 root 1.8 =head2 CLASS Net::Knuddels::Client
257    
258 root 1.9 Implement a Knuddels client connection.
259    
260 root 1.8 =over 4
261    
262     =cut
263    
264     package Net::Knuddels::Client;
265    
266 root 1.16 sub handle_room {
267     my ($self, $room) = @_;
268    
269     if ($room eq "-") {
270 elmex 1.23 if (scalar (keys %{$self->{room}}) == 1) {
271     return (keys %{$self->{room}})[0];
272 root 1.16 } else {
273     warn "Couldn't assign '-' room to a room!";
274 elmex 1.23 return '#nosuchroom';
275 root 1.16 }
276     } else {
277     return $room;
278     }
279     }
280    
281 elmex 1.20 sub update_user_stats {
282     my ($user) = @_;
283 root 1.16
284     if ($user->{name} =~ s/\cJ(\d+)$//) {
285     $user->{age} = $1
286     }
287    
288     if ($user->{picture} =~ m/\bmale/) {
289     $user->{gender} = 'm';
290 elmex 1.20
291 root 1.16 } elsif ($user->{picture} =~ m/female/) {
292     $user->{gender} = 'f';
293     }
294 root 1.19
295 root 1.16 return $user;
296     }
297    
298 elmex 1.22 sub del1 {
299     my ($str) = @_;
300     my $s = substr ($$str, 0, 1);
301     $$str = substr ($$str, 1);
302     $s
303     }
304    
305     sub del2 {
306     my ($str) = @_;
307     my $s = substr ($$str, 0, 2);
308     $$str = substr ($$str, 2);
309     $s
310     }
311    
312     sub todelim {
313     my ($str) = @_;
314     $$str =~ s/^(.*?)\365//;
315     $1;
316     }
317    
318     sub chk_flag {
319     my ($str) = @_;
320     if ($$str =~ s/^\343//) {
321     return 1;
322     }
323     return 0;
324     }
325    
326 root 1.9 =item new Net::Knuddels::Client [IO::Socket::new arguments]
327    
328     Create a new client connection.
329    
330 root 1.32 Optional extra arguments:
331    
332     command_wait => $cb($client,$wait)
333     This callback will be called with the client object
334     and a time to wait. It must call the C<command_cb> method
335     after the time specified, either by a blocking wait
336     or via some event loop callback. The default implementation
337     just does a blockign wait.
338    
339 root 1.9 =cut
340    
341     use IO::Socket::INET;
342    
343     sub new {
344     my ($class, @arg) = @_;
345    
346     my $fh = new IO::Socket::INET @arg
347     or Carp::croak "Net::Knuddels::Client::new: $!";
348    
349     my $self = bless {
350 root 1.27 queue => [],
351 root 1.9 fh => $fh,
352     proto => (new Net::Knuddels::Protocol),
353 root 1.28 rate => 1, # commands/s
354 root 1.32 command_wait => sub {
355     select undef, undef, undef, $_[1];
356     $_[0]->command_cb;
357     },
358     queue => [],
359 root 1.27 @arg,
360 root 1.9 }, $class;
361    
362     syswrite $fh, "\0";
363    
364 root 1.16 $self->register ("(" => sub {
365     $self->{login_challenge} = $_[0];
366     $self->{login_room} = $_[1];
367 elmex 1.23 $self->{proto}->feed_event (login => $_[1]);
368 root 1.16 });
369 elmex 1.23
370 elmex 1.22 $self->register (k => sub {
371     my @str = map { s/[\356\343]//; $_ } @_;
372     my @out;
373     push @out, split /#/, $_ for @str;
374     $self->{proto}->feed_event (dialog => \@out);
375     });
376 elmex 1.23
377 elmex 1.21 $self->register (t => sub {
378     my $src = $_[0];
379 elmex 1.23
380 elmex 1.21 if ($src eq '-') {
381 root 1.27 $_[2] = $self->{nick} . " " . $_[2];
382 elmex 1.23
383 elmex 1.21 } else {
384     $_[2] = $src . " " . $_[2];
385     }
386     $self->{proto}->feed_event (action_room => $self->handle_room ($_[1]), $_[2]);
387     });
388 elmex 1.23
389 root 1.27 my %last_msg; # the last message of a user, to avoid duplicates
390    
391 root 1.16 $self->register (r => sub {
392 elmex 1.21 my $src = $_[0];
393 root 1.27 $src = $self->{nick} if $src eq "-";
394    
395 elmex 1.21 $self->{proto}->feed_event (msg_priv => $self->handle_room ($_[2]), $src, $_[1], $_[3]);
396 root 1.27
397     if ($src eq "James") {
398     $self->{proto}->feed_event (msg_priv_james => $self->handle_room ($_[2]), $src, $_[1], $_[3]);
399     } elsif ($src eq $self->{nick}) {
400     $self->{proto}->feed_event (msg_priv_echo => $self->handle_room ($_[2]), $src, $_[1], $_[3]);
401     } else {
402     $self->{proto}->feed_event (msg_priv_nondup => $self->handle_room ($_[2]), $src, $_[1], $_[3])
403     if $last_msg{$src} ne $_[3];
404     $last_msg{$src} = $_[3];
405     }
406 root 1.16 });
407 root 1.27
408 root 1.16 $self->register (e => sub {
409     $self->{proto}->feed_event (msg_room => $self->handle_room ($_[1]), $_[0], $_[2]);
410     });
411 elmex 1.23
412     $self->register (l => sub {
413 root 1.16 my $room = $self->handle_room ($_[0]);
414     return if $room eq "-"; # things that shouln't happen
415    
416     my $user = {
417     name => $_[1],
418     flag => $_[2],
419     color => $_[3],
420     picture => $_[4]
421     };
422    
423 elmex 1.20 update_user_stats ($user);
424 root 1.16
425     my $rl = $self->{user_lists}->{lc $room}->{lc $user->{name}} = $user;
426    
427     $self->{proto}->feed_event (join_room => $room, $user);
428     });
429 root 1.27
430 root 1.16 $self->register (w => sub {
431     my $room = $self->handle_room ($_[1]);
432     return if $room eq "-"; # things that shouln't happen
433    
434     my $username = $_[0];
435    
436     my $u = delete $self->{user_lists}->{lc $room}->{lc $username};
437    
438     if (not defined $u) {
439     warn "User $username wasn't in room $room, trying to fix... but be careful!!!\n";
440     $u = { name => $username };
441     }
442    
443     $self->{proto}->feed_event (part_room => $room, $u);
444     });
445 elmex 1.23
446     $self->register (b => sub {
447     my @arg = @_;
448     my $cc = {};
449     $self->{knuddels_rooms}->{$cc->{name}} = {};
450    
451     my $last = $cc;
452     my $chan_cnt = 2;
453    
454     while (@arg) {
455     $cc->{name} = shift @arg;
456    
457     if ($cc->{name} =~ s/\cJ(\d+)$//) {
458     $cc->{user_count} = $1;
459     }
460 root 1.27
461 elmex 1.23 if ($cc->{name} =~ m/^"/) {
462     $cc->{name} = "$last->{name} $chan_cnt";
463     $chan_cnt++;
464    
465     } else {
466     $last = $cc;
467     $chan_cnt = 2;
468     }
469    
470     $cc->{flag1} = shift @arg;
471     $cc->{flag2} = shift @arg;
472    
473     my $i = 0;
474    
475     for (my $a = shift @arg; $a ne "-"; $a = shift @arg) {
476    
477     if ($i == 0) {
478     $cc->{picture} = $a;
479     $cc->{full_flag} = 1 if $cc->{picture} =~ m/full/i;
480     }
481     $i++;
482     }
483    
484     $self->{knuddels_rooms}->{$cc->{name}} = $cc;
485     $cc = {};
486 root 1.16 }
487    
488 elmex 1.23 $self->{proto}->feed_event (room_list => $self->{knuddels_rooms});
489     });
490    
491     $self->register (d => sub {
492     my $room = $self->handle_room ($_[0]);
493    
494     delete $self->{room}->{lc $room};
495     $self->{room}->{lc $_[1]} = { name => $_[1] };
496    
497     $self->{proto}->feed_event (change_room => $room, $_[1]);
498     });
499    
500     $self->register ('6' => sub {
501     # i have no exact clue what this message does,
502     # but java-code seems to say i should do this:
503    
504     warn "*********************** SIX MESSAGE GOT!!! CHECK PROTOCOL FOR OCCURENCE!!";
505     # delete $self->{room}->{lc ($self->handle_room ($_[0]))};
506     });
507    
508     $self->register (a => sub {
509 root 1.16 $self->{my_nick} = $_[1]; # i'm really _not_ shure about this
510    
511     my $ri = $self->{room}->{lc $_[0]} = {
512 elmex 1.23 name => $_[0],
513 root 1.16 picture => $_[7],
514     };
515    
516     $self->{proto}->feed_event (room_info => $_[0], $ri);
517     });
518 elmex 1.23
519 root 1.16 $self->register (u => sub {
520     my $room = shift;
521     my $rl = $self->{user_lists}->{lc $room} = {};
522     my $cur_u = {};
523    
524     while (@_) {
525     $cur_u->{name} = shift;
526     $cur_u->{flag} = shift;
527     $cur_u->{color} = shift;
528    
529     my $i = 0;
530    
531     while ((my $nxt = shift) ne "-") {
532     if ($i == 0) {
533     $cur_u->{picture} = $nxt;
534     }
535     $i++;
536     }
537    
538 elmex 1.20 update_user_stats ($cur_u);
539 root 1.16 $rl->{lc $cur_u->{name}} = $cur_u;
540     $cur_u = {};
541     }
542     $self->{proto}->feed_event (user_list => $room, $rl);
543     });
544    
545 root 1.9 $self
546     }
547    
548     =item $client->fh
549    
550     Return the fh used for communications. You are responsible for calling C<<
551 root 1.13 $client->ready >> whenever the fh becomes ready for reading.
552 root 1.9
553     =cut
554    
555     sub fh {
556     $_[0]->{fh}
557     }
558    
559 root 1.13 =item $client->ready
560    
561     To be called then the filehandle is ready for reading. Returns false if
562     the server closed the connection, true otherwise.
563    
564     =cut
565    
566     sub ready {
567     my ($self) = @_;
568    
569     sysread $self->{fh}, my $buf, 8192
570     or return;
571    
572     $self->{proto}->feed_data ($buf);
573    
574     1;
575     }
576    
577 root 1.32 =item $client->command_cb
578    
579     Should be called by the C<command_wait> callback when the timer has
580     expired, to send a delayed command to the server.
581    
582     =cut
583    
584     sub command_cb {
585     my ($self) = @_;
586    
587     while () {
588     last unless @{ $self->{queue} };
589    
590     my $NOW = Time::HiRes::time;
591     my $wait = $self->{next_command} - $NOW;
592    
593     if ($wait > 1e-4) {
594     $self->{command_wait}->($self, $wait);
595     last;
596     } else {
597     my ($type, @args) = @{ shift @{ $self->{queue} } };
598     $self->{next_command} = $NOW + $self->{rate} + 1e-4;
599    
600     #use Dumpvalue; Dumpvalue->new (compactDump => 1, veryCompact => 1, quoteHighBit => 1, tick => '"')->dumpValue ([$type, @args]);
601     syswrite $self->{fh}, $self->{proto}->encode_msg ($type, @args);
602     }
603     }
604     }
605    
606    
607    
608 root 1.9 =item $client->command ($type => @args)
609    
610 root 1.27 Send a message of type C<$type> and the given arguments to the server,
611     ensures a proper rate-limit.
612 root 1.9
613     =cut
614    
615     sub command {
616     my ($self, $type, @args) = @_;
617    
618 root 1.32 if (1 == push @{ $self->{queue} }, [$type, @args]) {
619     $self->command_cb;
620     }
621 root 1.9 }
622    
623     =item $client->login ($url, $unknown)
624    
625     Send a 't' message. The default for C<$url> is
626 root 1.34 C<http://www.knuddels.de/applet.html?v=87&c=-3> and C<$unknown> is C<3>.
627 root 1.9
628     =cut
629    
630     sub login {
631 root 1.13 my ($self, $url, $unknown) = @_;
632    
633 root 1.34 $self->command ("t", "V8.7", $url || "http://www.knuddels.de/applet.html?v=87&c=-3", $unknown || 3);
634 root 1.13 }
635    
636 elmex 1.23 =item $client->enter_room ($room, $nick, $password)
637    
638 root 1.27 Enters a room C<$room> with C<$nick> and C<$password>. (for joining
639     multiple rooms call this multiple times)
640 root 1.13
641 root 1.27 NOTE: i won't allow joins to multiple rooms with different
642     nick/password's, the java client reacted very confused.. don't know
643     whether the server supports this.
644 root 1.13
645     =cut
646    
647 elmex 1.23 sub enter_room {
648 root 1.13 my ($self, $room, $nick, $password) = @_;
649    
650 root 1.27 if (defined $self->{nick} and $self->{nick} ne $nick) {
651 elmex 1.23 return # i don't think knuddels-server will be happy if
652     # we join multiple rooms on multiple accounts over 1 connection
653     }
654    
655 root 1.16 exists $self->{login_challenge} or Carp::croak "set_nick can only be called after a login event";
656 root 1.13
657 root 1.27 $self->{nick} = $nick;
658 root 1.16 $self->command ("n", $room, $nick, Net::Knuddels::hash_pw $self->{login_challenge}, $password);
659 root 1.9 }
660    
661 elmex 1.22 =item $client->send_whois ($nick)
662    
663 root 1.27 Sends a whois-request for $nick.
664 elmex 1.22
665     =cut
666 root 1.27
667 elmex 1.22 sub send_whois {
668     my ($self, $room, $nick) = @_;
669    
670     $self->command ("e", $room, "/w $nick");
671     }
672    
673    
674 elmex 1.21 =item $client->send_room_msg ($nick, $room, $message)
675    
676 root 1.27 Sends a private C<$message> to C<$nick> over C<$room>.
677 elmex 1.21
678     =cut
679 root 1.27
680 elmex 1.21 sub send_room_msg {
681     my ($self, $room, $message) = @_;
682    
683     $self->command ("e", $room, $message);
684     }
685    
686    
687     =item $client->send_priv_msg ($nick, $room, $message)
688    
689 root 1.27 Sends a private C<$message> to C<$nick> over C<$room>.
690 elmex 1.21
691     =cut
692 root 1.27
693 elmex 1.21 sub send_priv_msg {
694     my ($self, $nick, $room, $message) = @_;
695    
696     $self->command ("e", $room, "/p $nick:$message");
697     }
698    
699 elmex 1.23 =item $client->send_join_room ($oldroom, $room)
700    
701 root 1.27 Sends the server a join command for C<$room>. This will result in a room
702     change from C<$oldroom> to C<$room>.
703 elmex 1.23
704     =cut
705 root 1.27
706 elmex 1.23 sub send_join_room {
707     my ($self, $old_room, $room) = @_;
708    
709     $self->command ("e", $old_room, "/go $room");
710     }
711    
712     =item $client->send_exit_room ($room)
713    
714 root 1.27 Exits C<$room> completly. (can be seen as counter method for C<enter_room ()>)
715 elmex 1.23
716     =cut
717 root 1.27
718 elmex 1.23 sub send_exit_room {
719     my ($self, $room) = @_;
720     $self->command ("w", $room, "\0", "\0");
721     delete $self->{room}->{lc $room};
722     }
723    
724 root 1.9 =item $client->register ($type => $cb)
725    
726 root 1.16 See L<Net::Knuddels::Protocol::register>. The following extra events will
727     be generated by this class:
728 root 1.9
729 root 1.16 login
730     set_nick can only be called _after_ a login event has occured.
731    
732     msg_room => $room, $user, $msg
733     produced when a public message is uttered :)
734    
735 root 1.27 msg_priv => $room, $src, $dst, $msg
736     personal message from $src to $dst. better use msg_priv_nondup,
737     msg_priv_james or msg_priv_echo
738    
739     msg_priv_echo => $room, $src, $dst, $msg
740     like msg_priv, but only for echoed messages
741    
742     msg_priv_james => $room, $src, $dst, $msg
743     like msg_priv, but only for messages from James
744    
745     msg_priv_nondup => $room, $src, $dst, $msg
746     like msg_priv, but avoids duplicate messages, echos and james.
747 root 1.16
748     user_list => $room, $list
749     the userlist of a channel named $room, a elmement of the list (a user)
750     looks like:
751     {
752     name => <name>,
753     flag => <some flag i don't know what it means>,
754     color => like /\d+.\d+.\d+/,
755     age => /\d+/,
756     gender => /(f|m)/,
757     picture => <the picture file to put behind the nick>
758     }
759    
760     room_info => $room, $room_info
761     some information about the $room:
762     $room_info =
763     {
764     picture => <some picturefile>
765     }
766    
767     join_room => $room, $user
768     join message of $user joined the room $room
769     $user contains the user structure (see user_list).
770    
771     part_room => $room, $user
772     part message of $user who left the room $room
773     $user contains the user structure (see user_list).
774 root 1.9 =cut
775    
776     sub register {
777     my ($self, $type, $cb) = @_;
778    
779 root 1.13 $self->{proto}->register ($type, $cb);
780 root 1.9 }
781    
782 root 1.8 =back
783    
784     =head1 AUTHOR
785    
786 root 1.35 Marc Lehmann <schmorp@schmorp.de>
787 root 1.8 http://home.schmorp.de/
788    
789 root 1.6 =cut
790    
791 root 1.2 1;
792