1 # BansPlugin v3 for 0.9.11
\r
3 # Managing kick and bans, automatically removing bans after timeouts, quiet bans, and kickban/quietban based on regexp
\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
9 # v1 -> v2 (kamu's version, never released)
\r
11 # * autoactions triggered on join
\r
12 # * action on join or badword can be anything: kick, ban, kickban, quiet
\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
24 # Licensed under GPL V2.
\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
31 class BansPlugin < Plugin
\r
34 TimerRe = /^\d+[smhd]$/
\r
35 ChannelRe = /^#+[^\s]+$/
\r
36 ChannelAllRe = /^(?:all|#+[^\s]+)$/
\r
37 ActionRe = /(?:ban|kick|kickban|silence|quiet)/
\r
43 def make_badword_rx(txt)
\r
44 return /\b#{txt}\b/i
\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
62 warn "Unknown action in old data #{ar.inspect} -- entry ignored"
\r
67 regexp = make_badword_rx(ar[2])
\r
68 badwords << BadWordAction.new(regexp, action, chan, "0s", "")
\r
70 @registry[:badwords] = badwords
\r
72 # Store the ones we couldn't convert
\r
73 @registry[:bans] = bans
\r
75 @registry.delete(:bans)
\r
78 @registry[:badwords] = Array.new unless @registry.has_key? :badwords
\r
81 # Convert old WhitelistEntries, which were simpler and labelled :bansmasks
\r
82 if @registry.has_key? :bans
\r
84 @registry[:bansmasks].each { |mask|
\r
85 badwords << WhitelistEntry.new(mask, "all")
\r
87 @registry[:whitelist] = wl
\r
88 @registry.delete(:bansmasks)
\r
90 @registry[:whitelist] = Array.new unless @registry.has_key? :whitelist
\r
93 @registry[:onjoin] = Array.new unless @registry.has_key? :onjoin
\r
96 def help(plugin, topic="")
\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
101 return "unban <nick/hostmask> [#channel]: unban a user from the given channel. defaults to the current channel"
\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
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
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
109 return "unsilence <nick/hostmask> [#channel]: allow the given user to talk on the given channel. defaults to the current channel"
\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
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
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
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
123 return"bans list <onjoin|badword|whitelist>: lists all onjoin or badwords or whitelist entries"
\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
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
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
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
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
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
156 do_cmd(auto.action.to_sym, m.source.nick, m.target, "0s", auto.reason)
\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
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
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
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
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
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
195 def add_onjoin(m, params=nil)
\r
197 host, channel = m.server.new_netmask(params[:host]), params[:channel].downcase
\r
198 action, reason = params[:action], params[:reason].to_s
\r
200 autos = @registry[:onjoin]
\r
201 autos << OnJoinAction.new(host, action, channel, reason.dup)
\r
202 @registry[:onjoin] = autos
\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
218 def rm_onjoin(m, params=nil)
\r
219 autos = @registry[:onjoin]
\r
220 count = autos.length
\r
222 idx = params[:idx].to_i
\r
226 m.reply "No such onjoin \##{idx}"
\r
229 autos.delete_at(idx-1)
\r
232 host = m.server.new_netmask(params[:host])
\r
233 channel = params[:channel].downcase
\r
235 autos.each { |rule|
\r
236 next unless ['all', rule.channel].include?(channel)
\r
237 autos.delete rule if rule.host == host
\r
244 @registry[:onjoin] = autos
\r
245 m.okay if count > autos.length
\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
252 badwords = @registry[:badwords]
\r
253 badwords << BadWordAction.new(regexp, action, channel, timer, reason)
\r
254 @registry[:badwords] = badwords
\r
259 def list_badword(m, params=nil)
\r
260 m.reply "badword rules: #{@registry[:badwords].length}"
\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
267 def rm_badword(m, params=nil)
\r
268 badwords = @registry[:badwords]
\r
269 count = badwords.length
\r
271 idx = params[:idx].to_i
\r
275 m.reply "No such badword \##{idx}"
\r
278 badwords.delete_at(idx-1)
\r
280 channel = params[:channel].downcase
\r
282 regexp = make_badword_rx(params[:regexp])
\r
283 debug "Trying to remove #{regexp.inspect} from #{badwords.inspect}"
\r
285 badwords.each { |badword|
\r
286 next unless ['all', badword.channel].include?(channel)
\r
287 badwords.delete(badword) if badword == regexp
\r
291 @registry[:badwords] = badwords
\r
292 m.okay if count > badwords.length
\r
295 def add_whitelist(m, params=nil)
\r
297 host, channel = m.server.new_netmask(params[:host]), params[:channel].downcase
\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
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
318 def rm_whitelist(m, params=nil)
\r
319 wl = @registry[:whitelist]
\r
322 idx = params[:idx].to_i
\r
326 m.reply "No such whitelist entry \##{idx}"
\r
329 wl.delete_at(idx-1)
\r
332 host = m.server.new_netmask(params[:host])
\r
333 channel = params[:channel].downcase
\r
336 next unless ['all', rule.channel].include?(channel)
\r
337 wl.delete rule if rule.host == host
\r
344 @registry[:whitelist] = wl
\r
345 m.okay if count > whitelist.length
\r
349 def check_channel(m, strchannel)
\r
351 raise "must specify channel if using privmsg" if m.private? and not strchannel
\r
352 channel = m.server.channel(strchannel) || m.target
\r
353 raise "I am not in that channel" unless channel.has_user?(@bot.nick)
\r
362 def do_cmd(action, nick, channel, timer_in=nil, reason=nil)
\r
369 timer = $1.to_i * 60
\r
371 timer = $1.to_i * 60 * 60
\r
373 timer = $1.to_i * 60 * 60 * 24
\r
375 raise "Wrong time specifications"
\r
380 set_mode(channel, "+b", nick)
\r
381 @bot.timer.add_once(timer) { set_mode(channel, "-b", nick) } if timer > 0
\r
383 set_mode(channel, "-b", nick)
\r
385 do_kick(channel, nick, reason)
\r
387 set_mode(channel, "+b", nick)
\r
388 @bot.timer.add_once(timer) { set_mode(channel, "-b", nick) } if timer > 0
\r
389 do_kick(channel, nick, reason)
\r
390 when :silence, :quiet
\r
391 set_mode(channel, "+q", nick)
\r
392 @bot.timer.add_once(timer) { set_mode(channel, "-q", nick) } if timer > 0
\r
393 when :unsilence, :unquiet
\r
394 set_mode(channel, "-q", nick)
\r
398 def set_mode(channel, mode, nick)
\r
399 host = channel.has_user?(nick) ? "*!*@" + channel.get_user(nick).host : nick
\r
400 @bot.mode(channel, mode, host)
\r
403 def do_kick(channel, nick, reason="")
\r
404 @bot.kick(channel, nick, reason)
\r
408 plugin = BansPlugin.new
\r
410 plugin.default_auth( 'act', false )
\r
411 plugin.default_auth( 'edit', false )
\r
412 plugin.default_auth( 'list', true )
\r
414 plugin.map 'ban :nick :timer :channel', :action => 'ban_user',
\r
415 :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},
\r
416 :defaults => {:timer => nil, :channel => nil},
\r
417 :auth_path => 'act'
\r
418 plugin.map 'unban :nick :channel', :action => 'unban_user',
\r
419 :requirements => {:channel => BansPlugin::ChannelRe},
\r
420 :defaults => {:channel => nil},
\r
421 :auth_path => 'act'
\r
422 plugin.map 'kick :nick :channel *reason', :action => 'kick_user',
\r
423 :requirements => {:channel => BansPlugin::ChannelRe},
\r
424 :defaults => {:channel => nil, :reason => 'requested'},
\r
425 :auth_path => 'act'
\r
426 plugin.map 'kickban :nick :timer :channel *reason', :action => 'kickban_user',
\r
427 :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},
\r
428 :defaults => {:timer => nil, :channel => nil, :reason => 'requested'},
\r
429 :auth_path => 'act'
\r
430 plugin.map 'silence :nick :timer :channel', :action => 'silence_user',
\r
431 :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},
\r
432 :defaults => {:timer => nil, :channel => nil},
\r
433 :auth_path => 'act'
\r
434 plugin.map 'unsilence :nick :channel', :action => 'unsilence_user',
\r
435 :requirements => {:channel => BansPlugin::ChannelRe},
\r
436 :defaults => {:channel => nil},
\r
437 :auth_path => 'act'
\r
439 plugin.map 'bans add onjoin :host :action :channel *reason', :action => 'add_onjoin',
\r
440 :requirements => {:action => BansPlugin::ActionRe, :channel => BansPlugin::ChannelAllRe},
\r
441 :defaults => {:action => 'kickban', :channel => 'all', :reason => 'netmask not welcome'},
\r
442 :auth_path => 'edit::onjoin'
\r
443 plugin.map 'bans rm onjoin index :idx', :action => 'rm_onjoin',
\r
444 :requirements => {:num => BansPlugin::IdxRe},
\r
445 :auth_path => 'edit::onjoin'
\r
446 plugin.map 'bans rm onjoin :host :channel', :action => 'rm_onjoin',
\r
447 :requirements => {:channel => BansPlugin::ChannelAllRe},
\r
448 :defaults => {:channel => 'all'},
\r
449 :auth_path => 'edit::onjoin'
\r
450 plugin.map 'bans list onjoin[s]', :action => 'list_onjoin',
\r
451 :auth_path => 'list::onjoin'
\r
453 plugin.map 'bans add badword :regexp :action :timer :channel *reason', :action => 'add_badword',
\r
454 :requirements => {:action => BansPlugin::ActionRe, :timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelAllRe},
\r
455 :defaults => {:action => 'silence', :timer => "0s", :channel => 'all', :reason => 'bad word'},
\r
456 :auth_path => 'edit::badword'
\r
457 plugin.map 'bans rm badword index :idx', :action => 'rm_badword',
\r
458 :requirements => {:num => BansPlugin::IdxRe},
\r
459 :auth_path => 'edit::badword'
\r
460 plugin.map 'bans rm badword :regexp :channel', :action => 'rm_badword',
\r
461 :requirements => {:channel => BansPlugin::ChannelAllRe},
\r
462 :defaults => {:channel => 'all'},
\r
463 :auth_path => 'edit::badword'
\r
464 plugin.map 'bans list badword[s]', :action => 'list_badword',
\r
465 :auth_path => 'list::badword'
\r
467 plugin.map 'bans add whitelist :host :channel', :action => 'add_whitelist',
\r
468 :requirements => {:channel => BansPlugin::ChannelAllRe},
\r
469 :defaults => {:channel => 'all'},
\r
470 :auth_path => 'edit::whitelist'
\r
471 plugin.map 'bans rm whitelist index :idx', :action => 'rm_whitelist',
\r
472 :requirements => {:num => BansPlugin::IdxRe},
\r
473 :auth_path => 'edit::whitelist'
\r
474 plugin.map 'bans rm whitelist :host :channel', :action => 'rm_whitelist',
\r
475 :requirements => {:channel => BansPlugin::ChannelAllRe},
\r
476 :defaults => {:channel => 'all'},
\r
477 :auth_path => 'edit::whitelist'
\r
478 plugin.map 'bans list whitelist', :action => 'list_whitelist',
\r
479 :auth_path => 'list::whitelist'
\r