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, 9 months ago) by elmex
Branch: MAIN
Changes since 1.1: +131 -35 lines
Log Message:
Some new commands.

File Contents

# Content
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 =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 sub send_msg {
79 my ($self, $client, @msg) = @_;
80 $self->{send_cb}->($client, $self->mk_msg (@msg));
81 }
82
83
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 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 my $c = uc $msg->{command};
157
158 my $r;
159 $r ||= $self->{cmd_cbs}->{$c}->($cl, $msg) if defined $self->{cmd_cbs}->{$c};
160 $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
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 }
231
232 }
233
234 =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 =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 $self->send_nameslist ($client, $chan);
280 }
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
305 if (not (defined $excl_cl) or $excl_cl != $_) {
306
307 $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 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
385 my $i = 0;
386
387 while ($msg =~ s/^[ ]([^ :\r\n\0][^ \r\n\0]*)//) {
388
389 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
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;