4 # :title: Bans Plugin v3 for rbot 0.9.11 and later
6 # Author:: Marco Gulino <marco@kmobiletools.org>
7 # Author:: kamu <mr.kamu@gmail.com>
8 # Author:: Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
10 # Copyright:: (C) 2006 Marco Gulino
11 # Copyright:: (C) 2007 kamu, Giuseppe Bilotta
15 # Managing kick and bans, automatically removing bans after timeouts, quiet
16 # bans, and kickban/quietban based on regexp
18 # v1 -> v2 (kamu's version, never released)
20 # * autoactions triggered on join
21 # * action on join or badword can be anything: kick, ban, kickban, quiet
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
31 # * enhanced list manipulation facilities
32 # * fixed regexp usage in requirements for plugin map
33 # * add proper auth management
35 define_structure :OnJoinAction, :host, :action, :channel, :reason
36 define_structure :BadWordAction, :regexp, :action, :channel, :timer, :reason
37 define_structure :WhitelistEntry, :host, :channel
39 class BansPlugin < Plugin
42 TimerRe = /^\d+[smhd]$/
43 ChannelRe = /^#+[^\s]+$/
44 ChannelAllRe = /^(?:all|#+[^\s]+)$/
45 ActionRe = /(?:ban|kick|kickban|silence|quiet)/
51 def make_badword_rx(txt)
52 return /\b(?:#{txt})\b/i
58 # Convert old BadWordActions, which were simpler and labelled :bans
59 if @registry.has_key? :bans
61 bans = @registry[:bans]
62 @registry[:bans].each { |ar|
70 warning "Unknown action in old data #{ar.inspect} -- entry ignored"
75 regexp = make_badword_rx(ar[2])
76 badwords << BadWordAction.new(regexp, action, chan, "0s", "")
78 @registry[:badwords] = badwords
80 # Store the ones we couldn't convert
81 @registry[:bans] = bans
83 @registry.delete(:bans)
86 @registry[:badwords] = Array.new unless @registry.has_key? :badwords
89 # Convert old WhitelistEntries, which were simpler and labelled :bansmasks
90 if @registry.has_key? :bans
92 @registry[:bansmasks].each { |mask|
93 badwords << WhitelistEntry.new(mask, "all")
95 @registry[:whitelist] = wl
96 @registry.delete(:bansmasks)
98 @registry[:whitelist] = Array.new unless @registry.has_key? :whitelist
101 @registry[:onjoin] = Array.new unless @registry.has_key? :onjoin
104 def help(plugin, topic="")
107 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"
109 return "unban <nick/hostmask> [#channel]: unban a user from the given channel. defaults to the current channel"
111 return "kick <nick> [#channel] [reason ...]: kick a user from the given channel with the given reason. defaults to the current channel, no reason"
113 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"
115 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"
117 return "unsilence <nick/hostmask> [#channel]: allow the given user to talk on the given channel. defaults to the current channel"
121 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"
123 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"
125 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"
127 return "bans add whitelist <hostmask> [#channel|all]: add the given hostmask to the whitelist. no autoaction will be triggered by users on the whitelist"
129 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."
131 return"bans list <onjoin|badword|whitelist>: lists all onjoin or badwords or whitelist entries"
134 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"
138 return unless m.respond_to?(:public?) and m.public?
139 @registry[:whitelist].each { |white|
140 next unless ['all', m.target.downcase].include?(white.channel)
141 return if m.source.matches?(white.host)
144 @registry[:badwords].each { |badword|
145 next unless ['all', m.target.downcase].include?(badword.channel)
146 next unless badword.regexp.match(m.message)
148 do_cmd(badword.action.to_sym, m.source.nick, m.target, badword.timer, badword.reason)
149 m.reply "bad word detected! #{badword.action} for #{badword.timer} because: #{badword.reason}"
155 @registry[:whitelist].each { |white|
156 next unless ['all', m.target.downcase].include?(white.channel)
157 return if m.source.matches?(white.host)
160 @registry[:onjoin].each { |auto|
161 next unless ['all', m.target.downcase].include?(auto.channel)
162 next unless m.source.matches? auto.host
164 do_cmd(auto.action.to_sym, m.source.nick, m.target, "0s", auto.reason)
169 def ban_user(m, params=nil)
170 nick, channel = params[:nick], check_channel(m, params[:channel])
171 timer = params[:timer]
172 do_cmd(:ban, nick, channel, timer)
175 def unban_user(m, params=nil)
176 nick, channel = params[:nick], check_channel(m, params[:channel])
177 do_cmd(:unban, nick, channel)
180 def kick_user(m, params=nil)
181 nick, channel = params[:nick], check_channel(m, params[:channel])
182 reason = params[:reason].to_s
183 do_cmd(:kick, nick, channel, "0s", reason)
186 def kickban_user(m, params=nil)
187 nick, channel, reason = params[:nick], check_channel(m, params[:channel])
188 timer, reason = params[:timer], params[:reason].to_s
189 do_cmd(:kickban, nick, channel, timer, reason)
192 def silence_user(m, params=nil)
193 nick, channel = params[:nick], check_channel(m, params[:channel])
194 timer = params[:timer]
195 do_cmd(:silence, nick, channel, timer)
198 def unsilence_user(m, params=nil)
199 nick, channel = params[:nick], check_channel(m, params[:channel])
200 do_cmd(:unsilence, nick, channel)
203 def add_onjoin(m, params=nil)
205 host, channel = m.server.new_netmask(params[:host]), params[:channel].downcase
206 action, reason = params[:action], params[:reason].to_s
208 autos = @registry[:onjoin]
209 autos << OnJoinAction.new(host, action, channel, reason.dup)
210 @registry[:onjoin] = autos
219 def list_onjoin(m, params=nil)
220 m.reply "onjoin rules: #{@registry[:onjoin].length}"
221 @registry[:onjoin].each_with_index { |auto, idx|
222 m.reply "\##{idx+1}: #{auto.host} | #{auto.action} | #{auto.channel} | '#{auto.reason}'"
226 def rm_onjoin(m, params=nil)
227 autos = @registry[:onjoin]
231 idx = params[:idx].to_i if params[:idx]
235 m.reply "No such onjoin \##{idx}"
238 autos.delete_at(idx-1)
241 host = m.server.new_netmask(params[:host])
242 channel = params[:channel].downcase
245 next unless ['all', rule.channel].include?(channel)
246 autos.delete rule if rule.host == host
253 @registry[:onjoin] = autos
254 if count > autos.length
257 m.reply "No matching onjoin rule for #{host} found"
261 def add_badword(m, params=nil)
262 regexp, channel = make_badword_rx(params[:regexp]), params[:channel].downcase.dup
263 action, timer, reason = params[:action], params[:timer].dup, params[:reason].to_s
265 badwords = @registry[:badwords]
266 badwords << BadWordAction.new(regexp, action, channel, timer, reason)
267 @registry[:badwords] = badwords
272 def list_badword(m, params=nil)
273 m.reply "badword rules: #{@registry[:badwords].length}"
275 @registry[:badwords].each_with_index { |badword, idx|
276 m.reply "\##{idx+1}: #{badword.regexp.source} | #{badword.action} | #{badword.channel} | #{badword.timer} | #{badword.reason}"
280 def rm_badword(m, params=nil)
281 badwords = @registry[:badwords]
282 count = badwords.length
285 idx = params[:idx].to_i if params[:idx]
289 m.reply "No such badword \##{idx}"
292 badwords.delete_at(idx-1)
294 channel = params[:channel].downcase
296 regexp = make_badword_rx(params[:regexp])
297 debug "Trying to remove #{regexp.inspect} from #{badwords.inspect}"
299 badwords.each { |badword|
300 next unless ['all', badword.channel].include?(channel)
301 debug "Removing #{badword.inspect}" if badword == regexp
302 badwords.delete(badword) if badword == regexp
306 @registry[:badwords] = badwords
307 if count > badwords.length
310 m.reply "No matching badword #{regexp} found"
314 def add_whitelist(m, params=nil)
316 host, channel = m.server.new_netmask(params[:host]), params[:channel].downcase
318 # TODO check if a whitelist entry for this host already exists
319 whitelist = @registry[:whitelist]
320 whitelist << WhitelistEntry.new(host, channel)
321 @registry[:whitelist] = whitelist
330 def list_whitelist(m, params=nil)
331 m.reply "whitelist entries: #{@registry[:whitelist].length}"
332 @registry[:whitelist].each_with_index { |auto, idx|
333 m.reply "\##{idx+1}: #{auto.host} | #{auto.channel}"
337 def rm_whitelist(m, params=nil)
338 wl = @registry[:whitelist]
342 idx = params[:idx].to_i if params[:idx]
346 m.reply "No such whitelist entry \##{idx}"
352 host = m.server.new_netmask(params[:host])
353 channel = params[:channel].downcase
356 next unless ['all', rule.channel].include?(channel)
357 wl.delete rule if rule.host == host
364 @registry[:whitelist] = wl
365 if count > whitelist.length
368 m.reply "No host matching #{host}"
373 def check_channel(m, strchannel)
375 raise "must specify channel if using privmsg" if m.private? and not strchannel
376 channel = m.server.channel(strchannel) || m.target
377 raise "I am not in that channel" unless channel.has_user?(@bot.nick)
386 def do_cmd(action, nick, channel, timer_in=nil, reason=nil)
395 timer = $1.to_i * 60 * 60
397 timer = $1.to_i * 60 * 60 * 24
399 raise "Wrong time specifications"
404 set_mode(channel, "+b", nick)
405 @bot.timer.add_once(timer) { set_mode(channel, "-b", nick) } if timer > 0
407 set_mode(channel, "-b", nick)
409 do_kick(channel, nick, reason)
411 set_mode(channel, "+b", nick)
412 @bot.timer.add_once(timer) { set_mode(channel, "-b", nick) } if timer > 0
413 do_kick(channel, nick, reason)
414 when :silence, :quiet
415 set_mode(channel, "+q", nick)
416 @bot.timer.add_once(timer) { set_mode(channel, "-q", nick) } if timer > 0
417 when :unsilence, :unquiet
418 set_mode(channel, "-q", nick)
422 def set_mode(channel, mode, nick)
423 host = channel.has_user?(nick) ? "*!*@" + channel.get_user(nick).host : nick
424 @bot.mode(channel, mode, host)
427 def do_kick(channel, nick, reason="")
428 @bot.kick(channel, nick, reason)
432 plugin = BansPlugin.new
434 plugin.default_auth( 'act', false )
435 plugin.default_auth( 'edit', false )
436 plugin.default_auth( 'list', true )
438 plugin.map 'ban :nick :timer :channel', :action => 'ban_user',
439 :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},
440 :defaults => {:timer => nil, :channel => nil},
442 plugin.map 'unban :nick :channel', :action => 'unban_user',
443 :requirements => {:channel => BansPlugin::ChannelRe},
444 :defaults => {:channel => nil},
446 plugin.map 'kick :nick :channel *reason', :action => 'kick_user',
447 :requirements => {:channel => BansPlugin::ChannelRe},
448 :defaults => {:channel => nil, :reason => 'requested'},
450 plugin.map 'kickban :nick :timer :channel *reason', :action => 'kickban_user',
451 :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},
452 :defaults => {:timer => nil, :channel => nil, :reason => 'requested'},
454 plugin.map 'silence :nick :timer :channel', :action => 'silence_user',
455 :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},
456 :defaults => {:timer => nil, :channel => nil},
458 plugin.map 'unsilence :nick :channel', :action => 'unsilence_user',
459 :requirements => {:channel => BansPlugin::ChannelRe},
460 :defaults => {:channel => nil},
463 plugin.map 'bans add onjoin :host :action :channel *reason', :action => 'add_onjoin',
464 :requirements => {:action => BansPlugin::ActionRe, :channel => BansPlugin::ChannelAllRe},
465 :defaults => {:action => 'kickban', :channel => 'all', :reason => 'netmask not welcome'},
466 :auth_path => 'edit::onjoin'
467 plugin.map 'bans rm onjoin index :idx', :action => 'rm_onjoin',
468 :requirements => {:num => BansPlugin::IdxRe},
469 :auth_path => 'edit::onjoin'
470 plugin.map 'bans rm onjoin :host :channel', :action => 'rm_onjoin',
471 :requirements => {:channel => BansPlugin::ChannelAllRe},
472 :defaults => {:channel => 'all'},
473 :auth_path => 'edit::onjoin'
474 plugin.map 'bans list onjoin[s]', :action => 'list_onjoin',
475 :auth_path => 'list::onjoin'
477 plugin.map 'bans add badword :regexp :action :timer :channel *reason', :action => 'add_badword',
478 :requirements => {:action => BansPlugin::ActionRe, :timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelAllRe},
479 :defaults => {:action => 'silence', :timer => "0s", :channel => 'all', :reason => 'bad word'},
480 :auth_path => 'edit::badword'
481 plugin.map 'bans rm badword index :idx', :action => 'rm_badword',
482 :requirements => {:num => BansPlugin::IdxRe},
483 :auth_path => 'edit::badword'
484 plugin.map 'bans rm badword :regexp :channel', :action => 'rm_badword',
485 :requirements => {:channel => BansPlugin::ChannelAllRe},
486 :defaults => {:channel => 'all'},
487 :auth_path => 'edit::badword'
488 plugin.map 'bans list badword[s]', :action => 'list_badword',
489 :auth_path => 'list::badword'
491 plugin.map 'bans add whitelist :host :channel', :action => 'add_whitelist',
492 :requirements => {:channel => BansPlugin::ChannelAllRe},
493 :defaults => {:channel => 'all'},
494 :auth_path => 'edit::whitelist'
495 plugin.map 'bans rm whitelist index :idx', :action => 'rm_whitelist',
496 :requirements => {:num => BansPlugin::IdxRe},
497 :auth_path => 'edit::whitelist'
498 plugin.map 'bans rm whitelist :host :channel', :action => 'rm_whitelist',
499 :requirements => {:channel => BansPlugin::ChannelAllRe},
500 :defaults => {:channel => 'all'},
501 :auth_path => 'edit::whitelist'
502 plugin.map 'bans list whitelist', :action => 'list_whitelist',
503 :auth_path => 'list::whitelist'