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

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 broadcast 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 It will add the C<$client> to the C<$channel> and will broadcast a JOIN to all members of that channel.
268
269 =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 $self->send_nameslist ($client, $chan);
282 }
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
307 if (not (defined $excl_cl) or $excl_cl != $_) {
308
309 $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 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
387 my $i = 0;
388
389 while ($msg =~ s/^[ ]([^ :\r\n\0][^ \r\n\0]*)//) {
390
391 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
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;