]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/bans.rb
script plugin: per-script permissions
[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
39 class BansPlugin < Plugin
40
41   IdxRe = /^\d+$/
42   TimerRe = /^\d+[smhd]$/
43   ChannelRe = /^#+[^\s]+$/
44   ChannelAllRe = /^(?:all|#+[^\s]+)$/
45   ActionRe = /(?:ban|kick|kickban|silence|quiet)/
46
47   def name
48     "bans"
49   end
50
51   def make_badword_rx(txt)
52     return /\b(?:#{txt})\b/i
53   end
54
55   def initialize
56     super
57
58     # Convert old BadWordActions, which were simpler and labelled :bans
59     if @registry.has_key? :bans
60       badwords = Array.new
61       bans = @registry[:bans]
62       @registry[:bans].each { |ar|
63         case ar[0]
64         when "quietban"
65           action = :silence
66         when "kickban"
67           action = :kickban
68         else
69           # Shouldn't happen
70           warning "Unknown action in old data #{ar.inspect} -- entry ignored"
71           next
72         end
73         bans.delete(ar)
74         chan = ar[1].downcase
75         regexp = make_badword_rx(ar[2])
76         badwords << BadWordAction.new(regexp, action, chan, "0s", "")
77       }
78       @registry[:badwords] = badwords
79       if bans.length > 0
80         # Store the ones we couldn't convert
81         @registry[:bans] = bans
82       else
83         @registry.delete(:bans)
84       end
85     else
86       @registry[:badwords] = Array.new unless @registry.has_key? :badwords
87     end
88
89     # Convert old WhitelistEntries, which were simpler and labelled :bansmasks
90     if @registry.has_key? :bans
91       wl = Array.new
92       @registry[:bansmasks].each { |mask|
93         badwords << WhitelistEntry.new(mask, "all")
94       }
95       @registry[:whitelist] = wl
96       @registry.delete(:bansmasks)
97     else
98       @registry[:whitelist] = Array.new unless @registry.has_key? :whitelist
99     end
100
101     @registry[:onjoin] = Array.new unless @registry.has_key? :onjoin
102   end
103
104   def help(plugin, topic="")
105     case plugin
106     when "ban"
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"
108     when "unban"
109       return "unban <nick/hostmask> [#channel]: unban a user from the given channel. defaults to the current channel"
110     when "kick"
111       return "kick <nick> [#channel] [reason ...]: kick a user from the given channel with the given reason. defaults to the current channel, no reason"
112     when "kickban"
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"
114     when "silence"
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"
116     when "unsilence"
117       return "unsilence <nick/hostmask> [#channel]: allow the given user to talk on the given channel. defaults to the current channel"
118     when "bans"
119       case topic
120       when "add"
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"
122       when "add onjoin"
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"
124       when "add badword"
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"
126       when "add whitelist"
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"
128       when "rm"
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."
130       when "list"
131         return"bans list <onjoin|badword|whitelist>: lists all onjoin or badwords or whitelist entries"
132       end
133     end
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"
135   end
136
137   def listen(m)
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)
142     }
143
144     @registry[:badwords].each { |badword|
145       next unless ['all', m.target.downcase].include?(badword.channel)
146       next unless badword.regexp.match(m.plainmessage)
147
148       m.reply "bad word detected! #{badword.action} for #{badword.timer} because: #{badword.reason}"
149       do_cmd(badword.action.to_sym, m.source.nick, m.target, badword.timer, badword.reason)
150       return
151     }
152   end
153
154   def join(m)
155     @registry[:whitelist].each { |white|
156       next unless ['all', m.target.downcase].include?(white.channel)
157       return if m.source.matches?(white.host)
158     }
159
160     @registry[:onjoin].each { |auto|
161       next unless ['all', m.target.downcase].include?(auto.channel)
162       next unless m.source.matches? auto.host
163
164       do_cmd(auto.action.to_sym, m.source.nick, m.target, "0s", auto.reason)
165       return
166     }
167   end
168
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)
173   end
174
175   def unban_user(m, params=nil)
176     nick, channel = params[:nick], check_channel(m, params[:channel])
177     do_cmd(:unban, nick, channel)
178   end
179
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)
184   end
185
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)
190   end
191
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)
196   end
197
198   def unsilence_user(m, params=nil)
199     nick, channel = params[:nick], check_channel(m, params[:channel])
200     do_cmd(:unsilence, nick, channel)
201   end
202
203   def add_onjoin(m, params=nil)
204     begin
205       host, channel = m.server.new_netmask(params[:host]), params[:channel].downcase
206       action, reason = params[:action], params[:reason].to_s
207
208       autos = @registry[:onjoin]
209       autos << OnJoinAction.new(host, action, channel, reason.dup)
210       @registry[:onjoin] = autos
211
212       m.okay
213     rescue
214       error $!
215       m.reply $!
216     end
217   end
218
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}'"
223     }
224   end
225
226   def rm_onjoin(m, params=nil)
227     autos = @registry[:onjoin]
228     count = autos.length
229
230     idx = nil
231     idx = params[:idx].to_i if params[:idx]
232
233     if idx
234       if idx > count
235         m.reply "No such onjoin \##{idx}"
236         return
237       end
238       autos.delete_at(idx-1)
239     else
240       begin
241         host = m.server.new_netmask(params[:host])
242         channel = params[:channel].downcase
243
244         autos.each { |rule|
245           next unless ['all', rule.channel].include?(channel)
246           autos.delete rule if rule.host == host
247         }
248       rescue
249         error $!
250         m.reply $!
251       end
252     end
253     @registry[:onjoin] = autos
254     if count > autos.length
255       m.okay
256     else
257       m.reply "No matching onjoin rule for #{host} found"
258     end
259   end
260
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
264
265     badwords = @registry[:badwords]
266     badwords << BadWordAction.new(regexp, action, channel, timer, reason)
267     @registry[:badwords] = badwords
268
269     m.okay
270   end
271
272   def list_badword(m, params=nil)
273     m.reply "badword rules: #{@registry[:badwords].length}"
274
275     @registry[:badwords].each_with_index { |badword, idx|
276       m.reply "\##{idx+1}: #{badword.regexp.source} | #{badword.action} | #{badword.channel} | #{badword.timer} | #{badword.reason}"
277     }
278   end
279
280   def rm_badword(m, params=nil)
281     badwords = @registry[:badwords]
282     count = badwords.length
283
284     idx = nil
285     idx = params[:idx].to_i if params[:idx]
286
287     if idx
288       if idx > count
289         m.reply "No such badword \##{idx}"
290         return
291       end
292       badwords.delete_at(idx-1)
293     else
294       channel = params[:channel].downcase
295
296       regexp = make_badword_rx(params[:regexp])
297       debug "Trying to remove #{regexp.inspect} from #{badwords.inspect}"
298
299       badwords.each { |badword|
300         next unless ['all', badword.channel].include?(channel)
301         debug "Removing #{badword.inspect}" if badword.regexp == regexp
302         badwords.delete(badword) if badword.regexp == regexp
303       }
304     end
305
306     @registry[:badwords] = badwords
307     if count > badwords.length
308       m.okay
309     else
310       m.reply "No matching badword #{regexp} found"
311     end
312   end
313
314   def add_whitelist(m, params=nil)
315     begin
316       host, channel = m.server.new_netmask(params[:host]), params[:channel].downcase
317
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
322
323       m.okay
324     rescue
325       error $!
326       m.reply $!
327     end
328   end
329
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}"
334     }
335   end
336
337   def rm_whitelist(m, params=nil)
338     wl = @registry[:whitelist]
339     count = wl.length
340
341     idx = nil
342     idx = params[:idx].to_i if params[:idx]
343
344     if idx
345       if idx > count
346         m.reply "No such whitelist entry \##{idx}"
347         return
348       end
349       wl.delete_at(idx-1)
350     else
351       begin
352         host = m.server.new_netmask(params[:host])
353         channel = params[:channel].downcase
354
355         wl.each { |rule|
356           next unless ['all', rule.channel].include?(channel)
357           wl.delete rule if rule.host == host
358         }
359       rescue
360         error $!
361         m.reply $!
362       end
363     end
364     @registry[:whitelist] = wl
365     if count > whitelist.length
366       m.okay
367     else
368       m.reply "No host matching #{host}"
369     end
370   end
371
372   private
373   def check_channel(m, strchannel)
374     begin
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)
378
379       return channel
380     rescue
381       error $!
382       m.reply $!
383     end
384   end
385
386   def do_cmd(action, nick, channel, timer_in=nil, reason=nil)
387     case timer_in
388     when nil
389       timer = 0
390     when /^(\d+)s$/
391       timer = $1.to_i
392     when /^(\d+)m$/
393       timer = $1.to_i * 60
394     when /^(\d+)h$/
395       timer = $1.to_i * 60 * 60 
396     when /^(\d+)d$/
397       timer = $1.to_i * 60 * 60 * 24
398     else
399       raise "Wrong time specifications"
400     end
401
402     case action
403     when :ban
404       set_temporary_mode(channel, 'b', nick, timer)
405     when :unban
406       set_mode(channel, "-b", nick)
407     when :kick
408       do_kick(channel, nick, reason)
409     when :kickban
410       set_temporary_mode(channel, 'b', nick, timer)
411       do_kick(channel, nick, reason)
412     when :silence, :quiet
413       set_mode(channel, "+q", nick)
414       @bot.timer.add_once(timer) { set_mode(channel, "-q", nick) } if timer > 0
415     when :unsilence, :unquiet
416       set_mode(channel, "-q", nick)
417     end
418   end
419
420   def set_mode(channel, mode, nick)
421     host = channel.has_user?(nick) ? "*!*@" + channel.get_user(nick).host : nick
422     @bot.mode(channel, mode, host)
423   end
424
425   def set_temporary_mode(channel, mode, nick, timer)
426     host = channel.has_user?(nick) ? "*!*@" + channel.users[nick].host : nick
427     @bot.mode(channel, "+#{mode}", host)
428     return if timer == 0
429     @bot.timer.add_once(timer) { @bot.mode(channel, "-#{mode}", host) }
430   end
431
432   def do_kick(channel, nick, reason="")
433     @bot.kick(channel, nick, reason)
434   end
435 end
436
437 plugin = BansPlugin.new
438
439 plugin.default_auth( 'act', false )
440 plugin.default_auth( 'edit', false )
441 plugin.default_auth( 'list', true )
442
443 plugin.map 'ban :nick :timer :channel', :action => 'ban_user',
444   :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},
445   :defaults => {:timer => nil, :channel => nil},
446   :auth_path => 'act'
447 plugin.map 'unban :nick :channel', :action => 'unban_user',
448   :requirements => {:channel => BansPlugin::ChannelRe},
449   :defaults => {:channel => nil},
450   :auth_path => 'act'
451 plugin.map 'kick :nick :channel *reason', :action => 'kick_user',
452   :requirements => {:channel => BansPlugin::ChannelRe},
453   :defaults => {:channel => nil, :reason => 'requested'},
454   :auth_path => 'act'
455 plugin.map 'kickban :nick :timer :channel *reason', :action => 'kickban_user',
456   :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},
457   :defaults => {:timer => nil, :channel => nil, :reason => 'requested'},
458   :auth_path => 'act'
459 plugin.map 'silence :nick :timer :channel', :action => 'silence_user',
460   :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},
461   :defaults => {:timer => nil, :channel => nil},
462   :auth_path => 'act'
463 plugin.map 'unsilence :nick :channel', :action => 'unsilence_user',
464   :requirements => {:channel => BansPlugin::ChannelRe},
465   :defaults => {:channel => nil},
466   :auth_path => 'act'
467
468 plugin.map 'bans add onjoin :host :action :channel *reason', :action => 'add_onjoin',
469   :requirements => {:action => BansPlugin::ActionRe, :channel => BansPlugin::ChannelAllRe},
470   :defaults => {:action => 'kickban', :channel => 'all', :reason => 'netmask not welcome'},
471   :auth_path => 'edit::onjoin'
472 plugin.map 'bans rm onjoin index :idx', :action => 'rm_onjoin',
473   :requirements => {:num => BansPlugin::IdxRe},
474   :auth_path => 'edit::onjoin'
475 plugin.map 'bans rm onjoin :host :channel', :action => 'rm_onjoin',
476   :requirements => {:channel => BansPlugin::ChannelAllRe},
477   :defaults => {:channel => 'all'},
478   :auth_path => 'edit::onjoin'
479 plugin.map 'bans list onjoin[s]', :action => 'list_onjoin',
480   :auth_path => 'list::onjoin'
481
482 plugin.map 'bans add badword :regexp :action :timer :channel *reason', :action => 'add_badword',
483   :requirements => {:action => BansPlugin::ActionRe, :timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelAllRe},
484   :defaults => {:action => 'silence', :timer => "0s", :channel => 'all', :reason => 'bad word'},
485   :auth_path => 'edit::badword'
486 plugin.map 'bans rm badword index :idx', :action => 'rm_badword',
487   :requirements => {:num => BansPlugin::IdxRe},
488   :auth_path => 'edit::badword'
489 plugin.map 'bans rm badword :regexp :channel', :action => 'rm_badword',
490   :requirements => {:channel => BansPlugin::ChannelAllRe},
491   :defaults => {:channel => 'all'},
492   :auth_path => 'edit::badword'
493 plugin.map 'bans list badword[s]', :action => 'list_badword',
494   :auth_path => 'list::badword'
495
496 plugin.map 'bans add whitelist :host :channel', :action => 'add_whitelist',
497   :requirements => {:channel => BansPlugin::ChannelAllRe},
498   :defaults => {:channel => 'all'},
499   :auth_path => 'edit::whitelist'
500 plugin.map 'bans rm whitelist index :idx', :action => 'rm_whitelist',
501   :requirements => {:num => BansPlugin::IdxRe},
502   :auth_path => 'edit::whitelist'
503 plugin.map 'bans rm whitelist :host :channel', :action => 'rm_whitelist',
504   :requirements => {:channel => BansPlugin::ChannelAllRe},
505   :defaults => {:channel => 'all'},
506   :auth_path => 'edit::whitelist'
507 plugin.map 'bans list whitelist', :action => 'list_whitelist',
508   :auth_path => 'list::whitelist'
509