File Coverage

blib/lib/Sirc/Autoop.pm
Criterion Covered Total %
statement 18 123 14.6
branch 0 82 0.0
condition 0 33 0.0
subroutine 6 20 30.0
pod 0 6 0.0
total 24 264 9.0


line stmt bran cond sub pod time code
1             # $Id: Autoop.pm,v 1.11 2000-07-27 12:01:04-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             # XXX
8             # - track nick changes
9              
10 1     1   776 use strict;
  1         2  
  1         43  
11              
12             package Sirc::Autoop;
13              
14 1     1   4 use Exporter ();
  1         2  
  1         24  
15 1     1   675 use Sirc::Chantrack qw(%Chan_op %Chan_user %Chan_voice);
  1         4  
  1         155  
16 1     1   6 use Sirc::LckHash ();
  1         9  
  1         36  
17 1         134 use Sirc::Util qw(addcmd addhelp add_hook addhook ban_pattern
18             docommand doset have_ops have_ops_q ieq
19             mask_to_re notice optional_channel
20             settable_boolean settable_int tell_question
21 1     1   18 timer userhost xgetarg xtell);
  1         1  
22              
23 1     1   5 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK @Autoop %Autovoice);
  1         2  
  1         2897  
24              
25             $VERSION = do{my@r=q$Revision: 1.11 $=~/\d+/g;sprintf '%d.'.'%03d'x$#r,@r};
26             $VERSION .= '-l' if q$Locker: $ =~ /: \S/;
27              
28             @ISA = qw(Exporter);
29             @EXPORT = qw();
30             @EXPORT_OK = qw(@Autoop %Autovoice);
31              
32             # These variables are tied to /set options. The *_delay options can be
33             # either an integer or a code ref which will compute it. You have to
34             # use doset rather than /set to set them to a code ref.
35             my $Autoop = 1; # no autoops done if false
36             my $Autoop_delay = sub { 3 + 2 * int rand 4 }; # secs before trying to autoop
37             my $Autovoice = 1;
38             my $Autovoice_control = 1; # ops can control autovoice delay
39             my $Autovoice_delay = sub { 3 + 2 * int rand 4 }; # secs before autovoice
40             my $Autovoice_timeout = 60 * 60; # secs -v is sticky
41             my $Debug = 0;
42             my $Verbose = 0;
43              
44             settable_boolean 'autoop', \$Autoop;
45             settable_int 'autoop_delay', \$Autoop_delay, sub { $_[1] >= 0 };
46              
47             settable_boolean 'autovoice', \$Autovoice;
48             settable_boolean 'autovoice_control', \$Autovoice_control;
49             settable_int 'autovoice_delay', \$Autovoice_delay, sub { $_[1] >= 0 };
50             settable_int 'autovoice_timeout', \$Autovoice_timeout, sub { $_[1] >= 0 };
51              
52             settable_boolean 'autoop_debug', \$Debug;
53             settable_boolean 'autoop_verbose', \$Verbose;
54              
55             # @Autoop is a list of array references. The first element of each
56             # array is a pattern to match against the channel, the second is a
57             # pattern to match against the nick!user@host. The optional third is
58             # either 'o' or 'v' (defaulting to 'o') to tell which mode to give.
59             #
60             # There's no user-level interface for adding data to this (yet).
61             @Autoop = ();
62              
63             # This is a hash whose keys are are channel names. New keys should
64             # get undef as value. Everybody joining one of the listed channels
65             # will get a +v after $Autovoice_delay seconds. If somebody gets a -v
66             # then leaves and rejoins, though, they won't get another +v unless
67             # $Autovoice_timeout seconds have passed or somebody else gives them a
68             # +v.
69             #
70             # There's no user-level interface for adding data to this (yet).
71             #
72             # The implementation causes each value to be a hashref pointing to
73             # another hash. The keys of the second level hashes are ban_pattern
74             # patterns converted to REs indicating the people who are in sticky -v
75             # mode, the value for each is the time at which it got -v. The second
76             # level hash will only be present if it has keys, as the last key is
77             # removed the $Autovoice{$channel} entry is undeffed.
78             tie %Autovoice, 'Sirc::LckHash';
79              
80             sub debug {
81 0 0   0 0   xtell 'autoop debug ' . join '', @_
82             if $Debug;
83             }
84              
85             sub verbose {
86 0 0 0 0 0   xtell join '', @_
87             if $Verbose || $Debug;
88             }
89              
90             sub autoop_match {
91 0     0 0   my ($channel, $nuh) = @_;
92              
93 0           debug "autoop_match @_";
94 0           for (@Autoop) {
95 0           my ($channel_pat, $nuh_pat, $type) = @$_;
96              
97 0 0         $type = 'o' if !defined $type;
98 0 0 0       if ($type ne 'o' && $type ne 'v') {
99 0           tell_question "Invalid autoop type `$type'"
100             . " for /$channel_pat/ /$nuh_pat/";
101 0           next;
102             }
103              
104 0           my $one = $channel =~ /$channel_pat/i;
105 0           my $two = $nuh =~ /$nuh_pat/i;
106 0           debug "channel/user $one/$two on $channel_pat/$nuh_pat";
107 0 0 0       if ($one && $two) {
108 0           return $type;
109             }
110             }
111 0           return 0;
112             }
113              
114             sub sticky_devoice {
115 0     0 0   my ($c, $n, $uh) = @_;
116 0           my ($h, $s, $expire);
117              
118 0           $h = $Autovoice{$c};
119 0 0         return 0 unless $h;
120              
121 0           $expire = time - $Autovoice_timeout;
122 0           $s = "$n!$uh";
123 0           for my $pat (keys %$h) {
124 0 0         if ($h->{$pat} < $expire) {
125 0           delete $h->{$pat};
126             # undef the %Autovoice value when the last member drops.
127 0 0         if (!%$h) {
128 0           undef $Autovoice{$c};
129 0           return 0;
130             }
131             }
132             else {
133 0 0         return 1 if $s =~ /$pat/;
134             }
135             }
136 0           return 0;
137             }
138              
139             sub autoop_do {
140 0     0 0   my ($this_channel, $this_nick, $this_userhost, $type) = @_;
141 0           my ($mode);
142              
143 0           debug "autoop_do @_";
144              
145 0           $mode = $type;
146 0 0         $mode = 'v' if $mode eq 'autovoice';
147              
148             # Don't +v or +o for ops.
149 0 0 0       if ($Chan_op{$this_channel}{$this_nick}) {
    0 0        
    0 0        
    0          
    0          
150 0           debug "autoop_do skip $this_channel/$this_nick/$type opped";
151             }
152              
153             # Don't +v people who got it already.
154             elsif ($mode eq 'v' && $Chan_voice{$this_channel}{$this_nick}) {
155 0           debug "autoop_do skip $this_channel/$this_nick/$type voiced";
156             }
157              
158             # Don't +v if we're not moderated.
159             elsif ($mode eq 'v' && $::mode{lc $this_channel} !~ /m/) {
160 0           debug "autoop_do skip $this_channel/$this_nick/$type not +m";
161             }
162              
163             # Don't autovoice people who have a sticky -v.
164             elsif ($type eq 'autovoice'
165             && sticky_devoice $this_channel, $this_nick, $this_userhost) {
166 0           debug "autoop_do skip $this_channel/$this_nick/$type sticky -v";
167             }
168              
169             # Don't bother if she left already
170             elsif (!$Chan_user{$this_channel}{$this_nick}) {
171 0           debug "autoop_do skip $this_channel/$this_nick/$type gone";
172             }
173              
174             else {
175 0           debug "autoop_do op $this_channel/$this_nick";
176 0           docommand "mode $this_channel +$mode $this_nick\n";
177             }
178             };
179              
180             sub autoop_try {
181 0     0 0   my ($this_channel, $this_nick, $this_userhost, $immediate) = @_;
182 0           my (@base_arg);
183              
184 0           debug "autoop_try @_";
185 0 0         have_ops $this_channel or return;
186 0 0         return if ieq $this_nick, $::nick;
187 0           @base_arg = ($this_channel, $this_nick, $this_userhost);
188              
189 0 0 0       if ($Autoop && (my $type = autoop_match $this_channel,
190             "$this_nick!$this_userhost")) {
191 0 0         my $delay = $immediate
    0          
192             ? 0
193             : ref($Autoop_delay) eq 'CODE'
194             ? &$Autoop_delay(@base_arg)
195             : $Autoop_delay;
196 0 0 0       verbose "Queueing +$type for $this_nick on $this_channel in $delay"
197             if $delay > 0 || $Debug;
198 0     0     timer $delay, sub { autoop_do @base_arg, $type };
  0            
199             }
200              
201 0 0 0       if ($Autovoice && exists $Autovoice{$this_channel}) {
202 0 0         my $delay = $immediate
    0          
203             ? 0
204             : ref($Autovoice_delay) eq 'CODE'
205             ? &$Autovoice_delay(@base_arg)
206             : $Autovoice_delay;
207 0 0 0       verbose "Queueing autovoice for $this_nick on $this_channel in $delay"
208             if $delay > 0 || $Debug;
209 0     0     timer $delay, sub { autoop_do @base_arg, 'autovoice' };
  0            
210             }
211             }
212              
213             sub main::hook_autoop_join {
214 0     0     my $channel = shift;
215 0           my @arg = ($channel, $::who, "$::user\@$::host");
216 0 0         autoop_try @arg, 0
217             if have_ops_q $channel;
218             }
219             addhook 'join', 'autoop_join';
220              
221             # When a /names list comes by, note people who don't have a voice in
222             # %Autovoice. Without this I'd try to +v them when I get +o myself.
223             # This only has any effect when joining the channel, though it runs for
224             # every /names.
225              
226             sub main::hook_autoop_names {
227 0     0     my ($rest) = @_;
228 0           my ($x1, $x2, $channel, $list) = split ' ', $rest, 4;
229              
230             # I used to test if the channel was +m here as well, but the on-join
231             # channel mode change can come after the /names.
232 0 0         return unless exists $Autovoice{$channel};
233              
234 0           my $now = time;
235 0           $list =~ s/^://;
236 0           for my $who (split ' ', $list) {
237 0 0         next if $who =~ /^[+@]/;
238             userhost $who, sub {
239 0     0     my $pat = mask_to_re ban_pattern $::who, $::user, $::host;
240 0   0       $Autovoice{$channel}{$pat} ||= $now;
241 0           };
242             }
243             }
244             addhook '353', 'autoop_names';
245              
246             # /autoop [channel]
247             sub main::cmd_autoop {
248 0     0     debug "cmd_autoop $::args";
249 0 0         optional_channel or return;
250 0           my $c = lc xgetarg;
251 0 0         have_ops $c or return;
252 0 0         $Autoop or return;
253 0           userhost [keys %{ $Chan_user{$c} }], sub {
254 0     0     autoop_try $c, $::who, "$::user\@$::host", 1;
255 0           };
256             }
257             addcmd 'autoop';
258             addhelp 'autoop', '[channel]',
259             q{Uses your autoop list to op and voice people on the current channel.
260             Since this happens automatically you don't normally have to do this, this
261             command is useful if you'd had autoopping disabled, or if there's a bug
262             in the system.
263             };
264              
265             # Try an /autoop after receiving ops.
266             add_hook '+op', sub {
267             my ($c, $n) = @_;
268              
269             timer 10, sub { main::cmd_autoop $c } if ieq $n, $::nick;
270             };
271              
272             add_hook '-voice', sub {
273             my ($c, $n) = @_;
274             my ($now);
275              
276             return unless $Autovoice;
277             return unless exists $Autovoice{$c};
278             $now = time;
279             userhost $n, sub {
280             $Autovoice{$c}{mask_to_re ban_pattern $::who, $::user, $::host}
281             = $now;
282             };
283             };
284              
285             add_hook '+voice', sub {
286             my ($c, $n) = @_;
287              
288             return unless $Autovoice{$c};
289             userhost $n, sub {
290             delete $Autovoice{$c}{mask_to_re
291             ban_pattern $::who, $::user, $::host};
292              
293             # If that was the last pattern, drop the hash ref.
294             undef $Autovoice{$c} unless %{ $Autovoice{$c} };
295             }
296             };
297              
298             # Allow ops on autovoiced channels to control your autovoice delay with
299             # a specially formatted /msg.
300             #
301             # autovoice report current value
302             # autovoice N set it to N
303             # autovoice +N add N seconds to the delay
304             # autovoice -N remove N seconds from the delay
305              
306             sub main::hook_autovoice_control_msg {
307 0     0     my ($msg) = @_;
308 0 0         return unless $msg =~ s/^\s*autovoice\b//i;
309 0 0         return unless $Autovoice_control;
310 0           debug "autovoice_control_msg [$msg] who [$::who]";
311              
312 0 0         if (!grep { $Chan_op{$_}{$::who} } keys %Autovoice) {
  0            
313 0           notice $::who, "You aren't an op on a channel I'm autovoicing.";
314 0           return;
315             }
316              
317 0 0         if (ref $Autovoice_delay) {
318 0           notice $::who, "My autovoice delay is a code ref, "
319             . "so remote control isn't available.";
320 0           return;
321             }
322              
323 0           my $new;
324 0 0         if ($msg =~ /^\s*$/) {
    0          
    0          
325 0           notice $::who, "Current autovoice delay is $Autovoice_delay.";
326             }
327             elsif ($msg =~ /^\s*(\d+)\s*$/) {
328 0           $new = $1;
329             }
330             elsif ($msg =~ /^\s*([+-]\s*\d+)\s*$/) {
331 0           my $n = $1;
332 0           $n =~ s/\s+//g;
333 0           $new = $Autovoice_delay + $n;
334             }
335             else {
336 0           notice $::who, "Unrecognized autovoice command, use: `autovoice', "
337             . "`autovoice N', `autovoice +N', `autovoice -N'.";
338             }
339              
340 0 0         if (defined $new) {
341 0 0         if ($new < 0) {
    0          
342 0           notice $::who, "Ignoring invalid delay $new.";
343             }
344             elsif ($new < 1) {
345 0           notice $::who, "Ignoring too-low delay $new, "
346             . "it would cause +v floods on netjoins.";
347             }
348             else {
349 0           doset 'autovoice_delay', $new;
350 0           notice $::who, "Autovoice delay set to $Autovoice_delay.";
351             }
352             }
353             }
354             addhook 'msg', 'autovoice_control_msg';
355              
356             1;