File Coverage

blib/lib/Sirc/Chantrack.pm
Criterion Covered Total %
statement 25 254 9.8
branch 0 98 0.0
condition 0 20 0.0
subroutine 10 30 33.3
pod 0 8 0.0
total 35 410 8.5


line stmt bran cond sub pod time code
1             # $Id: Chantrack.pm,v 1.13 2000-06-30 23:52:24-04 roderick Exp $
2             #
3             # Copyright (c) 1997-2000 Roderick Schertler. All rights reserved.
4             # This program is free software; you can redistribute it and/or modify
5             # it under the same terms as Perl itself.
6             #
7             # Documentation is at the __END__.
8              
9             # XXX
10             # - disconnect doesn't run when changing servers, hook a connection
11             # message additionally?
12             # - track voice status
13             # - sirc still outputs a message for users not on irc (code 401)
14              
15 1     1   5 use strict;
  1         2  
  1         41  
16              
17             package Sirc::Chantrack;
18              
19 1     1   531 use Sirc::LckHash ();
  1         2  
  1         28  
20 1         169 use Sirc::Util qw(add_hook_type addhook arg_count_error eval_verbose ieq
21             plausible_nick run_hook sl tell_error tell_question
22 1     1   563 xtell);
  1         2  
23 1     1   6 use Exporter ();
  1         2  
  1         37  
24              
25 1         377 use vars qw($VERSION @ISA @EXPORT_OK
26             %Channel %Chan_limit %Chan_op %Chan_user %Chan_voice
27 1     1   5 %Nick @Pend_userhost %User_chan %Userhost $Debug $Pkg);
  1         2  
28              
29             $VERSION = do{my@r=q$Revision: 1.13 $=~/\d+/g;sprintf '%d.'.'%03d'x$#r,@r};
30             $VERSION .= '-l' if q$Locker: $ =~ /: \S/;
31              
32             @ISA = qw(Exporter);
33             @EXPORT_OK = qw(%Channel %Chan_limit %Chan_op %Chan_user %Chan_voice
34             %Nick %User_chan chantrack_check chantrack_show);
35              
36             $Debug = 0;
37             $Pkg = __PACKAGE__;
38              
39             tie %Channel => 'Sirc::LckHash';
40             tie %Chan_limit => 'Sirc::LckHash';
41             tie %Chan_op => 'Sirc::LckHash';
42             tie %Chan_user => 'Sirc::LckHash';
43             tie %Chan_voice => 'Sirc::LckHash';
44             tie %Nick => 'Sirc::LckHash';
45             tie %Userhost => 'Sirc::LckHash';
46             tie %User_chan => 'Sirc::LckHash';
47              
48             add_hook_type '+op';
49             add_hook_type '-op';
50             add_hook_type '+voice';
51             add_hook_type '-voice';
52             add_hook_type 'drop-user';
53             add_hook_type 'limit';
54              
55             my $Old_w;
56 1     1   3 BEGIN { $Old_w = $^W; $^W = 1 }
  1         2367  
57             { my @dummy = ($::user, $::host) }
58              
59             sub debug {
60 0 0   0 0   xtell "debug " . join '', @_
61             if $Debug;
62             }
63              
64             sub userhost_split {
65 0 0   0 0   unless (@_ == 1) {
66 0           arg_count_error undef, 1, @_;
67 0           return ();
68             }
69 0 0         return $_[0] =~ /^(.*?)\@(.*)/s ? ($1, $2) : ();
70             }
71              
72             sub add_user_channel {
73 0 0   0 0   unless (@_ == 4) {
74 0           arg_count_error undef, 4, @_;
75 0           return;
76             }
77 0           my ($reason, $n, $c, $uh) = @_;
78              
79 0           debug "$reason add $n to $c uh $uh";
80 0 0         if (ieq $n, $::nick) {
81 0           $Channel{$c} = 1;
82 0 0         $Chan_limit{$c} = $::limit{lc $c}
83             if exists $::limit{lc $c};
84 0           tie %{ $Chan_user{$c} }, 'Sirc::LckHash';
  0            
85 0           tie %{ $Chan_op{$c} }, 'Sirc::LckHash';
  0            
86 0           tie %{ $Chan_voice{$c} }, 'Sirc::LckHash';
  0            
87             }
88 0 0         if (!exists $Nick{$n}) {
89 0           $Nick{$n} = $n;
90 0           tie %{ $User_chan{$n} }, 'Sirc::LckHash';
  0            
91             }
92 0 0         $Userhost{$n} = [userhost_split $uh]
93             if defined $uh;
94 0           $Chan_user{$c}{$n} = 1;
95              
96 0           $User_chan{$n}{$c} = 1;
97             }
98              
99             sub drop_user {
100 0 0   0 0   unless (@_ == 2) {
101 0           arg_count_error undef, 2, @_;
102 0           return;
103             }
104 0           my ($reason, $n) = @_;
105              
106 0 0         if (ieq $n, $::nick) {
107 0           debug "$reason drop everything";
108 0           %Channel = %Chan_limit = %Chan_user = %Chan_op = %Chan_voice
109             = %Nick = %Userhost = %User_chan = ();
110             }
111             else {
112 0           debug "$reason drop $n";
113 0           my @c = keys %{ $User_chan{$n} };
  0            
114 0           run_hook 'drop-user', $n, @c;
115 0           for my $c (@c) {
116 0           delete $Chan_user{$c}{$n};
117 0           delete $Chan_op{$c}{$n};
118 0           delete $Chan_voice{$c}{$n};
119             }
120 0           delete $Nick{$n};
121 0           delete $Userhost{$n};
122 0           delete $User_chan{$n};
123             }
124             }
125              
126             sub drop_user_channel {
127 0 0   0 0   unless (@_ == 3) {
128 0           arg_count_error undef, 3, @_;
129 0           return;
130             }
131 0           my ($reason, $n, $c) = @_;
132              
133 0           delete $Chan_user{$c}{$n};
134 0           delete $Chan_op{$c}{$n};
135 0           delete $Chan_voice{$c}{$n};
136 0           delete $User_chan{$n}{$c};
137             # XXX bug, scalar tied hash is always 0
138 0 0         if (!keys %{ $User_chan{$n} }) {
  0            
139             # That's the only channel this user was on, drop her entirely.
140 0           debug "$reason drop $n from $c";
141 0           delete $Nick{$n};
142 0           delete $Userhost{$n};
143 0           delete $User_chan{$n};
144             }
145             else {
146 0           debug "$reason drop $n from $c (partial)";
147             }
148              
149 0 0         if (ieq $n, $::nick) {
150 0           debug "$reason drop channel $c";
151 0           for my $tn (keys %{ $Chan_user{$c} }) {
  0            
152 0           drop_user_channel("self-$reason", $tn, $c);
153             }
154 0           delete $Channel{$c};
155 0           delete $Chan_limit{$c};
156 0           delete $Chan_user{$c};
157 0           delete $Chan_op{$c};
158 0           delete $Chan_voice{$c};
159             }
160             }
161              
162             sub main::hook_chantrack_join {
163 0     0     my ($c) = @_;
164 0           my $uh = "$::user\@$::host";
165 0           add_user_channel 'join', $::who, $c, $uh;
166             }
167             addhook 'join', 'chantrack_join';
168              
169             sub main::hook_chantrack_leave {
170 0     0     my ($c) = @_;
171 0           drop_user_channel 'leave', $::who, $c;
172             }
173             addhook 'leave', 'chantrack_leave';
174              
175             sub main::hook_chantrack_kick {
176 0     0     my ($n, $c) = @_;
177 0           drop_user_channel 'kick', $n, $c;
178             }
179             addhook 'kick', 'chantrack_kick';
180              
181             sub main::hook_chantrack_signoff {
182 0     0     drop_user 'signoff', $::who;
183             }
184             addhook 'signoff', 'chantrack_signoff';
185              
186             sub main::hook_chantrack_disconnect {
187 0     0     drop_user 'disconnect', $::nick;
188             }
189             addhook 'disconnect', 'chantrack_disconnect';
190              
191             sub main::hook_chantrack_nick {
192 0     0     my ($new_nick) = @_;
193 0           delete $Nick{$::who};
194 0           $Nick{$new_nick} = $new_nick;
195 0           $Userhost{$new_nick} = delete $Userhost{$::who};
196 0           $User_chan{$new_nick} = delete $User_chan{$::who};
197 0           for my $c (keys %{ $User_chan{$new_nick} }) {
  0            
198 0           debug "rename $::who -> $new_nick on $c";
199 0           $Chan_user{$c}{$new_nick} = delete $Chan_user{$c}{$::who};
200 0 0         if (exists $Chan_op{$c}{$::who}) {
201 0           debug "op rename $::who -> $new_nick on $c";
202 0           $Chan_op{$c}{$new_nick} = delete $Chan_op{$c}{$::who};
203             }
204 0 0         if (exists $Chan_voice{$c}{$::who}) {
205 0           debug "voice rename $::who -> $new_nick on $c";
206 0           $Chan_voice{$c}{$new_nick} = delete $Chan_voice{$c}{$::who};
207             }
208             }
209             }
210             addhook 'nick', 'chantrack_nick';
211              
212             sub main::hook_chantrack_mode {
213 0     0     my ($chan, $rest) = @_;
214 0           my ($op, @arg) = split ' ', $rest;
215              
216 0 0         return unless $chan =~ /^[\#&]/;
217              
218 0           my ($char, $add);
219 0           while ($op =~ s/^([-+])?(.)//) {
220 0 0         if (defined $1) {
221 0           $char = $1;
222 0           $add = ($char eq '+');
223             }
224 0           my $type = $2;
225 0 0 0       my $arg =
    0          
226             ($type eq 'l' && $add)
227             ? shift(@arg)
228             : $type =~ /[bkov]/
229             ? shift(@arg)
230             : '';
231              
232 0           debug "mode $char$type arg $arg";
233              
234 0 0         if ($type eq 'o') {
    0          
    0          
235 0 0 0       if ($add && !$Chan_op{$chan}{$arg}) {
    0 0        
236 0           debug "mode op add $arg on $chan";
237 0           $Chan_op{$chan}{$arg} = 1;
238 0           run_hook '+op', $chan, $arg;
239             }
240             elsif (!$add && $Chan_op{$chan}{$arg}) {
241 0           debug "mode op drop $arg on $chan";
242 0           delete $Chan_op{$chan}{$arg};
243 0           run_hook '-op', $chan, $arg;
244             }
245             }
246              
247             elsif ($type eq 'v') {
248 0 0 0       if ($add && !$Chan_voice{$chan}{$arg}) {
    0 0        
249 0           debug "mode voice add $arg on $chan";
250 0           $Chan_voice{$chan}{$arg} = 1;
251 0           run_hook '+voice', $chan, $arg;
252             }
253             elsif (!$add && $Chan_voice{$chan}{$arg}) {
254 0           debug "mode voice drop $arg on $chan";
255 0           delete $Chan_voice{$chan}{$arg};
256 0           run_hook '-voice', $chan, $arg;
257             }
258             }
259              
260             elsif ($type eq 'l') {
261 0           my $old = $Chan_limit{$chan};
262 0 0         if ($add) {
263 0           $Chan_limit{$chan} = $arg;
264             }
265             else {
266 0           delete $Chan_limit{$chan};
267             }
268 0           run_hook 'limit', $chan, $old, $Chan_limit{$chan};
269             }
270             }
271             }
272             addhook 'mode', 'chantrack_mode';
273              
274             sub main::hook_chantrack_324 {
275             # You can't use getarg here or you screw the handling sirc itself does.
276 0     0     my ($n, $c) = split ' ', $::args, 2;
277 0           main::hook_chantrack_mode $c, $::args;
278             }
279             addhook '324', 'chantrack_324';
280              
281             sub interpret_names_flag {
282 0 0   0 0   unless (@_ == 3) {
283 0           arg_count_error undef, 3, @_;
284 0           return;
285             }
286 0           my ($n, $c, $flag) = @_;
287              
288 0 0         if ($flag eq '@') {
289 0 0         if (!exists $Chan_op{$c}{$n}) {
290 0           $Chan_op{$c}{$n} = 1;
291 0           run_hook '+op', $c, $n;
292             }
293 0           return;
294             }
295              
296             # Not an op.
297 0 0         if (exists $Chan_op{$c}{$n}) {
298 0           delete $Chan_op{$c}{$n};
299 0           run_hook '-op', $c, $n;
300             }
301              
302 0 0         if ($flag eq '+') {
303 0 0         if (!exists $Chan_voice{$c}{$n}) {
304 0           $Chan_voice{$c}{$n} = 1;
305 0           run_hook '+voice', $c, $n;
306             }
307 0           return;
308             }
309              
310             # No voice.
311 0 0         if (exists $Chan_voice{$c}{$n}) {
312 0           delete $Chan_voice{$c}{$n};
313 0           run_hook '-voice', $c, $n;
314             }
315             }
316              
317             sub main::hook_chantrack_names {
318 0     0     my ($rest) = @_;
319 0           my ($x1, $x2, $chan, $list) = split ' ', $rest, 4;
320 0 0         return unless $Channel{$chan};
321 0           $list =~ s/^://;
322 0           for my $who (split ' ', $list) {
323 0 0         my $flag = ($who =~ s/^([+@])//) ? $1 : '';
324 0 0         if (!exists $Chan_user{$chan}{$who}) {
325 0           add_user_channel 'names', $who, $chan, undef;
326             }
327 0           interpret_names_flag $who, $chan, $flag;
328             }
329             }
330             addhook '353', 'chantrack_names';
331              
332 1     1   999 BEGIN { undef &main::userhost }
333             sub main::userhost {
334 0 0 0 0     unless (@_ == 2 || @_ == 3) {
335 0           arg_count_error undef, '2 or 3', @_;
336 0           return;
337             }
338 0           my ($n, $rhave, $rhavenot) = @_;
339 0           my (@full, @missing);
340              
341 0   0 0     $rhavenot ||= sub { tell_question "Cannot find $::who on irc" };
  0            
342 0 0         @full = ref $n ? @$n : ($n);
343              
344             # Process entries for which I already have the userhost info
345             # immediately.
346 0           for my $n (@full) {
347 0 0         if (!plausible_nick $n) {
    0          
348 0           tell_error "Invalid nick `$n'";
349             }
350             elsif ($Userhost{$n}) {
351 0           debug "userhost already have $n";
352 0           local ($::who, $::user, $::host) = ($n, @{ $Userhost{$n} });
  0            
353 0           eval_verbose 'immediate userhost', $rhave;
354             }
355             else {
356 0           debug "userhost needs $n";
357 0           push @missing, $n;
358             }
359             }
360              
361             # Queue USERHOST commands for the rest, 5 at a time.
362 0           while (@missing) {
363 0           my @this = splice @missing, 0, 5;
364 0           debug "doing userhost for @this";
365 0           sl "USERHOST @this";
366 0           push @Pend_userhost, [
367             $rhave,
368             $rhavenot,
369 0           { map { lc($_) => $_ } @this },
370             [ @this ],
371             ];
372             }
373             }
374              
375             sub main::hook_chantrack_userhost {
376 0     0     my ($x, @repl) = split ' ', $::args;
377 0           my (@parsed);
378              
379             # Since sirc's userhost parsing code is wrapped in the main raw_irc
380             # loop the only way I can override it is by setting $::skip.
381 0           $::skip = 1;
382              
383             # Parse the response.
384 0 0         $repl[0] =~ s/^://
385             if @repl;
386 0           for (@repl) {
387 0 0         next unless /^(\S+?)\*?=[+\-](.*?)\@(.*)/;
388 0           my ($n, $u, $h) = ($1, $2, $3);
389 0           push @parsed, [$n, $u, $h];
390             }
391              
392             # Check that the request at the head of the queue matches this
393             # response. Invalid nicks will not be present in the response, so
394             # just verify that nicks which are present were requested.
395 0 0         unless (@Pend_userhost) {
396 0           tell_error "USERHOST received without pending request";
397 0           return;
398             }
399 0           for my $rparsed (@parsed) {
400 0           my $n = $rparsed->[0];
401 0 0         if (!$Pend_userhost[0][2]{lc $n}) {
402 0           tell_error "USERHOST mismatch, nick $n not in request "
403 0           . "@{ $Pend_userhost[0][3] }";
404 0           return;
405             }
406             }
407              
408             # Break apart and remove the @Pend_userhost entry.
409 0           my ($rhave, $rhavenot, $rmap, $rlist) = @{ shift @Pend_userhost };
  0            
410              
411             # Loop through the nicks present in the request, saving the data (if
412             # appropriate) and calling the $rhave sub.
413 0           foreach (@parsed) {
414 0           my ($n, $u, $h) = @$_;
415 0           delete $rmap->{lc $n};
416 0 0         $Userhost{$n} = [$u, $h]
417             if exists $Nick{$n};
418 0           local ($::who, $::user, $::host) = ($n, $u, $h);
419 0           eval_verbose 'delayed userhost', $rhave;
420             }
421              
422             # Run $rhavenot for nicks still left in $rmap.
423 0           foreach (values %{ $rmap }) {
  0            
424 0           local ($::who, $::user, $::host) = ($_);
425 0           eval_verbose 'failed userhost', $rhavenot;
426             }
427             }
428             addhook '302', 'chantrack_userhost';
429              
430             sub chantrack_show {
431 0     0 0   for my $chan (sort keys %Chan_user) {
432 0           xtell "Channel $chan:";
433 0           for my $user (sort keys %{ $Chan_user{$chan} }) {
  0            
434 0           xtell sprintf ' %-12s %s',
435             ($Chan_op{$chan}{$user} ? '@'
436             : $Chan_voice{$chan}{$user} ? '+' : '') . $Nick{$user},
437 0 0         join '@', @{ $Userhost{$user} };
    0          
438             }
439             }
440             }
441              
442             sub chantrack_check {
443 0     0 0   my (@d, %d);
444              
445 0           @d = ();
446 0           for (qw(Channel Chan_op Chan_user Chan_voice)) {
447 1     1   8 push @d, [$_, join ' ', sort do { no strict 'refs'; keys %{ $_ } }];
  1         1  
  1         148  
  0            
  0            
  0            
448             }
449 0           while (@d > 1) {
450 0 0         $d[0][1] eq $d[1][1]
451             or print "Channel mismatch between $d[0][0] and $d[1][0]\n";
452 0           shift @d;
453             }
454             # XXX more checks
455              
456 0           require Data::Dumper;
457 0           my (@n, @v);
458 0           @n = qw(Channel Chan_limit Chan_op Chan_user Chan_voice
459             Nick Userhost User_chan);
460 0           for (@n) {
461 1     1   7 no strict 'refs';
  1         2  
  1         108  
462 0           push @v, \%$_;
463             }
464 0           print Data::Dumper->Dump(\@v, [map { "r$_" } @n]);
  0            
465             }
466              
467 1     1   47 BEGIN { $^W = $Old_w }
468              
469             1;
470              
471             __END__