]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/bans.rb
b77146b81d5a59d3f45ceba55c1c7c5d85ed0819
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / bans.rb
1 #-- vim:sw=2:et
2 #++
3 #
4 # :title: Bans Plugin v3 for rbot 0.9.11 and later
5 #
6 # Author:: Marco Gulino <marco@kmobiletools.org>
7 # Author:: kamu <mr.kamu@gmail.com>
8 # Author:: Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
9 #
10 # Copyright:: (C) 2006 Marco Gulino
11 # Copyright:: (C) 2007 kamu, Giuseppe Bilotta
12 #
13 # License:: GPL V2.
14 #
15 # Managing kick and bans, automatically removing bans after timeouts, quiet
16 # bans, and kickban/quietban based on regexp
17 #
18 # v1 -> v2 (kamu's version, never released)
19 #   * reworked
20 #   * autoactions triggered on join
21 #   * action on join or badword can be anything: kick, ban, kickban, quiet
22 #
23 # v2 -> v3 (GB)
24 #   * remove the 'bans' prefix from most of the commands
25 #   * (un)quiet has been renamed to (un)silence because 'quiet' was used to
26 #     tell the bot to keep quiet
27 #   * both (un)quiet and (un)silence are accepted as actions
28 #   * use the more descriptive 'onjoin' term for autoactions
29 #   * convert v1's (0.9.10) :bans and :bansmasks to BadWordActions and
30 #     WhitelistEntries
31 #   * enhanced list manipulation facilities
32 #   * fixed regexp usage in requirements for plugin map
33 #   * add proper auth management
34
35 define_structure :OnJoinAction, :host, :action, :channel, :reason
36 define_structure :BadWordAction, :regexp, :action, :channel, :timer, :reason
37 define_structure :WhitelistEntry, :host, :channel
38 define_structure :MassHlAction, :num, :perc, :action, :channel, :timer, :reason
39
40 class BansPlugin < Plugin
41
42   IdxRe = /^\d+$/
43   TimerRe = /^\d+[smhd]$/
44   ChannelRe = /^#+[^\s]+$/
45   ChannelAllRe = /^(?:all|#+[^\s]+)$/
46   ActionRe = /(?:ban|kick|kickban|silence|quiet)/
47
48   def name
49     "bans"
50   end
51
52   def make_badword_rx(txt)
53     return /\b(?:#{txt})\b/i
54   end
55
56   def initialize
57     super
58
59     # Convert old BadWordActions, which were simpler and labelled :bans
60     if @registry.has_key? :bans
61       badwords = Array.new
62       bans = @registry[:bans]
63       @registry[:bans].each { |ar|
64         case ar[0]
65         when "quietban"
66           action = :silence
67         when "kickban"
68           action = :kickban
69         else
70           # Shouldn't happen
71           warning "Unknown action in old data #{ar.inspect} -- entry ignored"
72           next
73         end
74         bans.delete(ar)
75         chan = ar[1].downcase
76         regexp = make_badword_rx(ar[2])
77         badwords << BadWordAction.new(regexp, action, chan, "0s", "")
78       }
79       @registry[:badwords] = badwords
80       if bans.length > 0
81         # Store the ones we couldn't convert
82         @registry[:bans] = bans
83       else
84         @registry.delete(:bans)
85       end
86     else
87       @registry[:badwords] = Array.new unless @registry.has_key? :badwords
88     end
89
90     # Convert old WhitelistEntries, which were simpler and labelled :bansmasks
91     if @registry.has_key? :bans
92       wl = Array.new
93       @registry[:bansmasks].each { |mask|
94         badwords << WhitelistEntry.new(mask, "all")
95       }
96       @registry[:whitelist] = wl
97       @registry.delete(:bansmasks)
98     else
99       @registry[:whitelist] = Array.new unless @registry.has_key? :whitelist
100     end
101
102     @registry[:onjoin] = Array.new unless @registry.has_key? :onjoin
103     @registry[:masshl] = Array.new unless @registry.has_key? :masshl
104   end
105
106   def help(plugin, topic="")
107     case plugin
108     when "ban"
109       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"
110     when "unban"
111       return "unban <nick/hostmask> [#channel]: unban a user from the given channel. defaults to the current channel"
112     when "kick"
113       return "kick <nick> [#channel] [reason ...]: kick a user from the given channel with the given reason. defaults to the current channel, no reason"
114     when "kickban"
115       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"
116     when "silence"
117       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"
118     when "unsilence"
119       return "unsilence <nick/hostmask> [#channel]: allow the given user to talk on the given channel. defaults to the current channel"
120     when "bans"
121       case topic
122       when "add"
123         return "bans add <onjoin|badword|whitelist|masshl>: add an automatic action for people that join or say some bad word, or a whitelist entry. further help available"
124       when "add onjoin"
125         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"
126       when "add badword"
127         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"
128       when "add whitelist"
129         return "bans add whitelist <hostmask> [#channel|all]: add the given hostmask to the whitelist. no autoaction will be triggered by users on the whitelist"
130       when "add masshl"
131         return "masshl add <max_nicks|percentage> [action] [Xs/m/h/d] [#channel|all] [reason ...]: adds an massive highligh action. You can use both max and % in one trigger, the higher value will be taken. For two triggers in one channel, the one with higher requirements will be taken"
132       when "rm"
133         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. For masshl, bans rm masshl index [#channel|all]"
134       when "list"
135         return"bans list <onjoin|badword|whitelist|masshl>: lists all onjoin or badwords or whitelist entries. For masshl, you can add [#channel|all]"
136       else
137         return "commands are: add, add onjoin, add badword, add whitelist, add masshl, rm, list"
138       end
139     end
140     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"
141   end
142
143   def message(m)
144     return unless m.channel
145     mm = m.plainmessage.irc_downcase(m.server.casemap).split(/[\s\.,:]/)
146     nicks_said = (m.channel.users.map { |u| u.downcase} & mm).size
147     return unless nicks_said > 0 # not really needed, but saves some cycles
148     got_nicks = 0
149     masshl_action = nil
150     @registry[:masshl].each { |masshl|
151       next unless masshl.channel == m.channel.downcase or masshl.channel == "all"
152       needed = [masshl.num.to_i, (masshl.perc * m.channel.user_nicks.size / 100).to_i].max
153       next if needed > nicks_said or needed < got_nicks
154       masshl_action = masshl
155       got_nicks = needed
156     }
157     return unless masshl_action
158     do_cmd masshl_action.action.intern, m.sourcenick, m.channel, masshl_action.timer, masshl_action.reason
159   end
160
161   def listen(m)
162     return unless m.respond_to?(:public?) and m.public?
163     @registry[:whitelist].each { |white|
164       next unless ['all', m.target.downcase].include?(white.channel)
165       return if m.source.matches?(white.host)
166     }
167
168     @registry[:badwords].each { |badword|
169       next unless ['all', m.target.downcase].include?(badword.channel)
170       next unless badword.regexp.match(m.plainmessage)
171
172       m.reply "bad word detected! #{badword.action} for #{badword.timer} because: #{badword.reason}"
173       do_cmd(badword.action.to_sym, m.source.nick, m.target, badword.timer, badword.reason)
174       return
175     }
176   end
177
178   def join(m)
179     @registry[:whitelist].each { |white|
180       next unless ['all', m.target.downcase].include?(white.channel)
181       return if m.source.matches?(white.host)
182     }
183
184     @registry[:onjoin].each { |auto|
185       next unless ['all', m.target.downcase].include?(auto.channel)
186       next unless m.source.matches? auto.host
187
188       do_cmd(auto.action.to_sym, m.source.nick, m.target, "0s", auto.reason)
189       return
190     }
191   end
192
193   def ban_user(m, params=nil)
194     nick, channel = params[:nick], check_channel(m, params[:channel])
195     timer = params[:timer]
196     do_cmd(:ban, nick, channel, timer)
197   end
198
199   def unban_user(m, params=nil)
200     nick, channel = params[:nick], check_channel(m, params[:channel])
201     do_cmd(:unban, nick, channel)
202   end
203
204   def kick_user(m, params=nil)
205     nick, channel = params[:nick], check_channel(m, params[:channel])
206     reason = params[:reason].to_s
207     do_cmd(:kick, nick, channel, "0s", reason)
208   end
209
210   def kickban_user(m, params=nil)
211     nick, channel, reason = params[:nick], check_channel(m, params[:channel])
212     timer, reason = params[:timer], params[:reason].to_s
213     do_cmd(:kickban, nick, channel, timer, reason)
214   end
215
216   def silence_user(m, params=nil)
217     nick, channel = params[:nick], check_channel(m, params[:channel])
218     timer = params[:timer]
219     do_cmd(:silence, nick, channel, timer)
220   end
221
222   def unsilence_user(m, params=nil)
223     nick, channel = params[:nick], check_channel(m, params[:channel])
224     do_cmd(:unsilence, nick, channel)
225   end
226
227   def add_masshl(m, params=nil)
228     num = params[:num].to_i
229     perc = params[:perc] ? /(\d{1,2})\%/.match(params[:perc])[1].to_i : 0
230     channel, action = params[:channel].downcase.dup, params[:action]
231     timer, reason = params[:timer].dup, params[:reason].to_s
232     if perc == 0 and num == 0
233       m.reply "both triggers 0, you don't want this."
234       return
235     end
236
237     masshl = @registry[:masshl]
238     masshl << MassHlAction.new(num, perc, action, channel, timer, reason)
239     @registry[:masshl] = masshl
240
241     m.okay
242   end
243
244   def rm_masshl(m, params=nil)
245     masshl = @registry[:masshl]
246     masshl_w = params[:channel] ? masshl.select { |mh| mh.channel == params[:channel].downcase } : masshl
247     count = masshl_w.length
248     idx = params[:idx].to_i
249
250     if idx > count
251       m.reply "No such masshl \##{idx}"
252       return
253     end
254     masshl.delete(masshl_w[idx-1])
255     @registry[:masshl] = masshl
256     m.okay
257   end
258
259   def list_masshl(m, params=nil)
260     masshl = @registry[:masshl]
261     masshl = masshl.select { |mh| mh.channel == params[:channel].downcase } if params[:channel]
262     m.reply params[:channel] ? "masshl rules: #{masshl.length} for #{params[:channel]}" : "masshl rules: #{masshl.length}"
263     masshl.each_with_index { |mh, idx|
264       m.reply "\##{idx+1}: #{mh.num} | #{mh.perc}% | #{mh.action} | #{mh.channel} | #{mh.timer} | #{mh.reason}"
265     }
266   end
267
268   def add_onjoin(m, params=nil)
269     begin
270       host, channel = m.server.new_netmask(params[:host]), params[:channel].downcase
271       action, reason = params[:action], params[:reason].to_s
272
273       autos = @registry[:onjoin]
274       autos << OnJoinAction.new(host, action, channel, reason.dup)
275       @registry[:onjoin] = autos
276
277       m.okay
278     rescue
279       error $!
280       m.reply $!
281     end
282   end
283
284   def list_onjoin(m, params=nil)
285     m.reply "onjoin rules: #{@registry[:onjoin].length}"
286     @registry[:onjoin].each_with_index { |auto, idx|
287       m.reply "\##{idx+1}: #{auto.host} | #{auto.action} | #{auto.channel} | '#{auto.reason}'"
288     }
289   end
290
291   def rm_onjoin(m, params=nil)
292     autos = @registry[:onjoin]
293     count = autos.length
294
295     idx = nil
296     idx = params[:idx].to_i if params[:idx]
297
298     if idx
299       if idx > count
300         m.reply "No such onjoin \##{idx}"
301         return
302       end
303       autos.delete_at(idx-1)
304     else
305       begin
306         host = m.server.new_netmask(params[:host])
307         channel = params[:channel].downcase
308
309         autos.each { |rule|
310           next unless ['all', rule.channel].include?(channel)
311           autos.delete rule if rule.host == host
312         }
313       rescue
314         error $!
315         m.reply $!
316       end
317     end
318     @registry[:onjoin] = autos
319     if count > autos.length
320       m.okay
321     else
322       m.reply "No matching onjoin rule for #{host} found"
323     end
324   end
325
326   def add_badword(m, params=nil)
327     regexp, channel = make_badword_rx(params[:regexp]), params[:channel].downcase.dup
328     action, timer, reason = params[:action], params[:timer].dup, params[:reason].to_s
329
330     badwords = @registry[:badwords]
331     badwords << BadWordAction.new(regexp, action, channel, timer, reason)
332     @registry[:badwords] = badwords
333
334     m.okay
335   end
336
337   def list_badword(m, params=nil)
338     m.reply "badword rules: #{@registry[:badwords].length}"
339
340     @registry[:badwords].each_with_index { |badword, idx|
341       m.reply "\##{idx+1}: #{badword.regexp.source} | #{badword.action} | #{badword.channel} | #{badword.timer} | #{badword.reason}"
342     }
343   end
344
345   def rm_badword(m, params=nil)
346     badwords = @registry[:badwords]
347     count = badwords.length
348
349     idx = nil
350     idx = params[:idx].to_i if params[:idx]
351
352     if idx
353       if idx > count
354         m.reply "No such badword \##{idx}"
355         return
356       end
357       badwords.delete_at(idx-1)
358     else
359       channel = params[:channel].downcase
360
361       regexp = make_badword_rx(params[:regexp])
362       debug "Trying to remove #{regexp.inspect} from #{badwords.inspect}"
363
364       badwords.each { |badword|
365         next unless ['all', badword.channel].include?(channel)
366         debug "Removing #{badword.inspect}" if badword.regexp == regexp
367         badwords.delete(badword) if badword.regexp == regexp
368       }
369     end
370
371     @registry[:badwords] = badwords
372     if count > badwords.length
373       m.okay
374     else
375       m.reply "No matching badword #{regexp} found"
376     end
377   end
378
379   def add_whitelist(m, params=nil)
380     begin
381       host, channel = m.server.new_netmask(params[:host]), params[:channel].downcase
382
383       # TODO check if a whitelist entry for this host already exists
384       whitelist = @registry[:whitelist]
385       whitelist << WhitelistEntry.new(host, channel)
386       @registry[:whitelist] = whitelist
387
388       m.okay
389     rescue
390       error $!
391       m.reply $!
392     end
393   end
394
395   def list_whitelist(m, params=nil)
396     m.reply "whitelist entries: #{@registry[:whitelist].length}"
397     @registry[:whitelist].each_with_index { |auto, idx|
398       m.reply "\##{idx+1}: #{auto.host} | #{auto.channel}"
399     }
400   end
401
402   def rm_whitelist(m, params=nil)
403     wl = @registry[:whitelist]
404     count = wl.length
405
406     idx = nil
407     idx = params[:idx].to_i if params[:idx]
408
409     if idx
410       if idx > count
411         m.reply "No such whitelist entry \##{idx}"
412         return
413       end
414       wl.delete_at(idx-1)
415     else
416       begin
417         host = m.server.new_netmask(params[:host])
418         channel = params[:channel].downcase
419
420         wl.each { |rule|
421           next unless ['all', rule.channel].include?(channel)
422           wl.delete rule if rule.host == host
423         }
424       rescue
425         error $!
426         m.reply $!
427       end
428     end
429     @registry[:whitelist] = wl
430     if count > whitelist.length
431       m.okay
432     else
433       m.reply "No host matching #{host}"
434     end
435   end
436
437   private
438   def check_channel(m, strchannel)
439     begin
440       raise "must specify channel if using privmsg" if m.private? and not strchannel
441       channel = m.server.channel(strchannel) || m.target
442       raise "I am not in that channel" unless channel.has_user?(@bot.nick)
443
444       return channel
445     rescue
446       error $!
447       m.reply $!
448     end
449   end
450
451   def do_cmd(action, nick, channel, timer_in=nil, reason=nil)
452     case timer_in
453     when nil
454       timer = 0
455     when /^(\d+)s$/
456       timer = $1.to_i
457     when /^(\d+)m$/
458       timer = $1.to_i * 60
459     when /^(\d+)h$/
460       timer = $1.to_i * 60 * 60 
461     when /^(\d+)d$/
462       timer = $1.to_i * 60 * 60 * 24
463     else
464       raise "Wrong time specifications"
465     end
466
467     case action
468     when :ban
469       set_temporary_mode(channel, 'b', nick, timer)
470     when :unban
471       set_mode(channel, "-b", nick)
472     when :kick
473       do_kick(channel, nick, reason)
474     when :kickban
475       set_temporary_mode(channel, 'b', nick, timer)
476       do_kick(channel, nick, reason)
477     when :silence, :quiet
478       set_mode(channel, "+q", nick)
479       @bot.timer.add_once(timer) { set_mode(channel, "-q", nick) } if timer > 0
480     when :unsilence, :unquiet
481       set_mode(channel, "-q", nick)
482     end
483   end
484
485   def set_mode(channel, mode, nick)
486     host = channel.has_user?(nick) ? "*!*@" + channel.get_user(nick).host : nick
487     @bot.mode(channel, mode, host)
488   end
489
490   def set_temporary_mode(channel, mode, nick, timer)
491     host = channel.has_user?(nick) ? "*!*@" + channel.users[nick].host : nick
492     @bot.mode(channel, "+#{mode}", host)
493     return if timer == 0
494     @bot.timer.add_once(timer) { @bot.mode(channel, "-#{mode}", host) }
495   end
496
497   def do_kick(channel, nick, reason="")
498     @bot.kick(channel, nick, reason)
499   end
500 end
501
502 plugin = BansPlugin.new
503
504 plugin.default_auth( 'act', false )
505 plugin.default_auth( 'edit', false )
506 plugin.default_auth( 'list', true )
507
508 plugin.map 'ban :nick :timer :channel', :action => 'ban_user',
509   :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},
510   :defaults => {:timer => nil, :channel => nil},
511   :auth_path => 'act'
512 plugin.map 'unban :nick :channel', :action => 'unban_user',
513   :requirements => {:channel => BansPlugin::ChannelRe},
514   :defaults => {:channel => nil},
515   :auth_path => 'act'
516 plugin.map 'kick :nick :channel *reason', :action => 'kick_user',
517   :requirements => {:channel => BansPlugin::ChannelRe},
518   :defaults => {:channel => nil, :reason => 'requested'},
519   :auth_path => 'act'
520 plugin.map 'kickban :nick :timer :channel *reason', :action => 'kickban_user',
521   :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},
522   :defaults => {:timer => nil, :channel => nil, :reason => 'requested'},
523   :auth_path => 'act'
524 plugin.map 'silence :nick :timer :channel', :action => 'silence_user',
525   :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},
526   :defaults => {:timer => nil, :channel => nil},
527   :auth_path => 'act'
528 plugin.map 'unsilence :nick :channel', :action => 'unsilence_user',
529   :requirements => {:channel => BansPlugin::ChannelRe},
530   :defaults => {:channel => nil},
531   :auth_path => 'act'
532
533 plugin.map 'bans add onjoin :host :action :channel *reason', :action => 'add_onjoin',
534   :requirements => {:action => BansPlugin::ActionRe, :channel => BansPlugin::ChannelAllRe},
535   :defaults => {:action => 'kickban', :channel => 'all', :reason => 'netmask not welcome'},
536   :auth_path => 'edit::onjoin'
537 plugin.map 'bans rm onjoin index :idx', :action => 'rm_onjoin',
538   :requirements => {:num => BansPlugin::IdxRe},
539   :auth_path => 'edit::onjoin'
540 plugin.map 'bans rm onjoin :host :channel', :action => 'rm_onjoin',
541   :requirements => {:channel => BansPlugin::ChannelAllRe},
542   :defaults => {:channel => 'all'},
543   :auth_path => 'edit::onjoin'
544 plugin.map 'bans list onjoin[s]', :action => 'list_onjoin',
545   :auth_path => 'list::onjoin'
546
547 plugin.map 'bans add badword :regexp :action :timer :channel *reason', :action => 'add_badword',
548   :requirements => {:action => BansPlugin::ActionRe, :timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelAllRe},
549   :defaults => {:action => 'silence', :timer => "0s", :channel => 'all', :reason => 'bad word'},
550   :auth_path => 'edit::badword'
551 plugin.map 'bans rm badword index :idx', :action => 'rm_badword',
552   :requirements => {:num => BansPlugin::IdxRe},
553   :auth_path => 'edit::badword'
554 plugin.map 'bans rm badword :regexp :channel', :action => 'rm_badword',
555   :requirements => {:channel => BansPlugin::ChannelAllRe},
556   :defaults => {:channel => 'all'},
557   :auth_path => 'edit::badword'
558 plugin.map 'bans list badword[s]', :action => 'list_badword',
559   :auth_path => 'list::badword'
560
561 plugin.map 'bans add whitelist :host :channel', :action => 'add_whitelist',
562   :requirements => {:channel => BansPlugin::ChannelAllRe},
563   :defaults => {:channel => 'all'},
564   :auth_path => 'edit::whitelist'
565 plugin.map 'bans rm whitelist index :idx', :action => 'rm_whitelist',
566   :requirements => {:num => BansPlugin::IdxRe},
567   :auth_path => 'edit::whitelist'
568 plugin.map 'bans rm whitelist :host :channel', :action => 'rm_whitelist',
569   :requirements => {:channel => BansPlugin::ChannelAllRe},
570   :defaults => {:channel => 'all'},
571   :auth_path => 'edit::whitelist'
572 plugin.map 'bans list whitelist', :action => 'list_whitelist',
573   :auth_path => 'list::whitelist'
574
575 plugin.map 'bans add masshl :num :perc :action :timer :channel *reason', :action => 'add_masshl',
576   :requirements => {:num => /\d{1,2}/, :perc => /\d{1,2}\%/,:action => BansPlugin::ActionRe, :timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelAllRe},
577   :defaults => {:action => 'silence', :timer => "0s", :channel => 'all', :reason => 'masshl'},
578   :auth_path => 'edit::masshl'
579 plugin.map 'bans add masshl :perc :num :action :timer :channel *reason', :action => 'add_masshl',
580   :requirements => {:num => /\d{1,2}/, :perc => /\d{1,2}\%/,:action => BansPlugin::ActionRe, :timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelAllRe},
581   :defaults => {:action => 'silence', :timer => "0s", :channel => 'all', :reason => 'masshl'},
582   :auth_path => 'edit::masshl'
583 plugin.map 'bans add masshl :perc :action :timer :channel *reason', :action => 'add_masshl',
584   :requirements => {:num => /\d{1,2}/, :perc => /\d{1,2}\%/,:action => BansPlugin::ActionRe, :timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelAllRe},
585   :defaults => {:num => 0, :action => 'silence', :timer => "0s", :channel => 'all', :reason => 'masshl'},
586   :auth_path => 'edit::masshl'
587 plugin.map 'bans add masshl :num :action :timer :channel *reason', :action => 'add_masshl',
588   :requirements => {:num => /\d{1,2}/, :perc => /\d{1,2}\%/,:action => BansPlugin::ActionRe, :timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelAllRe},
589   :defaults => {:perc => "0%", :action => 'silence', :timer => "0s", :channel => 'all', :reason => 'masshl'},
590   :auth_path => 'edit::masshl'
591 plugin.map 'bans rm masshl :idx', :action => 'rm_masshl',
592   :requirements => {:channel => nil, :num => BansPlugin::IdxRe},
593   :auth_path => 'edit::masshl'
594 plugin.map 'bans rm masshl :idx :channel', :action => 'rm_masshl',
595   :requirements => {:channel => BansPlugin::ChannelAllRe},
596   :defaults => {:channel => nil},
597   :auth_path => 'edit::masshl'
598 plugin.map 'bans list masshl', :action => 'list_masshl',
599   :auth_path => 'list::masshl'
600 plugin.map 'bans list masshl :channel', :action => 'list_masshl',
601   :defaults => {:channel => nil},
602   :auth_path => 'list::masshl'