ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-IRC-Server/Net/IRC/Server.pm
Revision: 1.3
Committed: Thu Jan 13 20:36:38 2005 UTC (21 years, 8 months ago) by elmex
Branch: MAIN
Changes since 1.2: +3 -1 lines
Log Message:
further improvements, and documentation

File Contents

# User Rev Content
1 elmex 1.1 =head1 NET::IRC::Server
2    
3     NET::IRCServer - a module to handle the IRC Protocol and implements a lightweight IRC Server (without server-to-server support)
4    
5     =head1 SYNOPSIS
6    
7     use NET::IRC::Server;
8    
9     $server = NET::IRC::Server->new (srv_prefix => "mein.irc.server.funtzt.net");
10    
11     $s->set_send_cb (sub { ... });
12    
13     $s->parse_irc_stream (...)
14    
15     =cut
16    
17     package Net::IRC::Server;
18     use Data::Dumper;
19     use strict;
20     use warnings;
21    
22     =head1 METHODS
23    
24     =over 4
25    
26     =cut
27    
28     =item new (srv_prefix => $server_prefix)
29    
30     srv_prefix - the prefix the server will use for irc commands from itself
31    
32     =cut
33     sub new {
34     my $class = shift;
35     my $self = { @_ };
36     bless $self, $class;
37     return $self;
38     }
39    
40     =item set_send_cb ($sub)
41    
42     The code-ref in the C<$sub> will be called everythime the server wants to send data to a client.
43     The callback will be called with following arguments:
44    
45     $sub->($client, $data)
46    
47     $client - the client datastructure you give C<parse_irc_stream ()>
48     $data - the raw data to send out, for example on a socket
49    
50     =cut
51     sub set_send_cb {
52     my ($self, $cb) = @_;
53     $self->{send_cb} = $cb;
54     }
55    
56     sub set_cmd_cb {
57     my ($self, $cmd, $cb) = @_;
58     $self->{cmd_cbs}->{uc $cmd} = $cb;
59     }
60    
61 elmex 1.2 =item send_srv_msg ($client, @msg)
62    
63     Sends a irc-message to C<$client> with the server-prefix.
64     @msg should look like: (<command>, <trailing>, <param-1>, ..., <param-n>)
65    
66     =cut
67     sub send_srv_msg {
68     my ($self, $client, @msg) = @_;
69     $self->send_msg ($client, $self->{srv_prefix}, @msg);
70     }
71    
72     =item send_msg ($client, @msg)
73    
74     Sends a irc-message to C<$client>.
75     @msg should look like: (<prefix>, <command>, <trailing>, <param-1>, ..., <param-n>)
76    
77     =cut
78 elmex 1.1 sub send_msg {
79     my ($self, $client, @msg) = @_;
80     $self->{send_cb}->($client, $self->mk_msg (@msg));
81     }
82    
83 elmex 1.2
84     =item send_nameslist ($client, $channel)
85    
86     This sends the names-list of C<$channel> to the C<$client>.
87    
88     =cut
89     sub send_nameslist {
90     my ($self, $client, $channel) = @_;
91    
92     # TODO: Modes for users! Look here too:
93     my @names = map { $_->{nickname} } values %{$self->{channels}->{lc $channel}};
94    
95     # TODO: Look for channel modes!
96    
97     my $i = 0;
98     my @part;
99    
100     while (@names) {
101     $i++;
102     push @part, shift @names;
103    
104     if ($i % 10 == 0) {
105     $self->send_srv_msg ($client, "353", join (' ', @part), "=", $channel);
106     @part = ();
107     }
108     }
109    
110     if (@part) {
111     $self->send_srv_msg ($client, "353", join (' ', @part), $client->{nickname}, "=", $channel);
112     }
113    
114     $self->send_srv_msg ($client, "366", "End of NAMES list", $client->{nickname}, $channel);
115     }
116    
117 elmex 1.1 sub mk_msg {
118     my ($self, $prefix, $command, $trail, @params) = @_;
119     my $msg = "";
120    
121     $msg .= defined $prefix ? ":$prefix " : "";
122     $msg .= "$command";
123    
124     map { $msg .= " $_" } @params;
125    
126     $msg .= defined $trail ? " :$trail" : "";
127     $msg .= "\r\n";
128    
129     return $msg;
130     }
131    
132     sub mk_clpref {
133     my ($self, $client) = @_;
134     return $client->{nickname}."!".$client->{username}.'@'.$client->{hostname};
135     }
136    
137     sub parse_irc_stream {
138     my ($self, $client, $data) = @_;
139    
140     $self->{buffer} .= $data;
141    
142     while ($self->{buffer} =~ s/^([^\r\n]*)\r?\n//) {
143     push @{$self->{msgs}}, $1;
144     }
145    
146     map { $self->handle_irc_msg ($client, $self->parse_irc_msg ($_)) } @{$self->{msgs}};
147    
148     $self->{msgs} = [];
149     }
150    
151     sub handle_irc_msg {
152     my ($self, $cl, $msg) = @_;
153    
154     return if not defined $msg;
155    
156 elmex 1.2 my $c = uc $msg->{command};
157 elmex 1.1
158     my $r;
159 elmex 1.2 $r ||= $self->{cmd_cbs}->{$c}->($cl, $msg) if defined $self->{cmd_cbs}->{$c};
160 elmex 1.1 $r ||= $self->{cmd_cbs}->{"*"}->($cl, $msg) if defined $self->{cmd_cbs}->{"*"};
161    
162     if ($r) {
163     warn "notihgn to do skip srv code\n";
164     return
165     }
166    
167     if ($c eq "USER") {
168     $cl->{username} = $msg->{params}->[0];
169     $cl->{realname} = $msg->{params}->[3];
170    
171     } elsif ($c eq "NICK") {
172     my $n = $msg->{params}->[0];
173     $self->change_nick ($n, $cl);
174     }
175    
176     if (defined $cl->{username}
177     and defined $cl->{nickname}
178     and not $cl->{registered})
179     {
180     $self->send_msg ($cl, $self->{srv_prefix}, "001",
181     "Welcome to NET::IRCServer! "
182     . $self->mk_clpref ($cl), $cl->{nickname});
183     $cl->{registered} = 1;
184     }
185    
186     if ($c eq "PASS") {
187     $cl->{password} = $msg->{params}->[0];
188    
189     } elsif ($c eq "JOIN") {
190     my @chnls = split /,/, $msg->{params}->[0];
191     my @keys;
192     @keys = split /,/, $msg->{params}->[1] if defined $msg->{params}->[1];
193    
194     $self->join_channel ($cl, $_, pop @keys) for @chnls;
195    
196     } elsif ($c eq "PART") {
197     my @chnls = split /,/, $msg->{params}->[0];
198     $self->part_channel ($cl, $_, $msg->{params}->[1]) for @chnls;
199    
200     } elsif ($c eq "NOTICE" or $c eq "PRIVMSG") {
201     $self->generic_msg ($cl, $msg->{params}->[0], $c, $msg->{params}->[1]);
202 elmex 1.2
203     } elsif ($c eq "QUIT") {
204     $self->quit ($cl, $msg->{params}->[0]);
205    
206     } elsif ($c eq "NAMES") {
207    
208     if (defined $msg->{params}->[1]) {
209     $self->send_srv_msg ($cl,"402", "No such server", $msg->{params}->[1]);
210    
211     } elsif (defined $msg->{params}->[0]) {
212     my @chans = split /,/, $msg->{params}->[0];
213     $self->send_nameslist ($cl, $_) for @chans;
214    
215     } else {
216     $self->send_nameslist ($cl, $_) for keys %{$self->{channels}};
217     }
218    
219     } elsif ($c eq "PING") {
220    
221     if (defined $msg->{params}->[1]) {
222     $self->send_srv_msg ($cl,"402", "No such server", $msg->{params}->[1]);
223     } else {
224     $self->send_srv_msg ($cl, "PONG", $msg->{params}->[0]);
225     }
226    
227     } else {
228     $self->send_srv_msg ($cl, "421", "Unknown command", $c)
229     if $c ne "USER" and $c ne "NICK"; # handled above
230 elmex 1.1 }
231    
232     }
233    
234 elmex 1.2 =item quit ($client, $quitmsg)
235    
236     It will remove the C<$client> from all lists in the server, and broadcasts the
237     C<$quitmsg> to all users on the channels the C<$client> was.
238    
239     =cut
240     sub quit {
241     my ($self, $client, $quitmsg) = @_;
242    
243     $self->channel_brdcst (
244     $self->mk_clpref ($client),
245     $_,
246     "QUIT",
247     $quitmsg) for keys %{$client->{channels}};
248    
249     $self->remove_channel ($client, $_) for keys %{$client->{channels}};
250     }
251    
252 elmex 1.1 =item part_channel ($client, $channel, $reason)
253    
254 elmex 1.3 It will remove the C<$client> from the C<$channel> and will broadcast a PART to all members of that channel.
255 elmex 1.1 The part C<$reason> may be undefined.
256    
257     =cut
258     sub part_channel {
259     my ($self, $client, $chan, $reas) = @_;
260    
261     $self->channel_brdcst ($self->mk_clpref ($client), $chan, "PART", $reas, $chan);
262     $self->remove_channel ($client, $chan);
263     }
264    
265     =item join_channel ($client, $channel, $key)
266    
267 elmex 1.3 It will add the C<$client> to the C<$channel> and will broadcast a JOIN to all members of that channel.
268    
269 elmex 1.1 =cut
270     sub join_channel {
271     my ($self, $client, $chan, $key) = @_;
272    
273     if ($chan eq "0") {
274     # part all channels of $client
275     $self->part_channel ($client, $_) for keys %{$client->{channels}};
276     $self->remove_channel ($client, $_) for keys %{$client->{channels}};
277    
278     } else {
279     $self->add_channel ($client, $chan);
280     $self->channel_brdcst ($self->mk_clpref ($client), $chan, "JOIN", $chan);
281 elmex 1.2 $self->send_nameslist ($client, $chan);
282 elmex 1.1 }
283     }
284    
285     sub add_channel {
286     my ($self, $client, $channel) = @_;
287     $self->{channels}->{lc $channel}->{lc $client->{nickname}} = $client;
288     $client->{channels}->{lc $channel} = 1;
289     }
290    
291     sub remove_channel {
292     my ($self, $client, $channel) = @_;
293     delete $self->{channels}->{lc $channel}->{lc $client->{nickname}};
294     delete $client->{channels}->{lc $channel};
295     }
296    
297     sub channel_brdcst {
298     my ($self, $prefix, $channel, $command, @rmsg) = @_;
299     $self->channel_brdcst_exclude (undef, $prefix, $channel, $command, @rmsg);
300     }
301    
302     sub channel_brdcst_exclude {
303     my ($self, $excl_cl, $prefix, $channel, $command, @rmsg) = @_;
304    
305     for (values %{$self->{channels}->{lc $channel}}) {
306 elmex 1.2
307 elmex 1.1 if (not (defined $excl_cl) or $excl_cl != $_) {
308 elmex 1.2
309 elmex 1.1 $self->send_msg ($_, $prefix, uc $command, @rmsg);
310     }
311     }
312     }
313    
314    
315     sub generic_msg {
316     my ($self, $client, $target, $msgcmd, $msg) = @_;
317    
318     my $pref = $self->mk_clpref ($client);
319    
320     if (exists $self->{channels}->{lc $target}) {
321     $self->channel_brdcst_exclude ($client, $pref, $target, uc ($msgcmd), $msg, $target);
322     } else {
323     $self->send_msg ($self->{reg_nicks}->{lc $target}, $pref, uc ($msgcmd), $msg, $target);
324     }
325     }
326    
327     sub check_nick_exists {
328     my ($self, $nick) = @_;
329     return 0;
330     }
331    
332     sub change_nick {
333     my ($self, $nick, $client) = @_;
334    
335     # check wether the nick to change to is already in use
336     if (exists $self->{reg_nicks}->{lc $nick}) {
337     $self->send_msg ($client, $self->{srv_prefix}, 433, "Nickname is already in use", $nick);
338     return;
339     }
340    
341     my %ppltotell; # hash-list of people to send a nick-update
342    
343     if (defined $client->{nickname}) {
344    
345     # update channels the client is on
346     for my $c (keys %{$client->{channels}}) {
347    
348     # search the people to tell this nick changed
349     for (values %{$self->{channels}->{lc $c}}) {
350     $ppltotell{$_->{nickname}} = $_;
351     }
352    
353     # change the nickname in the channel lists
354     delete $self->{channels}->{lc $c}->{$client->{nickname}};
355     $self->{channels}->{lc $c}->{$nick} = $client;
356     }
357     }
358    
359     if ($client->{registered}) { # only tell if this client is registered
360    
361     # send us and others that the nick changed
362     $ppltotell{$client->{nickname}} = $client;
363     my $oldprfx = $self->mk_clpref ($client);
364     $self->send_msg ($_, $oldprfx, "NICK", undef, $nick) for values %ppltotell;
365     }
366    
367     # now update the client data
368     delete $self->{reg_nicks}->{lc $client->{nickname}};
369     $client->{nickname} = $nick;
370     $self->{reg_nicks}->{$nick} = $client;
371    
372     return 1;
373     }
374    
375     sub parse_irc_msg {
376     my ($self, $msg) = @_;
377    
378 elmex 1.2 my $cmd;
379     my $pref;
380     my $t;
381     my @a;
382    
383     my $p = $msg =~ s/^(:([^ ]+)[ ])?([A-Za-z]+|\d{3})//;
384     $pref = $2;
385     $cmd = $3;
386 elmex 1.1
387 elmex 1.2 my $i = 0;
388    
389     while ($msg =~ s/^[ ]([^ :\r\n\0][^ \r\n\0]*)//) {
390 elmex 1.1
391 elmex 1.2 push @a, $1 if defined $1;
392     if (++$i > 13) { last; }
393     }
394    
395     if ($i == 14) {
396    
397     if ($msg =~ s/^[ ]:?([^\r\n\0]*)//) {
398     $t = $1 if $1 ne "";
399     }
400    
401     } else {
402    
403     if ($msg =~ s/^[ ]:([^\r\n\0]*)//) {
404     $t = $1 if $1 ne "";
405     }
406     }
407    
408     push @a, $t if defined $t;
409 elmex 1.1
410     my $m = { prefix => $pref, command => $cmd, params => \@a, trailing => $t };
411     return $p ? $m : undef;
412     }
413    
414     =back
415    
416     =cut
417    
418     1;