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, 8 months ago) by elmex
Branch: MAIN
Log Message:
First checkin of Net::IRC::Server

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     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;