]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/bans.rb
Better debugging when bans errors out
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / bans.rb
1 # BansPlugin v3 for 0.9.11\r
2 #\r
3 # Managing kick and bans, automatically removing bans after timeouts, quiet bans, and kickban/quietban based on regexp\r
4 #\r
5 # (c) 2006 Marco Gulino <marco@kmobiletools.org>\r
6 # (c) 2007 kamu <mr.kamu@gmail.com>\r
7 # (c) 2007 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>\r
8 #\r
9 # v1 -> v2 (kamu's version, never released)\r
10 #   * reworked\r
11 #   * autoactions triggered on join\r
12 #   * action on join or badword can be anything: kick, ban, kickban, quiet\r
13 #\r
14 # v2 -> v3 (GB)\r
15 #   * remove the 'bans' prefix from most of the commands\r
16 #   * (un)quiet has been renamed to (un)silence because 'quiet' was used to tell the bot to keep quiet\r
17 #   * both (un)quiet and (un)silence are accepted as actions\r
18 #   * use the more descriptive 'onjoin' term for autoactions\r
19 #   * convert v1's (0.9.10) :bans and :bansmasks to BadWordActions and WhitelistEntries\r
20 #   * enhanced list manipulation facilities\r
21 #   * fixed regexp usage in requirements for plugin map\r
22 #   * add proper auth management\r
23 #\r
24 # Licensed under GPL V2.\r
25 \r
26 OnJoinAction = Struct.new("OnJoinAction", :host, :action, :channel, :reason)\r
27 BadWordAction = Struct.new("BadWordAction", :regexp, :action, :channel, :timer, :reason)\r
28 WhitelistEntry = Struct.new("WhitelistEntry", :host, :channel)\r
29 \r
30 \r
31 class BansPlugin < Plugin\r
32 \r
33   IdxRe = /^\d+$/\r
34   TimerRe = /^\d+[smhd]$/\r
35   ChannelRe = /^#+[^\s]+$/\r
36   ChannelAllRe = /^(?:all|#+[^\s]+)$/\r
37   ActionRe = /(?:ban|kick|kickban|silence|quiet)/\r
38 \r
39   def name\r
40     "bans"\r
41   end\r
42 \r
43   def make_badword_rx(txt)\r
44     return /\b#{txt}\b/i\r
45   end\r
46 \r
47   def initialize\r
48     super\r
49 \r
50     # Convert old BadWordActions, which were simpler and labelled :bans\r
51     if @registry.has_key? :bans\r
52       badwords = Array.new\r
53       bans = @registry[:bans]\r
54       @registry[:bans].each { |ar|\r
55         case ar[0]\r
56         when "quietban"\r
57           action = :silence\r
58         when "kickban"\r
59           action = :kickban\r
60         else\r
61           # Shouldn't happen\r
62           warn "Unknown action in old data #{ar.inspect} -- entry ignored"\r
63           next\r
64         end\r
65         bans.delete(ar)\r
66         chan = ar[1]\r
67         regexp = make_badword_rx(ar[2])\r
68         badwords << BadWordAction.new(regexp, action, chan, "0s", "")\r
69       }\r
70       @registry[:badwords] = badwords\r
71       if bans.length > 0\r
72         # Store the ones we couldn't convert\r
73         @registry[:bans] = bans\r
74       else\r
75         @registry.delete(:bans)\r
76       end\r
77     else\r
78       @registry[:badwords] = Array.new unless @registry.has_key? :badwords\r
79     end\r
80 \r
81     # Convert old WhitelistEntries, which were simpler and labelled :bansmasks\r
82     if @registry.has_key? :bans\r
83       wl = Array.new\r
84       @registry[:bansmasks].each { |mask|\r
85         badwords << WhitelistEntry.new(mask, "all")\r
86       }\r
87       @registry[:whitelist] = wl\r
88       @registry.delete(:bansmasks)\r
89     else\r
90       @registry[:whitelist] = Array.new unless @registry.has_key? :whitelist\r
91     end\r
92 \r
93     @registry[:onjoin] = Array.new unless @registry.has_key? :onjoin\r
94   end\r
95 \r
96   def help(plugin, topic="")\r
97     case plugin\r
98     when "ban"\r
99       return "ban <nick/hostmask> [Xs/m/h/d] [#channel]: ban a user from the given channel for the given amount of time. default is forever, on the current channel"\r
100     when "unban"\r
101       return "unban <nick/hostmask> [#channel]: unban a user from the given channel. defaults to the current channel"\r
102     when "kick"\r
103       return "kick <nick> [#channel] [reason ...]: kick a user from the given channel with the given reason. defaults to the current channel, no reason"\r
104     when "kickban"\r
105       return "kickban <nick> [Xs/m/h/d] [#channel] [reason ...]: kicks and bans a user from the given channel for the given amount of time, with the given reason. default is forever, on the current channel, with no reason"\r
106     when "silence"\r
107       return "silence <nick/hostmask> [Xs/m/h/d] [#channel]: silence a user on the given channel for the given time. default is forever, on the current channel. not all servers support silencing users"\r
108     when "unsilence"\r
109       return "unsilence <nick/hostmask> [#channel]: allow the given user to talk on the given channel. defaults to the current channel"\r
110     when "bans"\r
111       case topic\r
112       when "add"\r
113         return "bans add <onjoin|badword|whitelist>: add an automatic action for people that join or say some bad word, or a whitelist entry. further help available"\r
114       when "add onjoin"\r
115         return "bans add onjoin <hostmask> [action] [#channel] [reason ...]: will add an autoaction for any one who joins with hostmask. default action is silence, default channel is all"\r
116       when "add badword"\r
117         return "bans add badword <regexp> [action] [Xs/m/h/d] [#channel|all] [reason ...]: adds a badword regexp, if a user sends a message that matches regexp, the action will be invoked. default action is silence, default channel is all"\r
118       when "add whitelist"\r
119         return "bans add whitelist <hostmask> [#channel|all]: add the given hostmask to the whitelist. no autoaction will be triggered by users on the whitelist"\r
120       when "rm"\r
121         return "bans rm <onjoin|badword|whitelist> <hostmask/regexp> [#channel], or bans rm <onjoin|badword|whitelist> index <num>: removes the specified onjoin or badword rule or whitelist entry."\r
122       when "list"\r
123         return"bans list <onjoin|badword|whitelist>: lists all onjoin or badwords or whitelist entries"\r
124       end\r
125     end\r
126     return "bans <command>: allows a user of the bot to do a range of bans and unbans. commands are: [un]ban, kick[ban], [un]silence, add, rm and list"\r
127   end\r
128 \r
129   def listen(m)\r
130     return unless m.respond_to?(:public?) and m.public?\r
131     @registry[:whitelist].each { |white|\r
132       next unless ['all', m.target.downcase].include?(white.channel)\r
133       return if m.source.matches?(white.host)\r
134     }\r
135 \r
136     @registry[:badwords].each { |badword|\r
137       next unless ['all', m.target.downcase].include?(badword.channel)\r
138       next unless badword.regexp.match(m.message)\r
139 \r
140       do_cmd(badword.action.to_sym, m.source.nick, m.target, badword.timer, badword.reason)\r
141       m.reply "bad word detected! #{badword.action} for #{badword.timer} because: #{badword.reason}"\r
142       return\r
143     }\r
144   end\r
145 \r
146   def join(m)\r
147     @registry[:whitelist].each { |white|\r
148       next unless ['all', m.target.downcase].include?(white.channel)\r
149       return if m.source.matches?(white.host)\r
150     }\r
151 \r
152     @registry[:onjoin].each { |auto|\r
153       next unless ['all', m.target.downcase].include?(auto.channel)\r
154       next unless m.source.matches? auto.host\r
155 \r
156       do_cmd(auto.action.to_sym, m.source.nick, m.target, "0s", auto.reason)\r
157       return\r
158     }\r
159   end\r
160 \r
161   def ban_user(m, params=nil)\r
162     nick, channel = params[:nick], check_channel(m, params[:channel])\r
163     timer = params[:timer]\r
164     do_cmd(:ban, nick, channel, timer)\r
165   end\r
166 \r
167   def unban_user(m, params=nil)\r
168     nick, channel = params[:nick], check_channel(m, params[:channel])\r
169     do_cmd(:unban, nick, channel)\r
170   end\r
171 \r
172   def kick_user(m, params=nil)\r
173     nick, channel = params[:nick], check_channel(m, params[:channel])\r
174     reason = params[:reason].to_s\r
175     do_cmd(:kick, nick, channel, "0s", reason)\r
176   end\r
177 \r
178   def kickban_user(m, params=nil)\r
179     nick, channel, reason = params[:nick], check_channel(m, params[:channel])\r
180     timer, reason = params[:timer], params[:reason].to_s\r
181     do_cmd(:kickban, nick, channel, timer, reason)\r
182   end\r
183 \r
184   def silence_user(m, params=nil)\r
185     nick, channel = params[:nick], check_channel(m, params[:channel])\r
186     timer = params[:timer]\r
187     do_cmd(:silence, nick, channel, timer)\r
188   end\r
189 \r
190   def unsilence_user(m, params=nil)\r
191     nick, channel = params[:nick], check_channel(m, params[:channel])\r
192     do_cmd(:unsilence, nick, channel)\r
193   end\r
194 \r
195   def add_onjoin(m, params=nil)\r
196     begin\r
197       host, channel = m.server.new_netmask(params[:host]), params[:channel].downcase\r
198       action, reason = params[:action], params[:reason].to_s\r
199 \r
200       autos = @registry[:onjoin]\r
201       autos << OnJoinAction.new(host, action, channel, reason.dup)\r
202       @registry[:onjoin] = autos\r
203 \r
204       m.okay\r
205     rescue\r
206       error $!\r
207       m.reply $!\r
208     end\r
209   end\r
210 \r
211   def list_onjoin(m, params=nil)\r
212     m.reply "onjoin rules: #{@registry[:onjoin].length}"\r
213     @registry[:onjoin].each_with_index { |auto, idx|\r
214       m.reply "\##{idx+1}: #{auto.host} | #{auto.action} | #{auto.channel} | '#{auto.reason}'"\r
215     }\r
216   end\r
217 \r
218   def rm_onjoin(m, params=nil)\r
219     autos = @registry[:onjoin]\r
220     count = autos.length\r
221 \r
222     idx = params[:idx].to_i\r
223 \r
224     if idx\r
225       if idx > count\r
226         m.reply "No such onjoin \##{idx}"\r
227         return\r
228       end\r
229       autos.delete_at(idx-1)\r
230     else\r
231       begin\r
232         host = m.server.new_netmask(params[:host])\r
233         channel = params[:channel].downcase\r
234 \r
235         autos.each { |rule|\r
236           next unless ['all', rule.channel].include?(channel)\r
237           autos.delete rule if rule.host == host\r
238         }\r
239       rescue\r
240         error $!\r
241         m.reply $!\r
242       end\r
243     end\r
244     @registry[:onjoin] = autos\r
245     m.okay if count > autos.length\r
246   end\r
247 \r
248   def add_badword(m, params=nil)\r
249     regexp, channel = make_badword_rx(params[:regexp]), params[:channel].dup\r
250     action, timer, reason = params[:action], params[:timer].dup, params[:reason].to_s\r
251 \r
252     badwords = @registry[:badwords]\r
253     badwords << BadWordAction.new(regexp, action, channel, timer, reason)\r
254     @registry[:badwords] = badwords\r
255 \r
256     m.okay\r
257   end\r
258 \r
259   def list_badword(m, params=nil)\r
260     m.reply "badword rules: #{@registry[:badwords].length}"\r
261 \r
262     @registry[:badwords].each_with_index { |badword, idx|\r
263       m.reply "\##{idx+1}: #{badword.regexp.source} | #{badword.action} | #{badword.channel} | #{badword.timer} | #{badword.reason}"\r
264     }\r
265   end\r
266 \r
267   def rm_badword(m, params=nil)\r
268     badwords = @registry[:badwords]\r
269     count = badwords.length\r
270 \r
271     idx = params[:idx].to_i\r
272 \r
273     if idx\r
274       if idx > count\r
275         m.reply "No such badword \##{idx}"\r
276         return\r
277       end\r
278       badwords.delete_at(idx-1)\r
279     else\r
280       channel = params[:channel].downcase\r
281 \r
282       regexp = make_badword_rx(params[:regexp])\r
283       debug "Trying to remove #{regexp.inspect} from #{badwords.inspect}"\r
284 \r
285       badwords.each { |badword|\r
286         next unless ['all', badword.channel].include?(channel)\r
287         badwords.delete(badword) if badword == regexp\r
288       }\r
289     end\r
290 \r
291     @registry[:badwords] = badwords\r
292     m.okay if count > badwords.length\r
293   end\r
294 \r
295   def add_whitelist(m, params=nil)\r
296     begin\r
297       host, channel = m.server.new_netmask(params[:host]), params[:channel].downcase\r
298 \r
299       # TODO check if a whitelist entry for this host already exists\r
300       whitelist = @registry[:whitelist]\r
301       whitelist << WhitelistEntry.new(host, channel)\r
302       @registry[:whitelist] = whitelist\r
303 \r
304       m.okay\r
305     rescue\r
306       error $!\r
307       m.reply $!\r
308     end\r
309   end\r
310 \r
311   def list_whitelist(m, params=nil)\r
312     m.reply "whitelist entries: #{@registry[:whitelist].length}"\r
313     @registry[:whitelist].each_with_index { |auto, idx|\r
314       m.reply "\##{idx+1}: #{auto.host} | #{auto.channel}"\r
315     }\r
316   end\r
317 \r
318   def rm_whitelist(m, params=nil)\r
319     wl = @registry[:whitelist]\r
320     count = wl.length\r
321 \r
322     idx = params[:idx].to_i\r
323 \r
324     if idx\r
325       if idx > count\r
326         m.reply "No such whitelist entry \##{idx}"\r
327         return\r
328       end\r
329       wl.delete_at(idx-1)\r
330     else\r
331       begin\r
332         host = m.server.new_netmask(params[:host])\r
333         channel = params[:channel].downcase\r
334 \r
335         wl.each { |rule|\r
336           next unless ['all', rule.channel].include?(channel)\r
337           wl.delete rule if rule.host == host\r
338         }\r
339       rescue\r
340         error $!\r
341         m.reply $!\r
342       end\r
343     end\r
344     @registry[:whitelist] = wl\r
345     m.okay if count > whitelist.length\r
346   end\r
347 \r
348   private\r
349   def check_channel(m, strchannel)\r
350     begin\r
351       raise "must specify channel if using privmsg" if strchannel.empty? and m.private?\r
352       channel = strchannel.empty? ? m.target : m.server.channel(strchannel)\r
353       raise "I am not in that channel" unless channel.has_user?(@bot)\r
354 \r
355       return channel\r
356     rescue\r
357       error $!\r
358       m.reply $!\r
359     end\r
360   end\r
361 \r
362   def do_cmd(action, nick, channel, timer="0s", reason="")\r
363     if timer.eql? "0s"\r
364       timer = 0\r
365     else\r
366       timer = $1.to_i if timer =~ /^(\d+)s$/\r
367       timer = $1.to_i * 60 if timer =~ /^(\d+)m$/\r
368       timer = $1.to_i * 60 * 60 if timer =~ /^(\d+)h$/\r
369       timer = $1.to_i * 60 * 60 * 24 if timer =~ /^(\d+)d$/\r
370     end\r
371 \r
372     case action\r
373     when :ban\r
374       set_mode(channel, "+b", nick)\r
375       @bot.timer.add_once(timer) { set_mode(channel, "-b", nick) } if timer > 0\r
376     when :unban\r
377       set_mode(channel, "-b", nick)\r
378     when :kick\r
379       do_kick(channel, nick, reason)\r
380     when :kickban\r
381       set_mode(channel, "+b", nick)\r
382       @bot.timer.add_once(timer) { set_mode(channel, "-b", nick) } if timer > 0\r
383       do_kick(channel, nick, reason)\r
384     when :silence, :quiet\r
385       set_mode(channel, "+q", nick)\r
386       @bot.timer.add_once(timer) { set_mode(channel, "-q", nick) } if timer > 0\r
387     when :unsilence, :unquiet\r
388       set_mode(channel, "-q", nick)\r
389     end\r
390   end\r
391 \r
392   def set_mode(channel, mode, nick)\r
393     host = channel.has_user?(nick) ? "*!*@" + channel.get_user(nick).host : nick\r
394     @bot.mode(channel, mode, host)\r
395   end\r
396 \r
397   def do_kick(channel, nick, reason="")\r
398     @bot.kick(channel, nick, reason)\r
399   end\r
400 end\r
401 \r
402 plugin = BansPlugin.new\r
403 \r
404 plugin.default_auth( 'act', false )\r
405 plugin.default_auth( 'edit', false )\r
406 plugin.default_auth( 'list', true )\r
407 \r
408 plugin.map 'ban :nick :timer :channel', :action => 'ban_user',\r
409   :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},\r
410   :defaults => {:timer => '0s', :channel => ''},\r
411   :auth_path => 'act'\r
412 plugin.map 'unban :nick :channel', :action => 'unban_user',\r
413   :requirements => {:channel => BansPlugin::ChannelRe},\r
414   :defaults => {:channel => ''},\r
415   :auth_path => 'act'\r
416 plugin.map 'kick :nick :channel *reason', :action => 'kick_user',\r
417   :requirements => {:channel => BansPlugin::ChannelRe},\r
418   :defaults => {:channel => '', :reason => ''},\r
419   :auth_path => 'act'\r
420 plugin.map 'kickban :nick :timer :channel *reason', :action => 'kickban_user',\r
421   :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},\r
422   :defaults => {:timer => '0s', :channel => '', :reason => ''},\r
423   :auth_path => 'act'\r
424 plugin.map 'silence :nick :timer :channel', :action => 'silence_user',\r
425   :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},\r
426   :defaults => {:timer => '0s', :channel => ''},\r
427   :auth_path => 'act'\r
428 plugin.map 'unsilence :nick :channel', :action => 'unsilence_user',\r
429   :requirements => {:channel => BansPlugin::ChannelRe},\r
430   :defaults => {:channel => ''},\r
431   :auth_path => 'act'\r
432 \r
433 plugin.map 'bans add onjoin :host :action :channel *reason', :action => 'add_onjoin',\r
434   :requirements => {:action => BansPlugin::ActionRe, :channel => BansPlugin::ChannelAllRe},\r
435   :defaults => {:action => 'kickban', :channel => 'all', :reason => 'netmask not welcome'},\r
436   :auth_path => 'edit::onjoin'\r
437 plugin.map 'bans rm onjoin index :idx', :action => 'rm_onjoin',\r
438   :requirements => {:num => BansPlugin::IdxRe},\r
439   :auth_path => 'edit::onjoin'\r
440 plugin.map 'bans rm onjoin :host :channel', :action => 'rm_onjoin',\r
441   :requirements => {:channel => BansPlugin::ChannelAllRe},\r
442   :defaults => {:channel => 'all'},\r
443   :auth_path => 'edit::onjoin'\r
444 plugin.map 'bans list onjoin[s]', :action => 'list_onjoin',\r
445   :auth_path => 'list::onjoin'\r
446 \r
447 plugin.map 'bans add badword :regexp :action :timer :channel *reason', :action => 'add_badword',\r
448   :requirements => {:action => BansPlugin::ActionRe, :timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelAllRe},\r
449   :defaults => {:action => 'silence', :timer => "0s", :channel => 'all', :reason => 'bad word'},\r
450   :auth_path => 'edit::badword'\r
451 plugin.map 'bans rm badword index :idx', :action => 'rm_badword',\r
452   :requirements => {:num => BansPlugin::IdxRe},\r
453   :auth_path => 'edit::badword'\r
454 plugin.map 'bans rm badword :regexp :channel', :action => 'rm_badword',\r
455   :requirements => {:channel => BansPlugin::ChannelAllRe},\r
456   :defaults => {:channel => 'all'},\r
457   :auth_path => 'edit::badword'\r
458 plugin.map 'bans list badword[s]', :action => 'list_badword',\r
459   :auth_path => 'list::badword'\r
460 \r
461 plugin.map 'bans add whitelist :host :channel', :action => 'add_whitelist',\r
462   :requirements => {:channel => BansPlugin::ChannelAllRe},\r
463   :defaults => {:channel => 'all'},\r
464   :auth_path => 'edit::whitelist'\r
465 plugin.map 'bans rm whitelist index :idx', :action => 'rm_whitelist',\r
466   :requirements => {:num => BansPlugin::IdxRe},\r
467   :auth_path => 'edit::whitelist'\r
468 plugin.map 'bans rm whitelist :host :channel', :action => 'rm_whitelist',\r
469   :requirements => {:channel => BansPlugin::ChannelAllRe},\r
470   :defaults => {:channel => 'all'},\r
471   :auth_path => 'edit::whitelist'\r
472 plugin.map 'bans list whitelist', :action => 'list_whitelist',\r
473   :auth_path => 'list::whitelist'\r
474 \r