ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-IRC-Server/Net/IRC/Server.pm
Revision: 1.1
Committed: Wed Jan 12 14:48:03 2005 UTC (21 years, 9 months ago) by elmex
Branch: MAIN
Log Message:
First checkin of Net::IRC::Server

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 sub send_msg {
62 my ($self, $client, @msg) = @_;
63 $self->{send_cb}->($client, $self->mk_msg (@msg));
64 }
65
66 sub mk_msg {
67 my ($self, $prefix, $command, $trail, @params) = @_;
68 my $msg = "";
69
70 $msg .= defined $prefix ? ":$prefix " : "";
71 $msg .= "$command";
72
73 map { $msg .= " $_" } @params;
74
75 $msg .= defined $trail ? " :$trail" : "";
76 $msg .= "\r\n";
77
78 return $msg;
79 }
80
81 sub mk_clpref {
82 my ($self, $client) = @_;
83 return $client->{nickname}."!".$client->{username}.'@'.$client->{hostname};
84 }
85
86 sub parse_irc_stream {
87 my ($self, $client, $data) = @_;
88
89 $self->{buffer} .= $data;
90
91 while ($self->{buffer} =~ s/^([^\r\n]*)\r?\n//) {
92 push @{$self->{msgs}}, $1;
93 }
94
95 map { $self->handle_irc_msg ($client, $self->parse_irc_msg ($_)) } @{$self->{msgs}};
96
97 $self->{msgs} = [];
98 }
99
100 sub handle_irc_msg {
101 my ($self, $cl, $msg) = @_;
102
103 return if not defined $msg;
104
105 my $c = $msg->{command};
106
107 my $r;
108 $r ||= $self->{cmd_cbs}->{uc $c}->($cl, $msg) if defined $self->{cmd_cbs}->{uc $c};
109 $r ||= $self->{cmd_cbs}->{"*"}->($cl, $msg) if defined $self->{cmd_cbs}->{"*"};
110
111 if ($r) {
112 warn "notihgn to do skip srv code\n";
113 return
114 }
115
116 if ($c eq "USER") {
117 $cl->{username} = $msg->{params}->[0];
118 $cl->{realname} = $msg->{params}->[3];
119
120 } elsif ($c eq "NICK") {
121 my $n = $msg->{params}->[0];
122 $self->change_nick ($n, $cl);
123 }
124
125 if (defined $cl->{username}
126 and defined $cl->{nickname}
127 and not $cl->{registered})
128 {
129 $self->send_msg ($cl, $self->{srv_prefix}, "001",
130 "Welcome to NET::IRCServer! "
131 . $self->mk_clpref ($cl), $cl->{nickname});
132 $cl->{registered} = 1;
133 }
134
135 if ($c eq "PASS") {
136 $cl->{password} = $msg->{params}->[0];
137
138 } elsif ($c eq "JOIN") {
139 my @chnls = split /,/, $msg->{params}->[0];
140 my @keys;
141 @keys = split /,/, $msg->{params}->[1] if defined $msg->{params}->[1];
142
143 $self->join_channel ($cl, $_, pop @keys) for @chnls;
144
145 } elsif ($c eq "PART") {
146 my @chnls = split /,/, $msg->{params}->[0];
147 $self->part_channel ($cl, $_, $msg->{params}->[1]) for @chnls;
148
149 } elsif ($c eq "NOTICE" or $c eq "PRIVMSG") {
150 $self->generic_msg ($cl, $msg->{params}->[0], $c, $msg->{params}->[1]);
151 }
152
153 }
154
155 =item part_channel ($client, $channel, $reason)
156
157 It will remove the C<$client> from the C<$channel> and will send a PART to all members of that channel.
158 The part C<$reason> may be undefined.
159
160 =cut
161 sub part_channel {
162 my ($self, $client, $chan, $reas) = @_;
163
164 $self->channel_brdcst ($self->mk_clpref ($client), $chan, "PART", $reas, $chan);
165 $self->remove_channel ($client, $chan);
166 }
167
168 =item join_channel ($client, $channel, $key)
169
170 =cut
171 sub join_channel {
172 my ($self, $client, $chan, $key) = @_;
173
174 if ($chan eq "0") {
175 # part all channels of $client
176 $self->part_channel ($client, $_) for keys %{$client->{channels}};
177 $self->remove_channel ($client, $_) for keys %{$client->{channels}};
178
179 } else {
180 $self->add_channel ($client, $chan);
181 $self->channel_brdcst ($self->mk_clpref ($client), $chan, "JOIN", $chan);
182 }
183 }
184
185 sub add_channel {
186 my ($self, $client, $channel) = @_;
187 $self->{channels}->{lc $channel}->{lc $client->{nickname}} = $client;
188 $client->{channels}->{lc $channel} = 1;
189 }
190
191 sub remove_channel {
192 my ($self, $client, $channel) = @_;
193 delete $self->{channels}->{lc $channel}->{lc $client->{nickname}};
194 delete $client->{channels}->{lc $channel};
195 }
196
197 sub channel_brdcst {
198 my ($self, $prefix, $channel, $command, @rmsg) = @_;
199 $self->channel_brdcst_exclude (undef, $prefix, $channel, $command, @rmsg);
200 }
201
202 sub channel_brdcst_exclude {
203 my ($self, $excl_cl, $prefix, $channel, $command, @rmsg) = @_;
204
205 for (values %{$self->{channels}->{lc $channel}}) {
206 if (not (defined $excl_cl) or $excl_cl != $_) {
207 $self->send_msg ($_, $prefix, uc $command, @rmsg);
208 }
209 }
210 }
211
212
213 sub generic_msg {
214 my ($self, $client, $target, $msgcmd, $msg) = @_;
215
216 my $pref = $self->mk_clpref ($client);
217
218 if (exists $self->{channels}->{lc $target}) {
219 $self->channel_brdcst_exclude ($client, $pref, $target, uc ($msgcmd), $msg, $target);
220 } else {
221 $self->send_msg ($self->{reg_nicks}->{lc $target}, $pref, uc ($msgcmd), $msg, $target);
222 }
223 }
224
225 sub check_nick_exists {
226 my ($self, $nick) = @_;
227 return 0;
228 }
229
230 sub change_nick {
231 my ($self, $nick, $client) = @_;
232
233 # check wether the nick to change to is already in use
234 if (exists $self->{reg_nicks}->{lc $nick}) {
235 $self->send_msg ($client, $self->{srv_prefix}, 433, "Nickname is already in use", $nick);
236 return;
237 }
238
239 my %ppltotell; # hash-list of people to send a nick-update
240
241 if (defined $client->{nickname}) {
242
243 # update channels the client is on
244 for my $c (keys %{$client->{channels}}) {
245
246 # search the people to tell this nick changed
247 for (values %{$self->{channels}->{lc $c}}) {
248 $ppltotell{$_->{nickname}} = $_;
249 }
250
251 # change the nickname in the channel lists
252 delete $self->{channels}->{lc $c}->{$client->{nickname}};
253 $self->{channels}->{lc $c}->{$nick} = $client;
254 }
255 }
256
257 if ($client->{registered}) { # only tell if this client is registered
258
259 # send us and others that the nick changed
260 $ppltotell{$client->{nickname}} = $client;
261 my $oldprfx = $self->mk_clpref ($client);
262 $self->send_msg ($_, $oldprfx, "NICK", undef, $nick) for values %ppltotell;
263 }
264
265 # now update the client data
266 delete $self->{reg_nicks}->{lc $client->{nickname}};
267 $client->{nickname} = $nick;
268 $self->{reg_nicks}->{$nick} = $client;
269
270 return 1;
271 }
272
273 our $cmd;
274 our $pref;
275 our $t;
276 our @a;
277
278 sub parse_irc_msg {
279 my ($self, $msg) = @_;
280
281 $cmd = "";
282 $pref = "";
283 $t = "";
284 @a = ();
285
286 my $p = $msg =~
287 m/^( : ([^ ]+)(?{$pref = $^N}) [ ] )? # the prefix
288
289 ([A-Za-z]+|\d{3})(?{$cmd = $^N}) # the command
290
291 (
292 # now either 14 params and a trailing with optional ':'
293 (?{@a = ()}) (
294 ( [ ] ([^ :\r\n\0][^ \r\n\0]*)(?{push @a, $^N}) ){14} # params
295
296 ( [ ]:? ([^\r\n\0]*) (?{$t = $^N}) )? # trailing
297 )
298
299 | # OR: 0 to 13 params and trailing with required ':'
300
301 (?{@a = ()}) (
302 ( [ ] ([^ :\r\n\0][^ \r\n\0]*)(?{push @a, $^N}) ){0,13} # 0 to 13 params
303
304 ( [ ]: ([^\r\n\0]*)(?{$t = $^N}) )? # trailing
305 )
306
307 )
308 $/x;
309
310 my $m = { prefix => $pref, command => $cmd, params => \@a, trailing => $t };
311
312 push @{$m->{params}}, $m->{trailing} if defined $m->{trailing};
313 return $p ? $m : undef;
314 }
315
316 =back
317
318 =cut
319
320 1;