ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-IRC-Server/Net/IRC/Server.pm
Revision: 1.2
Committed: Thu Jan 13 20:25:18 2005 UTC (21 years, 8 months ago) by elmex
Branch: MAIN
Changes since 1.1: +131 -35 lines
Log Message:
Some new commands.

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     It will remove the C<$client> from the C<$channel> and will send a PART to all members of that channel.
255     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     =cut
268     sub join_channel {
269     my ($self, $client, $chan, $key) = @_;
270    
271     if ($chan eq "0") {
272     # part all channels of $client
273     $self->part_channel ($client, $_) for keys %{$client->{channels}};
274     $self->remove_channel ($client, $_) for keys %{$client->{channels}};
275    
276     } else {
277     $self->add_channel ($client, $chan);
278     $self->channel_brdcst ($self->mk_clpref ($client), $chan, "JOIN", $chan);
279 elmex 1.2 $self->send_nameslist ($client, $chan);
280 elmex 1.1 }
281     }
282    
283     sub add_channel {
284     my ($self, $client, $channel) = @_;
285     $self->{channels}->{lc $channel}->{lc $client->{nickname}} = $client;
286     $client->{channels}->{lc $channel} = 1;
287     }
288    
289     sub remove_channel {
290     my ($self, $client, $channel) = @_;
291     delete $self->{channels}->{lc $channel}->{lc $client->{nickname}};
292     delete $client->{channels}->{lc $channel};
293     }
294    
295     sub channel_brdcst {
296     my ($self, $prefix, $channel, $command, @rmsg) = @_;
297     $self->channel_brdcst_exclude (undef, $prefix, $channel, $command, @rmsg);
298     }
299    
300     sub channel_brdcst_exclude {
301     my ($self, $excl_cl, $prefix, $channel, $command, @rmsg) = @_;
302    
303     for (values %{$self->{channels}->{lc $channel}}) {
304 elmex 1.2
305 elmex 1.1 if (not (defined $excl_cl) or $excl_cl != $_) {
306 elmex 1.2
307 elmex 1.1 $self->send_msg ($_, $prefix, uc $command, @rmsg);
308     }
309     }
310     }
311    
312    
313     sub generic_msg {
314     my ($self, $client, $target, $msgcmd, $msg) = @_;
315    
316     my $pref = $self->mk_clpref ($client);
317    
318     if (exists $self->{channels}->{lc $target}) {
319     $self->channel_brdcst_exclude ($client, $pref, $target, uc ($msgcmd), $msg, $target);
320     } else {
321     $self->send_msg ($self->{reg_nicks}->{lc $target}, $pref, uc ($msgcmd), $msg, $target);
322     }
323     }
324    
325     sub check_nick_exists {
326     my ($self, $nick) = @_;
327     return 0;
328     }
329    
330     sub change_nick {
331     my ($self, $nick, $client) = @_;
332    
333     # check wether the nick to change to is already in use
334     if (exists $self->{reg_nicks}->{lc $nick}) {
335     $self->send_msg ($client, $self->{srv_prefix}, 433, "Nickname is already in use", $nick);
336     return;
337     }
338    
339     my %ppltotell; # hash-list of people to send a nick-update
340    
341     if (defined $client->{nickname}) {
342    
343     # update channels the client is on
344     for my $c (keys %{$client->{channels}}) {
345    
346     # search the people to tell this nick changed
347     for (values %{$self->{channels}->{lc $c}}) {
348     $ppltotell{$_->{nickname}} = $_;
349     }
350    
351     # change the nickname in the channel lists
352     delete $self->{channels}->{lc $c}->{$client->{nickname}};
353     $self->{channels}->{lc $c}->{$nick} = $client;
354     }
355     }
356    
357     if ($client->{registered}) { # only tell if this client is registered
358    
359     # send us and others that the nick changed
360     $ppltotell{$client->{nickname}} = $client;
361     my $oldprfx = $self->mk_clpref ($client);
362     $self->send_msg ($_, $oldprfx, "NICK", undef, $nick) for values %ppltotell;
363     }
364    
365     # now update the client data
366     delete $self->{reg_nicks}->{lc $client->{nickname}};
367     $client->{nickname} = $nick;
368     $self->{reg_nicks}->{$nick} = $client;
369    
370     return 1;
371     }
372    
373     sub parse_irc_msg {
374     my ($self, $msg) = @_;
375    
376 elmex 1.2 my $cmd;
377     my $pref;
378     my $t;
379     my @a;
380    
381     my $p = $msg =~ s/^(:([^ ]+)[ ])?([A-Za-z]+|\d{3})//;
382     $pref = $2;
383     $cmd = $3;
384 elmex 1.1
385 elmex 1.2 my $i = 0;
386    
387     while ($msg =~ s/^[ ]([^ :\r\n\0][^ \r\n\0]*)//) {
388 elmex 1.1
389 elmex 1.2 push @a, $1 if defined $1;
390     if (++$i > 13) { last; }
391     }
392    
393     if ($i == 14) {
394    
395     if ($msg =~ s/^[ ]:?([^\r\n\0]*)//) {
396     $t = $1 if $1 ne "";
397     }
398    
399     } else {
400    
401     if ($msg =~ s/^[ ]:([^\r\n\0]*)//) {
402     $t = $1 if $1 ne "";
403     }
404     }
405    
406     push @a, $t if defined $t;
407 elmex 1.1
408     my $m = { prefix => $pref, command => $cmd, params => \@a, trailing => $t };
409     return $p ? $m : undef;
410     }
411    
412     =back
413    
414     =cut
415    
416     1;