]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blobdiff - data/rbot/plugins/bans.rb
plugin(script): remove deprecated $SAFE
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / bans.rb
index 8a8f3389f84d18f77da73e18e5f19d119189b5db..99b664dfd91b04c641a6c30ca0d6640dd5d79d16 100644 (file)
-# BansPlugin v3 for 0.9.11\r
-#\r
-# Managing kick and bans, automatically removing bans after timeouts, quiet bans, and kickban/quietban based on regexp\r
-#\r
-# (c) 2006 Marco Gulino <marco@kmobiletools.org>\r
-# (c) 2007 kamu <mr.kamu@gmail.com>\r
-# (c) 2007 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>\r
-#\r
-# v1 -> v2 (kamu's version, never released)\r
-#   * reworked\r
-#   * autoactions triggered on join\r
-#   * action on join or badword can be anything: kick, ban, kickban, quiet\r
-#\r
-# v2 -> v3 (GB)\r
-#   * remove the 'bans' prefix from most of the commands\r
-#   * (un)quiet has been renamed to (un)silence because 'quiet' was used to tell the bot to keep quiet\r
-#   * both (un)quiet and (un)silence are accepted as actions\r
-#   * use the more descriptive 'onjoin' term for autoactions\r
-#   * convert v1's (0.9.10) :bans and :bansmasks to BadWordActions and WhitelistEntries\r
-#   * enhanced list manipulation facilities\r
-#   * fixed regexp usage in requirements for plugin map\r
-#   * add proper auth management\r
-#\r
-# Licensed under GPL V2.\r
-\r
-OnJoinAction = Struct.new("OnJoinAction", :host, :action, :channel, :reason)\r
-BadWordAction = Struct.new("BadWordAction", :regexp, :action, :channel, :timer, :reason)\r
-WhitelistEntry = Struct.new("WhitelistEntry", :host, :channel)\r
-\r
-\r
-class BansPlugin < Plugin\r
-\r
-  IdxRe = /^\d+$/\r
-  TimerRe = /^\d+[smhd]$/\r
-  ChannelRe = /^#+[^\s]+$/\r
-  ChannelAllRe = /^(?:all|#+[^\s]+)$/\r
-  ActionRe = /(?:ban|kick|kickban|silence|quiet)/\r
-\r
-  def name\r
-    "bans"\r
-  end\r
-\r
-  def make_badword_rx(txt)\r
-    return /\b#{txt}\b/i\r
-  end\r
-\r
-  def initialize\r
-    super\r
-\r
-    # Convert old BadWordActions, which were simpler and labelled :bans\r
-    if @registry.has_key? :bans\r
-      badwords = Array.new\r
-      bans = @registry[:bans]\r
-      @registry[:bans].each { |ar|\r
-        case ar[0]\r
-        when "quietban"\r
-          action = :silence\r
-        when "kickban"\r
-          action = :kickban\r
-        else\r
-          # Shouldn't happen\r
-          warn "Unknown action in old data #{ar.inspect} -- entry ignored"\r
-          next\r
-        end\r
-        bans.delete(ar)\r
-        chan = ar[1]\r
-        regexp = make_badword_rx(ar[2])\r
-        badwords << BadWordAction.new(regexp, action, chan, "0s", "")\r
-      }\r
-      @registry[:badwords] = badwords\r
-      if bans.length > 0\r
-        # Store the ones we couldn't convert\r
-        @registry[:bans] = bans\r
-      else\r
-        @registry.delete(:bans)\r
-      end\r
-    else\r
-      @registry[:badwords] = Array.new unless @registry.has_key? :badwords\r
-    end\r
-\r
-    # Convert old WhitelistEntries, which were simpler and labelled :bansmasks\r
-    if @registry.has_key? :bans\r
-      wl = Array.new\r
-      @registry[:bansmasks].each { |mask|\r
-        badwords << WhitelistEntry.new(mask, "all")\r
-      }\r
-      @registry[:whitelist] = wl\r
-      @registry.delete(:bansmasks)\r
-    else\r
-      @registry[:whitelist] = Array.new unless @registry.has_key? :whitelist\r
-    end\r
-\r
-    @registry[:onjoin] = Array.new unless @registry.has_key? :onjoin\r
-  end\r
-\r
-  def help(plugin, topic="")\r
-    case plugin\r
-    when "ban"\r
-      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
-    when "unban"\r
-      return "unban <nick/hostmask> [#channel]: unban a user from the given channel. defaults to the current channel"\r
-    when "kick"\r
-      return "kick <nick> [#channel] [reason ...]: kick a user from the given channel with the given reason. defaults to the current channel, no reason"\r
-    when "kickban"\r
-      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
-    when "silence"\r
-      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
-    when "unsilence"\r
-      return "unsilence <nick/hostmask> [#channel]: allow the given user to talk on the given channel. defaults to the current channel"\r
-    when "bans"\r
-      case topic\r
-      when "add"\r
-        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
-      when "add onjoin"\r
-        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
-      when "add badword"\r
-        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
-      when "add whitelist"\r
-        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
-      when "rm"\r
-        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
-      when "list"\r
-        return"bans list <onjoin|badword|whitelist>: lists all onjoin or badwords or whitelist entries"\r
-      end\r
-    end\r
-    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
-  end\r
-\r
-  def listen(m)\r
-    return unless m.respond_to?(:public?) and m.public?\r
-    @registry[:whitelist].each { |white|\r
-      next unless ['all', m.target.downcase].include?(white.channel)\r
-      return if m.source.matches?(white.host)\r
-    }\r
-\r
-    @registry[:badwords].each { |badword|\r
-      next unless ['all', m.target.downcase].include?(badword.channel)\r
-      next unless badword.regexp.match(m.message)\r
-\r
-      do_cmd(badword.action.to_sym, m.source.nick, m.target, badword.timer, badword.reason)\r
-      m.reply "bad word detected! #{badword.action} for #{badword.timer} because: #{badword.reason}"\r
-      return\r
-    }\r
-  end\r
-\r
-  def join(m)\r
-    @registry[:whitelist].each { |white|\r
-      next unless ['all', m.target.downcase].include?(white.channel)\r
-      return if m.source.matches?(white.host)\r
-    }\r
-\r
-    @registry[:onjoin].each { |auto|\r
-      next unless ['all', m.target.downcase].include?(auto.channel)\r
-      next unless m.source.matches? auto.host\r
-\r
-      do_cmd(auto.action.to_sym, m.source.nick, m.target, "0s", auto.reason)\r
-      return\r
-    }\r
-  end\r
-\r
-  def ban_user(m, params=nil)\r
-    nick, channel = params[:nick], check_channel(m, params[:channel])\r
-    timer = params[:timer]\r
-    do_cmd(:ban, nick, channel, timer)\r
-  end\r
-\r
-  def unban_user(m, params=nil)\r
-    nick, channel = params[:nick], check_channel(m, params[:channel])\r
-    do_cmd(:unban, nick, channel)\r
-  end\r
-\r
-  def kick_user(m, params=nil)\r
-    nick, channel = params[:nick], check_channel(m, params[:channel])\r
-    reason = params[:reason].to_s\r
-    do_cmd(:kick, nick, channel, "0s", reason)\r
-  end\r
-\r
-  def kickban_user(m, params=nil)\r
-    nick, channel, reason = params[:nick], check_channel(m, params[:channel])\r
-    timer, reason = params[:timer], params[:reason].to_s\r
-    do_cmd(:kickban, nick, channel, timer, reason)\r
-  end\r
-\r
-  def silence_user(m, params=nil)\r
-    nick, channel = params[:nick], check_channel(m, params[:channel])\r
-    timer = params[:timer]\r
-    do_cmd(:silence, nick, channel, timer)\r
-  end\r
-\r
-  def unsilence_user(m, params=nil)\r
-    nick, channel = params[:nick], check_channel(m, params[:channel])\r
-    do_cmd(:unsilence, nick, channel)\r
-  end\r
-\r
-  def add_onjoin(m, params=nil)\r
-    begin\r
-      host, channel = m.server.new_netmask(params[:host]), params[:channel].downcase\r
-      action, reason = params[:action], params[:reason].to_s\r
-\r
-      autos = @registry[:onjoin]\r
-      autos << OnJoinAction.new(host, action, channel, reason.dup)\r
-      @registry[:onjoin] = autos\r
-\r
-      m.okay\r
-    rescue\r
-      error $!\r
-      m.reply $!\r
-    end\r
-  end\r
-\r
-  def list_onjoin(m, params=nil)\r
-    m.reply "onjoin rules: #{@registry[:onjoin].length}"\r
-    @registry[:onjoin].each_with_index { |auto, idx|\r
-      m.reply "\##{idx+1}: #{auto.host} | #{auto.action} | #{auto.channel} | '#{auto.reason}'"\r
-    }\r
-  end\r
-\r
-  def rm_onjoin(m, params=nil)\r
-    autos = @registry[:onjoin]\r
-    count = autos.length\r
-\r
-    idx = params[:idx].to_i\r
-\r
-    if idx\r
-      if idx > count\r
-        m.reply "No such onjoin \##{idx}"\r
-        return\r
-      end\r
-      autos.delete_at(idx-1)\r
-    else\r
-      begin\r
-        host = m.server.new_netmask(params[:host])\r
-        channel = params[:channel].downcase\r
-\r
-        autos.each { |rule|\r
-          next unless ['all', rule.channel].include?(channel)\r
-          autos.delete rule if rule.host == host\r
-        }\r
-      rescue\r
-        error $!\r
-        m.reply $!\r
-      end\r
-    end\r
-    @registry[:onjoin] = autos\r
-    m.okay if count > autos.length\r
-  end\r
-\r
-  def add_badword(m, params=nil)\r
-    regexp, channel = make_badword_rx(params[:regexp]), params[:channel].dup\r
-    action, timer, reason = params[:action], params[:timer].dup, params[:reason].to_s\r
-\r
-    badwords = @registry[:badwords]\r
-    badwords << BadWordAction.new(regexp, action, channel, timer, reason)\r
-    @registry[:badwords] = badwords\r
-\r
-    m.okay\r
-  end\r
-\r
-  def list_badword(m, params=nil)\r
-    m.reply "badword rules: #{@registry[:badwords].length}"\r
-\r
-    @registry[:badwords].each_with_index { |badword, idx|\r
-      m.reply "\##{idx+1}: #{badword.regexp.source} | #{badword.action} | #{badword.channel} | #{badword.timer} | #{badword.reason}"\r
-    }\r
-  end\r
-\r
-  def rm_badword(m, params=nil)\r
-    badwords = @registry[:badwords]\r
-    count = badwords.length\r
-\r
-    idx = params[:idx].to_i\r
-\r
-    if idx\r
-      if idx > count\r
-        m.reply "No such badword \##{idx}"\r
-        return\r
-      end\r
-      badwords.delete_at(idx-1)\r
-    else\r
-      channel = params[:channel].downcase\r
-\r
-      regexp = make_badword_rx(params[:regexp])\r
-      debug "Trying to remove #{regexp.inspect} from #{badwords.inspect}"\r
-\r
-      badwords.each { |badword|\r
-        next unless ['all', badword.channel].include?(channel)\r
-        badwords.delete(badword) if badword == regexp\r
-      }\r
-    end\r
-\r
-    @registry[:badwords] = badwords\r
-    m.okay if count > badwords.length\r
-  end\r
-\r
-  def add_whitelist(m, params=nil)\r
-    begin\r
-      host, channel = m.server.new_netmask(params[:host]), params[:channel].downcase\r
-\r
-      # TODO check if a whitelist entry for this host already exists\r
-      whitelist = @registry[:whitelist]\r
-      whitelist << WhitelistEntry.new(host, channel)\r
-      @registry[:whitelist] = whitelist\r
-\r
-      m.okay\r
-    rescue\r
-      error $!\r
-      m.reply $!\r
-    end\r
-  end\r
-\r
-  def list_whitelist(m, params=nil)\r
-    m.reply "whitelist entries: #{@registry[:whitelist].length}"\r
-    @registry[:whitelist].each_with_index { |auto, idx|\r
-      m.reply "\##{idx+1}: #{auto.host} | #{auto.channel}"\r
-    }\r
-  end\r
-\r
-  def rm_whitelist(m, params=nil)\r
-    wl = @registry[:whitelist]\r
-    count = wl.length\r
-\r
-    idx = params[:idx].to_i\r
-\r
-    if idx\r
-      if idx > count\r
-        m.reply "No such whitelist entry \##{idx}"\r
-        return\r
-      end\r
-      wl.delete_at(idx-1)\r
-    else\r
-      begin\r
-        host = m.server.new_netmask(params[:host])\r
-        channel = params[:channel].downcase\r
-\r
-        wl.each { |rule|\r
-          next unless ['all', rule.channel].include?(channel)\r
-          wl.delete rule if rule.host == host\r
-        }\r
-      rescue\r
-        error $!\r
-        m.reply $!\r
-      end\r
-    end\r
-    @registry[:whitelist] = wl\r
-    m.okay if count > whitelist.length\r
-  end\r
-\r
-  private\r
-  def check_channel(m, strchannel)\r
-    begin\r
-      raise "must specify channel if using privmsg" if m.private? and not strchannel\r
-      channel = m.server.channel(strchannel) || m.target\r
-      raise "I am not in that channel" unless channel.has_user?(@bot.nick)\r
-\r
-      return channel\r
-    rescue\r
-      error $!\r
-      m.reply $!\r
-    end\r
-  end\r
-\r
-  def do_cmd(action, nick, channel, timer_in=nil, reason=nil)\r
-    case timer_in\r
-    when nil\r
-      timer = 0\r
-    when /^(\d+)s$/\r
-      timer = $1.to_i\r
-    when /^(\d+)m$/\r
-      timer = $1.to_i * 60\r
-    when /^(\d+)h$/\r
-      timer = $1.to_i * 60 * 60 \r
-    when /^(\d+)d$/\r
-      timer = $1.to_i * 60 * 60 * 24\r
-    else\r
-      raise "Wrong time specifications"\r
-    end\r
-\r
-    case action\r
-    when :ban\r
-      set_mode(channel, "+b", nick)\r
-      @bot.timer.add_once(timer) { set_mode(channel, "-b", nick) } if timer > 0\r
-    when :unban\r
-      set_mode(channel, "-b", nick)\r
-    when :kick\r
-      do_kick(channel, nick, reason)\r
-    when :kickban\r
-      set_mode(channel, "+b", nick)\r
-      @bot.timer.add_once(timer) { set_mode(channel, "-b", nick) } if timer > 0\r
-      do_kick(channel, nick, reason)\r
-    when :silence, :quiet\r
-      set_mode(channel, "+q", nick)\r
-      @bot.timer.add_once(timer) { set_mode(channel, "-q", nick) } if timer > 0\r
-    when :unsilence, :unquiet\r
-      set_mode(channel, "-q", nick)\r
-    end\r
-  end\r
-\r
-  def set_mode(channel, mode, nick)\r
-    host = channel.has_user?(nick) ? "*!*@" + channel.get_user(nick).host : nick\r
-    @bot.mode(channel, mode, host)\r
-  end\r
-\r
-  def do_kick(channel, nick, reason="")\r
-    @bot.kick(channel, nick, reason)\r
-  end\r
-end\r
-\r
-plugin = BansPlugin.new\r
-\r
-plugin.default_auth( 'act', false )\r
-plugin.default_auth( 'edit', false )\r
-plugin.default_auth( 'list', true )\r
-\r
-plugin.map 'ban :nick :timer :channel', :action => 'ban_user',\r
-  :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},\r
-  :defaults => {:timer => nil, :channel => nil},\r
-  :auth_path => 'act'\r
-plugin.map 'unban :nick :channel', :action => 'unban_user',\r
-  :requirements => {:channel => BansPlugin::ChannelRe},\r
-  :defaults => {:channel => nil},\r
-  :auth_path => 'act'\r
-plugin.map 'kick :nick :channel *reason', :action => 'kick_user',\r
-  :requirements => {:channel => BansPlugin::ChannelRe},\r
-  :defaults => {:channel => nil, :reason => 'requested'},\r
-  :auth_path => 'act'\r
-plugin.map 'kickban :nick :timer :channel *reason', :action => 'kickban_user',\r
-  :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},\r
-  :defaults => {:timer => nil, :channel => nil, :reason => 'requested'},\r
-  :auth_path => 'act'\r
-plugin.map 'silence :nick :timer :channel', :action => 'silence_user',\r
-  :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},\r
-  :defaults => {:timer => nil, :channel => nil},\r
-  :auth_path => 'act'\r
-plugin.map 'unsilence :nick :channel', :action => 'unsilence_user',\r
-  :requirements => {:channel => BansPlugin::ChannelRe},\r
-  :defaults => {:channel => nil},\r
-  :auth_path => 'act'\r
-\r
-plugin.map 'bans add onjoin :host :action :channel *reason', :action => 'add_onjoin',\r
-  :requirements => {:action => BansPlugin::ActionRe, :channel => BansPlugin::ChannelAllRe},\r
-  :defaults => {:action => 'kickban', :channel => 'all', :reason => 'netmask not welcome'},\r
-  :auth_path => 'edit::onjoin'\r
-plugin.map 'bans rm onjoin index :idx', :action => 'rm_onjoin',\r
-  :requirements => {:num => BansPlugin::IdxRe},\r
-  :auth_path => 'edit::onjoin'\r
-plugin.map 'bans rm onjoin :host :channel', :action => 'rm_onjoin',\r
-  :requirements => {:channel => BansPlugin::ChannelAllRe},\r
-  :defaults => {:channel => 'all'},\r
-  :auth_path => 'edit::onjoin'\r
-plugin.map 'bans list onjoin[s]', :action => 'list_onjoin',\r
-  :auth_path => 'list::onjoin'\r
-\r
-plugin.map 'bans add badword :regexp :action :timer :channel *reason', :action => 'add_badword',\r
-  :requirements => {:action => BansPlugin::ActionRe, :timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelAllRe},\r
-  :defaults => {:action => 'silence', :timer => "0s", :channel => 'all', :reason => 'bad word'},\r
-  :auth_path => 'edit::badword'\r
-plugin.map 'bans rm badword index :idx', :action => 'rm_badword',\r
-  :requirements => {:num => BansPlugin::IdxRe},\r
-  :auth_path => 'edit::badword'\r
-plugin.map 'bans rm badword :regexp :channel', :action => 'rm_badword',\r
-  :requirements => {:channel => BansPlugin::ChannelAllRe},\r
-  :defaults => {:channel => 'all'},\r
-  :auth_path => 'edit::badword'\r
-plugin.map 'bans list badword[s]', :action => 'list_badword',\r
-  :auth_path => 'list::badword'\r
-\r
-plugin.map 'bans add whitelist :host :channel', :action => 'add_whitelist',\r
-  :requirements => {:channel => BansPlugin::ChannelAllRe},\r
-  :defaults => {:channel => 'all'},\r
-  :auth_path => 'edit::whitelist'\r
-plugin.map 'bans rm whitelist index :idx', :action => 'rm_whitelist',\r
-  :requirements => {:num => BansPlugin::IdxRe},\r
-  :auth_path => 'edit::whitelist'\r
-plugin.map 'bans rm whitelist :host :channel', :action => 'rm_whitelist',\r
-  :requirements => {:channel => BansPlugin::ChannelAllRe},\r
-  :defaults => {:channel => 'all'},\r
-  :auth_path => 'edit::whitelist'\r
-plugin.map 'bans list whitelist', :action => 'list_whitelist',\r
-  :auth_path => 'list::whitelist'\r
-\r
+#-- vim:sw=2:et
+#++
+#
+# :title: Bans Plugin v3 for rbot 0.9.11 and later
+#
+# Author:: Marco Gulino <marco@kmobiletools.org>
+# Author:: kamu <mr.kamu@gmail.com>
+# Author:: Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
+#
+# Copyright:: (C) 2006 Marco Gulino
+# Copyright:: (C) 2007 kamu, Giuseppe Bilotta
+#
+# License:: GPL V2.
+#
+# Managing kick and bans, automatically removing bans after timeouts, quiet
+# bans, and kickban/quietban based on regexp
+#
+# v1 -> v2 (kamu's version, never released)
+#   * reworked
+#   * autoactions triggered on join
+#   * action on join or badword can be anything: kick, ban, kickban, quiet
+#
+# v2 -> v3 (GB)
+#   * remove the 'bans' prefix from most of the commands
+#   * (un)quiet has been renamed to (un)silence because 'quiet' was used to
+#     tell the bot to keep quiet
+#   * both (un)quiet and (un)silence are accepted as actions
+#   * use the more descriptive 'onjoin' term for autoactions
+#   * convert v1's (0.9.10) :bans and :bansmasks to BadWordActions and
+#     WhitelistEntries
+#   * enhanced list manipulation facilities
+#   * fixed regexp usage in requirements for plugin map
+#   * add proper auth management
+
+define_structure :OnJoinAction, :host, :action, :channel, :reason
+define_structure :BadWordAction, :regexp, :action, :channel, :timer, :reason
+define_structure :WhitelistEntry, :host, :channel
+define_structure :MassHlAction, :num, :perc, :action, :channel, :timer, :reason
+
+class BansPlugin < Plugin
+
+  IdxRe = /^\d+$/
+  TimerRe = /^\d+[smhd]$/
+  ChannelRe = /^#+[^\s]+$/
+  ChannelAllRe = /^(?:all|#+[^\s]+)$/
+  ActionRe = /(?:ban|kick|kickban|silence|quiet)/
+
+  def name
+    "bans"
+  end
+
+  def make_badword_rx(txt)
+    return /\b(?:#{txt})\b/i
+  end
+
+  def initialize
+    super
+
+    # Convert old BadWordActions, which were simpler and labelled :bans
+    if @registry.has_key? :bans
+      badwords = Array.new
+      bans = @registry[:bans]
+      @registry[:bans].each { |ar|
+        case ar[0]
+        when "quietban"
+          action = :silence
+        when "kickban"
+          action = :kickban
+        else
+          # Shouldn't happen
+          warning "Unknown action in old data #{ar.inspect} -- entry ignored"
+          next
+        end
+        bans.delete(ar)
+        chan = ar[1].downcase
+        regexp = make_badword_rx(ar[2])
+        badwords << BadWordAction.new(regexp, action, chan, "0s", "")
+      }
+      @registry[:badwords] = badwords
+      if bans.length > 0
+        # Store the ones we couldn't convert
+        @registry[:bans] = bans
+      else
+        @registry.delete(:bans)
+      end
+    else
+      @registry[:badwords] = Array.new unless @registry.has_key? :badwords
+    end
+
+    # Convert old WhitelistEntries, which were simpler and labelled :bansmasks
+    if @registry.has_key? :bans
+      wl = Array.new
+      @registry[:bansmasks].each { |mask|
+        badwords << WhitelistEntry.new(mask, "all")
+      }
+      @registry[:whitelist] = wl
+      @registry.delete(:bansmasks)
+    else
+      @registry[:whitelist] = Array.new unless @registry.has_key? :whitelist
+    end
+
+    @registry[:onjoin] = Array.new unless @registry.has_key? :onjoin
+    @registry[:masshl] = Array.new unless @registry.has_key? :masshl
+  end
+
+  def help(plugin, topic="")
+    case plugin
+    when "ban"
+      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"
+    when "unban"
+      return "unban <nick/hostmask> [#channel]: unban a user from the given channel. defaults to the current channel"
+    when "kick"
+      return "kick <nick> [#channel] [reason ...]: kick a user from the given channel with the given reason. defaults to the current channel, no reason"
+    when "kickban"
+      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"
+    when "silence"
+      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"
+    when "unsilence"
+      return "unsilence <nick/hostmask> [#channel]: allow the given user to talk on the given channel. defaults to the current channel"
+    when "bans"
+      case topic
+      when "add"
+        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"
+      when "add onjoin"
+        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"
+      when "add badword"
+        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"
+      when "add whitelist"
+        return "bans add whitelist <hostmask> [#channel|all]: add the given hostmask to the whitelist. no autoaction will be triggered by users on the whitelist"
+      when "add masshl"
+        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"
+      when "rm"
+        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]"
+      when "list"
+        return"bans list <onjoin|badword|whitelist|masshl>: lists all onjoin or badwords or whitelist entries. For masshl, you can add [#channel|all]"
+      else
+        return "commands are: add, add onjoin, add badword, add whitelist, add masshl, rm, list"
+      end
+    end
+    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"
+  end
+
+  def message(m)
+    return unless m.channel
+
+    # check the whitelist first
+    @registry[:whitelist].each { |white|
+      next unless ['all', m.target.downcase].include?(white.channel)
+      return if m.source.matches?(white.host)
+    }
+
+    # check the badwords next
+    @registry[:badwords].each { |badword|
+      next unless ['all', m.target.downcase].include?(badword.channel)
+      next unless badword.regexp.match(m.plainmessage)
+
+      m.reply "bad word detected! #{badword.action} for #{badword.timer} because: #{badword.reason}"
+      do_cmd(badword.action.to_sym, m.source.nick, m.target, badword.timer, badword.reason)
+      return
+    }
+
+    # and finally, see if the user triggered masshl
+    mm = m.plainmessage.irc_downcase(m.server.casemap).split(/[\s\.,:]/)
+    nicks_said = (m.channel.users.map { |u| u.downcase} & mm).size
+    return unless nicks_said > 0 # not really needed, but saves some cycles
+    got_nicks = 0
+    masshl_action = nil
+    @registry[:masshl].each { |masshl|
+      next unless masshl.channel == m.channel.downcase or masshl.channel == "all"
+      needed = [masshl.num.to_i, (masshl.perc * m.channel.user_nicks.size / 100).to_i].max
+      next if needed > nicks_said or needed < got_nicks
+      masshl_action = masshl
+      got_nicks = needed
+    }
+    return unless masshl_action
+    do_cmd masshl_action.action.intern, m.sourcenick, m.channel, masshl_action.timer, masshl_action.reason
+  end
+
+  def join(m)
+    @registry[:whitelist].each { |white|
+      next unless ['all', m.target.downcase].include?(white.channel)
+      return if m.source.matches?(white.host)
+    }
+
+    @registry[:onjoin].each { |auto|
+      next unless ['all', m.target.downcase].include?(auto.channel)
+      next unless m.source.matches? auto.host
+
+      do_cmd(auto.action.to_sym, m.source.nick, m.target, "0s", auto.reason)
+      return
+    }
+  end
+
+  def ban_user(m, params=nil)
+    nick, channel = params[:nick], check_channel(m, params[:channel])
+    timer = params[:timer]
+    do_cmd(:ban, nick, channel, timer)
+  end
+
+  def unban_user(m, params=nil)
+    nick, channel = params[:nick], check_channel(m, params[:channel])
+    do_cmd(:unban, nick, channel)
+  end
+
+  def kick_user(m, params=nil)
+    nick, channel = params[:nick], check_channel(m, params[:channel])
+    reason = params[:reason].to_s
+    do_cmd(:kick, nick, channel, "0s", reason)
+  end
+
+  def kickban_user(m, params=nil)
+    nick, channel, reason = params[:nick], check_channel(m, params[:channel])
+    timer, reason = params[:timer], params[:reason].to_s
+    do_cmd(:kickban, nick, channel, timer, reason)
+  end
+
+  def silence_user(m, params=nil)
+    nick, channel = params[:nick], check_channel(m, params[:channel])
+    timer = params[:timer]
+    do_cmd(:silence, nick, channel, timer)
+  end
+
+  def unsilence_user(m, params=nil)
+    nick, channel = params[:nick], check_channel(m, params[:channel])
+    do_cmd(:unsilence, nick, channel)
+  end
+
+  def add_masshl(m, params=nil)
+    num = params[:num].to_i
+    perc = params[:perc] ? /(\d{1,2})\%/.match(params[:perc])[1].to_i : 0
+    channel, action = params[:channel].downcase.dup, params[:action]
+    timer, reason = params[:timer].dup, params[:reason].to_s
+    if perc == 0 and num == 0
+      m.reply "both triggers 0, you don't want this."
+      return
+    end
+
+    masshl = @registry[:masshl]
+    masshl << MassHlAction.new(num, perc, action, channel, timer, reason)
+    @registry[:masshl] = masshl
+
+    m.okay
+  end
+
+  def rm_masshl(m, params=nil)
+    masshl = @registry[:masshl]
+    masshl_w = params[:channel] ? masshl.select { |mh| mh.channel == params[:channel].downcase } : masshl
+    count = masshl_w.length
+    idx = params[:idx].to_i
+
+    if idx > count
+      m.reply "No such masshl \##{idx}"
+      return
+    end
+    masshl.delete(masshl_w[idx-1])
+    @registry[:masshl] = masshl
+    m.okay
+  end
+
+  def list_masshl(m, params=nil)
+    masshl = @registry[:masshl]
+    masshl = masshl.select { |mh| mh.channel == params[:channel].downcase } if params[:channel]
+    m.reply params[:channel] ? "masshl rules: #{masshl.length} for #{params[:channel]}" : "masshl rules: #{masshl.length}"
+    masshl.each_with_index { |mh, idx|
+      m.reply "\##{idx+1}: #{mh.num} | #{mh.perc}% | #{mh.action} | #{mh.channel} | #{mh.timer} | #{mh.reason}"
+    }
+  end
+
+  def add_onjoin(m, params=nil)
+    begin
+      host, channel = m.server.new_netmask(params[:host]), params[:channel].downcase
+      action, reason = params[:action], params[:reason].to_s
+
+      autos = @registry[:onjoin]
+      autos << OnJoinAction.new(host, action, channel, reason.dup)
+      @registry[:onjoin] = autos
+
+      m.okay
+    rescue
+      error $!
+      m.reply $!
+    end
+  end
+
+  def list_onjoin(m, params=nil)
+    m.reply "onjoin rules: #{@registry[:onjoin].length}"
+    @registry[:onjoin].each_with_index { |auto, idx|
+      m.reply "\##{idx+1}: #{auto.host} | #{auto.action} | #{auto.channel} | '#{auto.reason}'"
+    }
+  end
+
+  def rm_onjoin(m, params=nil)
+    autos = @registry[:onjoin]
+    count = autos.length
+
+    idx = nil
+    idx = params[:idx].to_i if params[:idx]
+
+    if idx
+      if idx > count
+        m.reply "No such onjoin \##{idx}"
+        return
+      end
+      autos.delete_at(idx-1)
+    else
+      begin
+        host = m.server.new_netmask(params[:host])
+        channel = params[:channel].downcase
+
+        autos.each { |rule|
+          next unless ['all', rule.channel].include?(channel)
+          autos.delete rule if rule.host == host
+        }
+      rescue
+        error $!
+        m.reply $!
+      end
+    end
+    @registry[:onjoin] = autos
+    if count > autos.length
+      m.okay
+    else
+      m.reply "No matching onjoin rule for #{host} found"
+    end
+  end
+
+  def add_badword(m, params=nil)
+    regexp, channel = make_badword_rx(params[:regexp]), params[:channel].downcase.dup
+    action, timer, reason = params[:action], params[:timer].dup, params[:reason].to_s
+
+    badwords = @registry[:badwords]
+    badwords << BadWordAction.new(regexp, action, channel, timer, reason)
+    @registry[:badwords] = badwords
+
+    m.okay
+  end
+
+  def list_badword(m, params=nil)
+    m.reply "badword rules: #{@registry[:badwords].length}"
+
+    @registry[:badwords].each_with_index { |badword, idx|
+      m.reply "\##{idx+1}: #{badword.regexp.source} | #{badword.action} | #{badword.channel} | #{badword.timer} | #{badword.reason}"
+    }
+  end
+
+  def rm_badword(m, params=nil)
+    badwords = @registry[:badwords]
+    count = badwords.length
+
+    idx = nil
+    idx = params[:idx].to_i if params[:idx]
+
+    if idx
+      if idx > count
+        m.reply "No such badword \##{idx}"
+        return
+      end
+      badwords.delete_at(idx-1)
+    else
+      channel = params[:channel].downcase
+
+      regexp = make_badword_rx(params[:regexp])
+      debug "Trying to remove #{regexp.inspect} from #{badwords.inspect}"
+
+      badwords.each { |badword|
+        next unless ['all', badword.channel].include?(channel)
+        debug "Removing #{badword.inspect}" if badword.regexp == regexp
+        badwords.delete(badword) if badword.regexp == regexp
+      }
+    end
+
+    @registry[:badwords] = badwords
+    if count > badwords.length
+      m.okay
+    else
+      m.reply "No matching badword #{regexp} found"
+    end
+  end
+
+  def add_whitelist(m, params=nil)
+    begin
+      host, channel = m.server.new_netmask(params[:host]), params[:channel].downcase
+
+      # TODO check if a whitelist entry for this host already exists
+      whitelist = @registry[:whitelist]
+      whitelist << WhitelistEntry.new(host, channel)
+      @registry[:whitelist] = whitelist
+
+      m.okay
+    rescue
+      error $!
+      m.reply $!
+    end
+  end
+
+  def list_whitelist(m, params=nil)
+    m.reply "whitelist entries: #{@registry[:whitelist].length}"
+    @registry[:whitelist].each_with_index { |auto, idx|
+      m.reply "\##{idx+1}: #{auto.host} | #{auto.channel}"
+    }
+  end
+
+  def rm_whitelist(m, params=nil)
+    wl = @registry[:whitelist]
+    count = wl.length
+
+    idx = nil
+    idx = params[:idx].to_i if params[:idx]
+
+    if idx
+      if idx > count
+        m.reply "No such whitelist entry \##{idx}"
+        return
+      end
+      wl.delete_at(idx-1)
+    else
+      begin
+        host = m.server.new_netmask(params[:host])
+        channel = params[:channel].downcase
+
+        wl.each { |rule|
+          next unless ['all', rule.channel].include?(channel)
+          wl.delete rule if rule.host == host
+        }
+      rescue
+        error $!
+        m.reply $!
+      end
+    end
+    @registry[:whitelist] = wl
+    if count > whitelist.length
+      m.okay
+    else
+      m.reply "No host matching #{host}"
+    end
+  end
+
+  private
+  def check_channel(m, strchannel)
+    begin
+      raise "must specify channel if using privmsg" if m.private? and not strchannel
+      channel = m.server.channel(strchannel) || m.target
+      raise "I am not in that channel" unless channel.has_user?(@bot.nick)
+
+      return channel
+    rescue
+      error $!
+      m.reply $!
+    end
+  end
+
+  def do_cmd(action, nick, channel, timer_in=nil, reason=nil)
+    case timer_in
+    when nil
+      timer = 0
+    when /^(\d+)s$/
+      timer = $1.to_i
+    when /^(\d+)m$/
+      timer = $1.to_i * 60
+    when /^(\d+)h$/
+      timer = $1.to_i * 60 * 60
+    when /^(\d+)d$/
+      timer = $1.to_i * 60 * 60 * 24
+    else
+      raise "Wrong time specifications"
+    end
+
+    case action
+    when :ban
+      set_temporary_mode(channel, 'b', nick, timer)
+    when :unban
+      set_mode(channel, "-b", nick)
+    when :kick
+      do_kick(channel, nick, reason)
+    when :kickban
+      set_temporary_mode(channel, 'b', nick, timer)
+      do_kick(channel, nick, reason)
+    when :silence, :quiet
+      set_mode(channel, "+q", nick)
+      @bot.timer.add_once(timer) { set_mode(channel, "-q", nick) } if timer > 0
+    when :unsilence, :unquiet
+      set_mode(channel, "-q", nick)
+    end
+  end
+
+  def set_mode(channel, mode, nick)
+    host = channel.has_user?(nick) ? "*!*@" + channel.get_user(nick).host : nick
+    @bot.mode(channel, mode, host)
+  end
+
+  def set_temporary_mode(channel, mode, nick, timer)
+    host = channel.has_user?(nick) ? "*!*@" + channel.users[nick].host : nick
+    @bot.mode(channel, "+#{mode}", host)
+    return if timer == 0
+    @bot.timer.add_once(timer) { @bot.mode(channel, "-#{mode}", host) }
+  end
+
+  def do_kick(channel, nick, reason="")
+    @bot.kick(channel, nick, reason)
+  end
+end
+
+plugin = BansPlugin.new
+
+plugin.default_auth( 'act', false )
+plugin.default_auth( 'edit', false )
+plugin.default_auth( 'list', true )
+
+plugin.map 'ban :nick :timer :channel', :action => 'ban_user',
+  :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},
+  :defaults => {:timer => nil, :channel => nil},
+  :auth_path => 'act'
+plugin.map 'unban :nick :channel', :action => 'unban_user',
+  :requirements => {:channel => BansPlugin::ChannelRe},
+  :defaults => {:channel => nil},
+  :auth_path => 'act'
+plugin.map 'kick :nick :channel *reason', :action => 'kick_user',
+  :requirements => {:channel => BansPlugin::ChannelRe},
+  :defaults => {:channel => nil, :reason => 'requested'},
+  :auth_path => 'act'
+plugin.map 'kickban :nick :timer :channel *reason', :action => 'kickban_user',
+  :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},
+  :defaults => {:timer => nil, :channel => nil, :reason => 'requested'},
+  :auth_path => 'act'
+plugin.map 'silence :nick :timer :channel', :action => 'silence_user',
+  :requirements => {:timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelRe},
+  :defaults => {:timer => nil, :channel => nil},
+  :auth_path => 'act'
+plugin.map 'unsilence :nick :channel', :action => 'unsilence_user',
+  :requirements => {:channel => BansPlugin::ChannelRe},
+  :defaults => {:channel => nil},
+  :auth_path => 'act'
+
+plugin.map 'bans add onjoin :host :action :channel *reason', :action => 'add_onjoin',
+  :requirements => {:action => BansPlugin::ActionRe, :channel => BansPlugin::ChannelAllRe},
+  :defaults => {:action => 'kickban', :channel => 'all', :reason => 'netmask not welcome'},
+  :auth_path => 'edit::onjoin'
+plugin.map 'bans rm onjoin index :idx', :action => 'rm_onjoin',
+  :requirements => {:num => BansPlugin::IdxRe},
+  :auth_path => 'edit::onjoin'
+plugin.map 'bans rm onjoin :host :channel', :action => 'rm_onjoin',
+  :requirements => {:channel => BansPlugin::ChannelAllRe},
+  :defaults => {:channel => 'all'},
+  :auth_path => 'edit::onjoin'
+plugin.map 'bans list onjoin[s]', :action => 'list_onjoin',
+  :auth_path => 'list::onjoin'
+
+plugin.map 'bans add badword :regexp :action :timer :channel *reason', :action => 'add_badword',
+  :requirements => {:action => BansPlugin::ActionRe, :timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelAllRe},
+  :defaults => {:action => 'silence', :timer => "0s", :channel => 'all', :reason => 'bad word'},
+  :auth_path => 'edit::badword'
+plugin.map 'bans rm badword index :idx', :action => 'rm_badword',
+  :requirements => {:num => BansPlugin::IdxRe},
+  :auth_path => 'edit::badword'
+plugin.map 'bans rm badword :regexp :channel', :action => 'rm_badword',
+  :requirements => {:channel => BansPlugin::ChannelAllRe},
+  :defaults => {:channel => 'all'},
+  :auth_path => 'edit::badword'
+plugin.map 'bans list badword[s]', :action => 'list_badword',
+  :auth_path => 'list::badword'
+
+plugin.map 'bans add whitelist :host :channel', :action => 'add_whitelist',
+  :requirements => {:channel => BansPlugin::ChannelAllRe},
+  :defaults => {:channel => 'all'},
+  :auth_path => 'edit::whitelist'
+plugin.map 'bans rm whitelist index :idx', :action => 'rm_whitelist',
+  :requirements => {:num => BansPlugin::IdxRe},
+  :auth_path => 'edit::whitelist'
+plugin.map 'bans rm whitelist :host :channel', :action => 'rm_whitelist',
+  :requirements => {:channel => BansPlugin::ChannelAllRe},
+  :defaults => {:channel => 'all'},
+  :auth_path => 'edit::whitelist'
+plugin.map 'bans list whitelist', :action => 'list_whitelist',
+  :auth_path => 'list::whitelist'
+
+plugin.map 'bans add masshl :num :perc :action :timer :channel *reason', :action => 'add_masshl',
+  :requirements => {:num => /\d{1,2}/, :perc => /\d{1,2}\%/,:action => BansPlugin::ActionRe, :timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelAllRe},
+  :defaults => {:action => 'silence', :timer => "0s", :channel => 'all', :reason => 'masshl'},
+  :auth_path => 'edit::masshl'
+plugin.map 'bans add masshl :perc :num :action :timer :channel *reason', :action => 'add_masshl',
+  :requirements => {:num => /\d{1,2}/, :perc => /\d{1,2}\%/,:action => BansPlugin::ActionRe, :timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelAllRe},
+  :defaults => {:action => 'silence', :timer => "0s", :channel => 'all', :reason => 'masshl'},
+  :auth_path => 'edit::masshl'
+plugin.map 'bans add masshl :perc :action :timer :channel *reason', :action => 'add_masshl',
+  :requirements => {:num => /\d{1,2}/, :perc => /\d{1,2}\%/,:action => BansPlugin::ActionRe, :timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelAllRe},
+  :defaults => {:num => 0, :action => 'silence', :timer => "0s", :channel => 'all', :reason => 'masshl'},
+  :auth_path => 'edit::masshl'
+plugin.map 'bans add masshl :num :action :timer :channel *reason', :action => 'add_masshl',
+  :requirements => {:num => /\d{1,2}/, :perc => /\d{1,2}\%/,:action => BansPlugin::ActionRe, :timer => BansPlugin::TimerRe, :channel => BansPlugin::ChannelAllRe},
+  :defaults => {:perc => "0%", :action => 'silence', :timer => "0s", :channel => 'all', :reason => 'masshl'},
+  :auth_path => 'edit::masshl'
+plugin.map 'bans rm masshl :idx', :action => 'rm_masshl',
+  :requirements => {:channel => nil, :num => BansPlugin::IdxRe},
+  :auth_path => 'edit::masshl'
+plugin.map 'bans rm masshl :idx :channel', :action => 'rm_masshl',
+  :requirements => {:channel => BansPlugin::ChannelAllRe},
+  :defaults => {:channel => nil},
+  :auth_path => 'edit::masshl'
+plugin.map 'bans list masshl', :action => 'list_masshl',
+  :auth_path => 'list::masshl'
+plugin.map 'bans list masshl :channel', :action => 'list_masshl',
+  :defaults => {:channel => nil},
+  :auth_path => 'list::masshl'