]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blobdiff - lib/rbot/plugins.rb
New IRC Framework: use #reverse_each() instead of #each() when clearing the list...
[user/henk/code/ruby/rbot.git] / lib / rbot / plugins.rb
index b0626b9687181c8416e5d3e68e19ce0efaa439b4..7b476da93a5819bdbfb359a5e9c73a8d99aa8a77 100644 (file)
@@ -61,11 +61,14 @@ module Plugins
                          etc.
 
   privmsg(PrivMessage)::
                          etc.
 
   privmsg(PrivMessage)::
-                         called for a PRIVMSG if the first word matches one
+                         Called for a PRIVMSG if the first word matches one
                          the plugin register()d for. Use m.plugin to get
                          that word and m.params for the rest of the message,
                          if applicable.
 
                          the plugin register()d for. Use m.plugin to get
                          that word and m.params for the rest of the message,
                          if applicable.
 
+  unreplied(PrivMessage)::
+                         Called for a PRIVMSG which has not been replied to.
+
   kick(KickMessage)::
                          Called when a user (or the bot) is kicked from a
                          channel the bot is in.
   kick(KickMessage)::
                          Called when a user (or the bot) is kicked from a
                          channel the bot is in.
@@ -88,6 +91,10 @@ module Plugins
   connect()::            Called when a server is joined successfully, but
                          before autojoin channels are joined (no params)
 
   connect()::            Called when a server is joined successfully, but
                          before autojoin channels are joined (no params)
 
+  set_language(String)::
+                         Called when the user sets a new language
+                         whose name is the given String
+
   save::                 Called when you are required to save your plugin's
                          state, if you maintain data between sessions
 
   save::                 Called when you are required to save your plugin's
                          state, if you maintain data between sessions
 
@@ -98,21 +105,26 @@ module Plugins
 
   class BotModule
     attr_reader :bot   # the associated bot
 
   class BotModule
     attr_reader :bot   # the associated bot
-    attr_reader :botmodule_class # the botmodule class (:coremodule or :plugin)
 
     # initialise your bot module. Always call super if you override this method,
     # as important variables are set up for you
 
     # initialise your bot module. Always call super if you override this method,
     # as important variables are set up for you
-    def initialize(kl)
-      @manager = Plugins::pluginmanager
+    def initialize
+      @manager = Plugins::manager
       @bot = @manager.bot
 
       @bot = @manager.bot
 
-      @botmodule_class = kl.to_sym
       @botmodule_triggers = Array.new
 
       @handler = MessageMapper.new(self)
       @registry = BotRegistryAccessor.new(@bot, self.class.to_s.gsub(/^.*::/, ""))
 
       @manager.add_botmodule(self)
       @botmodule_triggers = Array.new
 
       @handler = MessageMapper.new(self)
       @registry = BotRegistryAccessor.new(@bot, self.class.to_s.gsub(/^.*::/, ""))
 
       @manager.add_botmodule(self)
+      if self.respond_to?('set_language')
+        self.set_language(@bot.lang.language)
+      end
+    end
+
+    def botmodule_class
+      :BotModule
     end
 
     def flush_registry
     end
 
     def flush_registry
@@ -183,6 +195,11 @@ module Plugins
       name
     end
 
       name
     end
 
+    # intern the name
+    def to_sym
+      self.name.to_sym
+    end
+
     # return a help string for your module. for complex modules, you may wish
     # to break your help into topics, and return a list of available topics if
     # +topic+ is nil. +plugin+ is passed containing the matching prefix for
     # return a help string for your module. for complex modules, you may wish
     # to break your help into topics, and return a list of available topics if
     # +topic+ is nil. +plugin+ is passed containing the matching prefix for
@@ -197,7 +214,11 @@ module Plugins
     # message prefixes
     def register(cmd, opts={})
       raise ArgumentError, "Second argument must be a hash!" unless opts.kind_of?(Hash)
     # message prefixes
     def register(cmd, opts={})
       raise ArgumentError, "Second argument must be a hash!" unless opts.kind_of?(Hash)
-      return if @manager.knows?(cmd, @botmodule_class)
+      who = @manager.who_handles?(cmd)
+      if who
+        raise "Command #{cmd} is already handled by #{who.botmodule_class} #{who}" if who != self
+        return
+      end
       if opts.has_key?(:auth)
         @manager.register(self, cmd, opts[:auth])
       else
       if opts.has_key?(:auth)
         @manager.register(self, cmd, opts[:auth])
       else
@@ -215,14 +236,14 @@ module Plugins
   end
 
   class CoreBotModule < BotModule
   end
 
   class CoreBotModule < BotModule
-    def initialize
-      super(:coremodule)
+    def botmodule_class
+      :CoreBotModule
     end
   end
 
   class Plugin < BotModule
     end
   end
 
   class Plugin < BotModule
-    def initialize
-      super(:plugin)
+    def botmodule_class
+      :Plugin
     end
   end
 
     end
   end
 
@@ -234,23 +255,29 @@ module Plugins
     attr_reader :botmodules
 
     def initialize
     attr_reader :botmodules
 
     def initialize
-      bot_associate(nil)
+      @botmodules = {
+        :CoreBotModule => [],
+        :Plugin => []
+      }
+
+      @names_hash = Hash.new
+      @commandmappers = Hash.new
 
       @dirs = []
 
       @dirs = []
+
+      @failed = Array.new
+      @ignored = Array.new
+
+      bot_associate(nil)
     end
 
     # Reset lists of botmodules
     def reset_botmodule_lists
     end
 
     # Reset lists of botmodules
     def reset_botmodule_lists
-      @botmodules = {
-        :coremodule => [],
-        :plugin => []
-      }
-
-      @commandmappers = {
-        :coremodule => {},
-        :plugin => {}
-      }
-
+      @botmodules[:CoreBotModule].clear
+      @botmodules[:Plugin].clear
+      @names_hash.clear
+      @commandmappers.clear
+      @failures_shown = false
     end
 
     # Associate with bot _bot_
     end
 
     # Associate with bot _bot_
@@ -259,47 +286,52 @@ module Plugins
       @bot = bot
     end
 
       @bot = bot
     end
 
-    # Returns +true+ if _name_ is a known botmodule of class kl
-    def knows?(name, kl)
-      return @commandmappers[kl.to_sym].has_key?(name.to_sym)
+    # Returns the botmodule with the given _name_
+    def [](name)
+      @names_hash[name.to_sym]
+    end
+
+    # Returns +true+ if _cmd_ has already been registered as a command
+    def who_handles?(cmd)
+      return nil unless @commandmappers.has_key?(cmd.to_sym)
+      return @commandmappers[cmd.to_sym][:botmodule]
     end
 
     # Registers botmodule _botmodule_ with command _cmd_ and command path _auth_path_
     def register(botmodule, cmd, auth_path)
       raise TypeError, "First argument #{botmodule.inspect} is not of class BotModule" unless botmodule.kind_of?(BotModule)
     end
 
     # Registers botmodule _botmodule_ with command _cmd_ and command path _auth_path_
     def register(botmodule, cmd, auth_path)
       raise TypeError, "First argument #{botmodule.inspect} is not of class BotModule" unless botmodule.kind_of?(BotModule)
-      kl = botmodule.botmodule_class
-      @commandmappers[kl.to_sym][cmd.to_sym] = {:botmodule => botmodule, :auth => auth_path}
-      h = @commandmappers[kl.to_sym][cmd.to_sym]
-      # debug "Registered command mapper for #{cmd.to_sym} (#{kl.to_sym}): #{h[:botmodule].name} with command path #{h[:auth]}"
+      @commandmappers[cmd.to_sym] = {:botmodule => botmodule, :auth => auth_path}
     end
 
     def add_botmodule(botmodule)
       raise TypeError, "Argument #{botmodule.inspect} is not of class BotModule" unless botmodule.kind_of?(BotModule)
       kl = botmodule.botmodule_class
     end
 
     def add_botmodule(botmodule)
       raise TypeError, "Argument #{botmodule.inspect} is not of class BotModule" unless botmodule.kind_of?(BotModule)
       kl = botmodule.botmodule_class
-      raise "#{kl.to_s} #{botmodule.name} already registered!" if @botmodules[kl.to_sym].include?(botmodule)
-      @botmodules[kl.to_sym] << botmodule
+      if @names_hash.has_key?(botmodule.to_sym)
+        case self[botmodule].botmodule_class
+        when kl
+          raise "#{kl} #{botmodule} already registered!"
+        else
+          raise "#{self[botmodule].botmodule_class} #{botmodule} already registered, cannot re-register as #{kl}"
+        end
+      end
+      @botmodules[kl] << botmodule
+      @names_hash[botmodule.to_sym] = botmodule
     end
 
     # Returns an array of the loaded plugins
     def core_modules
     end
 
     # Returns an array of the loaded plugins
     def core_modules
-      @botmodules[:coremodule]
+      @botmodules[:CoreBotModule]
     end
 
     # Returns an array of the loaded plugins
     def plugins
     end
 
     # Returns an array of the loaded plugins
     def plugins
-      @botmodules[:plugin]
+      @botmodules[:Plugin]
     end
 
     # Returns a hash of the registered message prefixes and associated
     # plugins
     end
 
     # Returns a hash of the registered message prefixes and associated
     # plugins
-    def plugin_commands
-      @commandmappers[:plugin]
-    end
-
-    # Returns a hash of the registered message prefixes and associated
-    # core modules
-    def core_commands
-      @commandmappers[:coremodule]
+    def commands
+      @commandmappers
     end
 
     # Makes a string of error _err_ by adding text _str_
     end
 
     # Makes a string of error _err_ by adding text _str_
@@ -359,10 +391,15 @@ module Plugins
       debug "Botmodule loading path: #{@dirs.join(', ')}"
     end
 
       debug "Botmodule loading path: #{@dirs.join(', ')}"
     end
 
+    def clear_botmodule_dirs
+      @dirs.clear
+      debug "Botmodule loading path cleared"
+    end
+
     # load plugins from pre-assigned list of directories
     def scan
     # load plugins from pre-assigned list of directories
     def scan
-      @failed = Array.new
-      @ignored = Array.new
+      @failed.clear
+      @ignored.clear
       processed = Hash.new
 
       @bot.config['plugins.blacklist'].each { |p|
       processed = Hash.new
 
       @bot.config['plugins.blacklist'].each { |p|
@@ -453,12 +490,12 @@ module Plugins
         list << "no plugins active"
       end
       # Ignored plugins next
         list << "no plugins active"
       end
       # Ignored plugins next
-      unless @ignored.empty?
+      unless @ignored.empty? or @failures_shown
         list << "; #{Underline}#{@ignored.length} plugin#{'s' if @ignored.length > 1} ignored#{Underline}"
         list << ": use #{Bold}help ignored plugins#{Bold} to see why" unless short
       end
       # Failed plugins next
         list << "; #{Underline}#{@ignored.length} plugin#{'s' if @ignored.length > 1} ignored#{Underline}"
         list << ": use #{Bold}help ignored plugins#{Bold} to see why" unless short
       end
       # Failed plugins next
-      unless @failed.empty?
+      unless @failed.empty? or @failures_shown
         list << "; #{Reverse}#{@failed.length} plugin#{'s' if @failed.length > 1} failed to load#{Reverse}"
         list << ": use #{Bold}help failed plugins#{Bold} to see why" unless short
       end
         list << "; #{Reverse}#{@failed.length} plugin#{'s' if @failed.length > 1} failed to load#{Reverse}"
         list << ": use #{Bold}help failed plugins#{Bold} to see why" unless short
       end
@@ -467,7 +504,9 @@ module Plugins
 
     # return list of help topics (plugin names)
     def helptopics
 
     # return list of help topics (plugin names)
     def helptopics
-      return status
+      rv = status
+      @failures_shown = true
+      rv
     end
 
     def length
     end
 
     def length
@@ -492,24 +531,26 @@ module Plugins
         }.join("\n")
       when /ignored?\s*plugins?/
         return "no plugins were ignored" if @ignored.empty?
         }.join("\n")
       when /ignored?\s*plugins?/
         return "no plugins were ignored" if @ignored.empty?
-        return @ignored.inject(Array.new) { |list, p|
-          case p[:reason]
-          when :loaded
-            list << "#{p[:name]} in #{p[:dir]} (overruled by previous)"
-          else
-            list << "#{p[:name]} in #{p[:dir]} (#{p[:reason].to_s})"
-          end
-          list
-        }.join(", ")
+
+        tmp = Hash.new
+        @ignored.each do |p|
+          reason = p[:loaded] ? 'overruled by previous' : p[:reason].to_s
+          ((tmp[p[:dir]] ||= Hash.new)[reason] ||= Array.new).push(p[:name])
+        end
+
+        return tmp.map do |dir, reasons|
+          s = reasons.map { |r, list|
+            list.map { |_| _.sub(/\.rb$/, '') }.join(', ') + " (#{r})"
+          }.join('; ')
+          "in #{dir}: #{s}"
+        end.join('; ')
       when /^(\S+)\s*(.*)$/
         key = $1
         params = $2
 
       when /^(\S+)\s*(.*)$/
         key = $1
         params = $2
 
-       # We test for the mapped commands first
-        k = key.to_sym
-        [core_commands, plugin_commands].each { |pl|
-          next unless pl.has_key?(k)
-          p = pl[k][:botmodule] 
+       # Let's see if we can match a plugin by the given name
+        (core_modules + plugins).each { |p|
+         next unless p.name == key
           begin
             return p.help(key, params)
           rescue Exception => err
           begin
             return p.help(key, params)
           rescue Exception => err
@@ -518,16 +559,17 @@ module Plugins
           end
         }
 
           end
         }
 
-       # If no such commmand was found, we look for a botmodule with that name
-        (core_modules + plugins).each { |p|
-         next unless p.name == key
+       # Nope, let's see if it's a command, and ask for help at the corresponding botmodule
+        k = key.to_sym
+        if commands.has_key?(k)
+          p = commands[k][:botmodule] 
           begin
             return p.help(key, params)
           rescue Exception => err
             #rescue TimeoutError, StandardError, NameError, SyntaxError => err
             error report_error("#{p.botmodule_class} #{p.name} help() failed:", err)
           end
           begin
             return p.help(key, params)
           rescue Exception => err
             #rescue TimeoutError, StandardError, NameError, SyntaxError => err
             error report_error("#{p.botmodule_class} #{p.name} help() failed:", err)
           end
-        }
+        end
       end
       return false
     end
       end
       return false
     end
@@ -558,52 +600,41 @@ module Plugins
     def privmsg(m)
       # debug "Delegating privmsg #{m.message.inspect} from #{m.source} to #{m.replyto} with pluginkey #{m.plugin.inspect}"
       return unless m.plugin
     def privmsg(m)
       # debug "Delegating privmsg #{m.message.inspect} from #{m.source} to #{m.replyto} with pluginkey #{m.plugin.inspect}"
       return unless m.plugin
-      [core_commands, plugin_commands].each { |pl|
-        # We do it this way to skip creating spurious keys
-        # FIXME use fetch?
-        k = m.plugin.to_sym
-        if pl.has_key?(k)
-          p = pl[k][:botmodule]
-          a = pl[k][:auth]
-        else
-          p = nil
-          a = nil
-        end
-        if p
-          # We check here for things that don't check themselves
-          # (e.g. mapped things)
-          # debug "Checking auth ..."
-          if a.nil? || @bot.auth.allow?(a, m.source, m.replyto)
-            # debug "Checking response ..."
-            if p.respond_to?("privmsg")
-              begin
-                # debug "#{p.botmodule_class} #{p.name} responds"
-                p.privmsg(m)
-              rescue Exception => err
-                raise if err.kind_of?(SystemExit)
-                error report_error("#{p.botmodule_class} #{p.name} privmsg() failed:", err)
-                raise if err.kind_of?(BDB::Fatal)
-              end
-              # debug "Successfully delegated #{m.message}"
-              return true
-            else
-              # debug "#{p.botmodule_class} #{p.name} is registered, but it doesn't respond to privmsg()"
+      k = m.plugin.to_sym
+      if commands.has_key?(k)
+        p = commands[k][:botmodule]
+        a = commands[k][:auth]
+        # We check here for things that don't check themselves
+        # (e.g. mapped things)
+        # debug "Checking auth ..."
+        if a.nil? || @bot.auth.allow?(a, m.source, m.replyto)
+          # debug "Checking response ..."
+          if p.respond_to?("privmsg")
+            begin
+              # debug "#{p.botmodule_class} #{p.name} responds"
+              p.privmsg(m)
+            rescue Exception => err
+              raise if err.kind_of?(SystemExit)
+              error report_error("#{p.botmodule_class} #{p.name} privmsg() failed:", err)
+              raise if err.kind_of?(BDB::Fatal)
             end
             end
+            # debug "Successfully delegated #{m.message}"
+            return true
           else
           else
-            # debug "#{p.botmodule_class} #{p.name} is registered, but #{m.source} isn't allowed to call #{m.plugin.inspect} on #{m.replyto}"
+            # debug "#{p.botmodule_class} #{p.name} is registered, but it doesn't respond to privmsg()"
           end
         else
           end
         else
-          # debug "No #{pl.values.first[:botmodule].botmodule_class} registered #{m.plugin.inspect}" unless pl.empty?
+          # debug "#{p.botmodule_class} #{p.name} is registered, but #{m.source} isn't allowed to call #{m.plugin.inspect} on #{m.replyto}"
         end
         end
-        # debug "Finished delegating privmsg with key #{m.plugin.inspect}" + ( pl.empty? ? "" : " to #{pl.values.first[:botmodule].botmodule_class}s" )
-      }
+      end
+      # debug "Finished delegating privmsg with key #{m.plugin.inspect}" + ( pl.empty? ? "" : " to #{pl.values.first[:botmodule].botmodule_class}s" )
       return false
       # debug "Finished delegating privmsg with key #{m.plugin.inspect}"
     end
   end
 
   # Returns the only PluginManagerClass instance
       return false
       # debug "Finished delegating privmsg with key #{m.plugin.inspect}"
     end
   end
 
   # Returns the only PluginManagerClass instance
-  def Plugins.pluginmanager
+  def Plugins.manager
     return PluginManagerClass.instance
   end
 
     return PluginManagerClass.instance
   end