X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=lib%2Frbot%2Fplugins.rb;h=4d51cfc5e845d380cef3b1e588755dfe33353ef5;hb=47bcf91419e614b34ed04e05c8d28663c6c5202c;hp=564495e0c5d89beaba2566e68ce59cfb2974b335;hpb=cb722191584309094ce0a57b95000e2e0751988d;p=user%2Fhenk%2Fcode%2Fruby%2Frbot.git diff --git a/lib/rbot/plugins.rb b/lib/rbot/plugins.rb index 564495e0..4d51cfc5 100644 --- a/lib/rbot/plugins.rb +++ b/lib/rbot/plugins.rb @@ -1,23 +1,36 @@ +#-- vim:sw=2:et +#++ +# +# :title: rbot plugin management + require 'singleton' module Irc - BotConfig.register BotConfigArrayValue.new('plugins.blacklist', +class Bot + Config.register Config::ArrayValue.new('plugins.blacklist', :default => [], :wizard => false, :requires_rescan => true, :desc => "Plugins that should not be loaded") module Plugins require 'rbot/messagemapper' -=begin - base class for all rbot plugins - certain methods will be called if they are provided, if you define one of - the following methods, it will be called as appropriate: +=begin rdoc + BotModule is the base class for the modules that enhance the rbot + functionality. Rather than subclassing BotModule, however, one should + subclass either CoreBotModule (reserved for system modules) or Plugin + (for user plugins). + + A BotModule interacts with Irc events by defining one or more of the following + methods, which get called as appropriate when the corresponding Irc event + happens. map(template, options):: map!(template, options):: - map is the new, cleaner way to respond to specific message formats - without littering your plugin code with regexps. The difference - between map and map! is that map! will not register the new command - as an alternative name for the plugin. + map is the new, cleaner way to respond to specific message formats without + littering your plugin code with regexps, and should be used instead of + #register() and #privmsg() (see below) when possible. + + The difference between map and map! is that map! will not register the new + command as an alternative name for the plugin. Examples: @@ -49,9 +62,11 @@ module Plugins plugin.map 'karmastats', :auth => 'karma' # maps can be restricted to public or private message: - plugin.map 'karmastats', :private false, - plugin.map 'karmastats', :public false, - end + plugin.map 'karmastats', :private => false + plugin.map 'karmastats', :public => false + + See MessageMapper#map for more information on the template format and the + allowed options. listen(UserMessage):: Called for all messages of any type. To @@ -60,16 +75,38 @@ module Plugins QuitMessage, PartMessage, JoinMessage, NickMessage, etc. + ctcp_listen(UserMessage):: + Called for all messages that contain a CTCP command. + Use message.ctcp to get the CTCP command, and + message.message to get the parameter string. To reply, + use message.ctcp_reply, which sends a private NOTICE + to the sender. + + message(PrivMessage):: + Called for all PRIVMSG. Hook on this method if you + need to handle PRIVMSGs regardless of whether they are + addressed to the bot or not, and regardless of + privmsg(PrivMessage):: - called for a PRIVMSG if the first word matches one - the plugin register()d for. Use m.plugin to get + Called for a PRIVMSG if the first word matches one + the plugin #register()ed 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. + + notice(NoticeMessage):: + Called for all Notices. Please notice that in general + should not be replied to. + kick(KickMessage):: Called when a user (or the bot) is kicked from a channel the bot is in. + invite(InviteMessage):: + Called when the bot is invited to a channel. + join(JoinMessage):: Called when a user (or the bot) joins a channel @@ -81,11 +118,21 @@ module Plugins nick(NickMessage):: Called when a user (or the bot) changes Nick + modechange(ModeChangeMessage):: + Called when a User or Channel mode is changed topic(TopicMessage):: Called when a user (or the bot) changes a channel topic - connect():: Called when a server is joined successfully, but + welcome(WelcomeMessage):: + Called when the welcome message is received on + joining a server succesfully. + + motd(MotdMessage):: + Called when the Message Of The Day is fully + recevied from the server. + + connect:: Called when a server is joined successfully, but before autojoin channels are joined (no params) set_language(String):: @@ -101,18 +148,43 @@ module Plugins =end class BotModule - attr_reader :bot # the associated bot + # the associated bot + attr_reader :bot + + # the plugin registry + attr_reader :registry - # initialise your bot module. Always call super if you override this method, - # as important variables are set up for you + # the message map handler + attr_reader :handler + + # Initialise your bot module. Always call super if you override this method, + # as important variables are set up for you: + # + # @bot:: + # the rbot instance + # @registry:: + # the botmodule's registry, which can be used to store permanent data + # (see Registry::Accessor for additional documentation) + # + # Other instance variables which are defined and should not be overwritten + # byt the user, but aren't usually accessed directly, are: + # + # @manager:: + # the plugins manager instance + # @botmodule_triggers:: + # an Array of words this plugin #register()ed itself for + # @handler:: + # the MessageMapper that handles this plugin's maps + # def initialize - @manager = Plugins::pluginmanager + @manager = Plugins::manager @bot = @manager.bot + @priority = nil @botmodule_triggers = Array.new @handler = MessageMapper.new(self) - @registry = BotRegistryAccessor.new(@bot, self.class.to_s.gsub(/^.*::/, "")) + @registry = Registry::Accessor.new(@bot, self.class.to_s.gsub(/^.*::/, "")) @manager.add_botmodule(self) if self.respond_to?('set_language') @@ -120,43 +192,76 @@ module Plugins end end + # Changing the value of @priority directly will cause problems, + # Please use priority=. + def priority + @priority ||= 1 + end + + # Returns the symbol :BotModule def botmodule_class :BotModule end + # Method called to flush the registry, thus ensuring that the botmodule's permanent + # data is committed to disk + # def flush_registry # debug "Flushing #{@registry}" @registry.flush end + # Method called to cleanup before the plugin is unloaded. If you overload + # this method to handle additional cleanup tasks, remember to call super() + # so that the default cleanup actions are taken care of as well. + # def cleanup # debug "Closing #{@registry}" @registry.close end + # Handle an Irc::PrivMessage for which this BotModule has a map. The method + # is called automatically and there is usually no need to call it + # explicitly. + # def handle(m) @handler.handle(m) end + # Signal to other BotModules that an even happened. + # + def call_event(ev, *args) + @bot.plugins.delegate('event_' + ev.to_s.gsub(/[^\w\?!]+/, '_'), *(args.push Hash.new)) + end + + # call-seq: map(template, options) + # + # This is the preferred way to register the BotModule so that it + # responds to appropriately-formed messages on Irc. + # def map(*args) - @handler.map(self, *args) - # register this map - name = @handler.last.items[0] - self.register name, :auth => nil - unless self.respond_to?('privmsg') - def self.privmsg(m) - handle(m) - end - end + do_map(false, *args) end + # call-seq: map!(template, options) + # + # This is the same as map but doesn't register the new command + # as an alternative name for the plugin. + # def map!(*args) + do_map(true, *args) + end + + # Auxiliary method called by #map and #map! + def do_map(silent, *args) @handler.map(self, *args) # register this map - name = @handler.last.items[0] - self.register name, :auth => nil, :hidden => true + map = @handler.last + name = map.items[0] + self.register name, :auth => nil, :hidden => silent + @manager.register_map(self, map) unless self.respond_to?('privmsg') - def self.privmsg(m) + def self.privmsg(m) #:nodoc: handle(m) end end @@ -181,23 +286,23 @@ module Plugins [name, cmd].compact.join("::") end - # return an identifier for this plugin, defaults to a list of the message + # Return an identifier for this plugin, defaults to a list of the message # prefixes handled (used for error messages etc) def name self.class.to_s.downcase.sub(/^#::/,"").sub(/(plugin|module)?$/,"") end - # just calls name + # Just calls name def to_s name end - # intern the name + # Intern the name def to_sym self.name.to_sym end - # return a help string for your module. for complex modules, you may wish + # 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 # this message - if your plugin handles multiple prefixes, make sure you @@ -206,9 +311,14 @@ module Plugins "no help" end - # register the plugin as a handler for messages prefixed +name+ - # this can be called multiple times for a plugin to handle multiple - # message prefixes + # Register the plugin as a handler for messages prefixed _cmd_. + # + # This can be called multiple times for a plugin to handle multiple message + # prefixes. + # + # This command is now superceded by the #map() command, which should be used + # instead whenever possible. + # def register(cmd, opts={}) raise ArgumentError, "Second argument must be a hash!" unless opts.kind_of?(Hash) who = @manager.who_handles?(cmd) @@ -224,20 +334,41 @@ module Plugins @botmodule_triggers << cmd unless opts.fetch(:hidden, false) end - # default usage method provided as a utility for simple plugins. The + # Default usage method provided as a utility for simple plugins. The # MessageMapper uses 'usage' as its default fallback method. + # def usage(m, params = {}) - m.reply "incorrect usage, ask for help using '#{@bot.nick}: help #{m.plugin}'" + m.reply(_("incorrect usage, ask for help using '%{command}'") % {:command => "#{@bot.nick}: help #{m.plugin}"}) end + # Define the priority of the module. During event delegation, lower + # priority modules will be called first. Default priority is 1 + def priority=(prio) + if @priority != prio + @priority = prio + @bot.plugins.mark_priorities_dirty + end + end end + # A CoreBotModule is a BotModule that provides core functionality. + # + # This class should not be used by user plugins, as it's reserved for system + # plugins such as the ones that handle authentication, configuration and basic + # functionality. + # class CoreBotModule < BotModule def botmodule_class :CoreBotModule end end + # A Plugin is a BotModule that provides additional functionality. + # + # A user-defined plugin should subclass this, and then define any of the + # methods described in the documentation for BotModule to handle interaction + # with Irc events. + # class Plugin < BotModule def botmodule_class :Plugin @@ -250,6 +381,17 @@ module Plugins include Singleton attr_reader :bot attr_reader :botmodules + attr_reader :maps + + # This is the list of patterns commonly delegated to plugins. + # A fast delegation lookup is enabled for them. + DEFAULT_DELEGATE_PATTERNS = %r{^(?: + connect|names|nick| + listen|ctcp_listen|privmsg|unreplied| + kick|join|part|quit| + save|cleanup|flush_registry| + set_.*|event_.* + )$}x def initialize @botmodules = { @@ -259,6 +401,14 @@ module Plugins @names_hash = Hash.new @commandmappers = Hash.new + @maps = Hash.new + + # modules will be sorted on first delegate call + @sorted_modules = nil + + @delegate_list = Hash.new { |h, k| + h[k] = Array.new + } @dirs = [] @@ -268,12 +418,28 @@ module Plugins bot_associate(nil) end + def inspect + ret = self.to_s[0..-2] + ret << ' corebotmodules=' + ret << @botmodules[:CoreBotModule].map { |m| + m.name + }.inspect + ret << ' plugins=' + ret << @botmodules[:Plugin].map { |m| + m.name + }.inspect + ret << ">" + end + # Reset lists of botmodules def reset_botmodule_lists @botmodules[:CoreBotModule].clear @botmodules[:Plugin].clear @names_hash.clear @commandmappers.clear + @maps.clear + @failures_shown = false + mark_priorities_dirty end # Associate with bot _bot_ @@ -299,6 +465,19 @@ module Plugins @commandmappers[cmd.to_sym] = {:botmodule => botmodule, :auth => auth_path} end + # Registers botmodule _botmodule_ with map _map_. This adds the map to the #maps hash + # which has three keys: + # + # botmodule:: the associated botmodule + # auth:: an array of auth keys checked by the map; the first is the full_auth_path of the map + # map:: the actual MessageTemplate object + # + # + def register_map(botmodule, map) + raise TypeError, "First argument #{botmodule.inspect} is not of class BotModule" unless botmodule.kind_of?(BotModule) + @maps[map.template] = { :botmodule => botmodule, :auth => [map.options[:full_auth_path]], :map => map } + end + def add_botmodule(botmodule) raise TypeError, "Argument #{botmodule.inspect} is not of class BotModule" unless botmodule.kind_of?(BotModule) kl = botmodule.botmodule_class @@ -312,6 +491,7 @@ module Plugins end @botmodules[kl] << botmodule @names_hash[botmodule.to_sym] = botmodule + mark_priorities_dirty end # Returns an array of the loaded plugins @@ -330,6 +510,12 @@ module Plugins @commandmappers end + # Tells the PluginManager that the next time it delegates an event, it + # should sort the modules by priority + def mark_priorities_dirty + @sorted_modules = nil + end + # Makes a string of error _err_ by adding text _str_ def report_error(str, err) ([str, err.inspect] + err.backtrace).join("\n") @@ -348,17 +534,19 @@ module Plugins # the idea here is to prevent namespace pollution. perhaps there # is another way? plugin_module = Module.new + # each plugin uses its own textdomain, we bind it automatically here + bindtextdomain_to(plugin_module, "rbot-#{File.basename(fname, '.rb')}") desc = desc.to_s + " " if desc begin - plugin_string = IO.readlines(fname).join("") + plugin_string = IO.read(fname) debug "loading #{desc}#{fname}" plugin_module.module_eval(plugin_string, fname) return :loaded rescue Exception => err # rescue TimeoutError, StandardError, NameError, LoadError, SyntaxError => err - warning report_error("#{desc}#{fname} load failed", err) + error report_error("#{desc}#{fname} load failed", err) bt = err.backtrace.select { |line| line.match(/^(\(eval\)|#{fname}):\d+/) } @@ -387,10 +575,17 @@ module Plugins 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 @failed.clear @ignored.clear + @delegate_list.clear + processed = Hash.new @bot.config['plugins.blacklist'].each { |p| @@ -435,6 +630,12 @@ module Plugins end } debug "finished loading plugins: #{status(true)}" + (core_modules + plugins).each { |p| + p.methods.grep(DEFAULT_DELEGATE_PATTERNS).each { |m| + @delegate_list[m.intern] << p + } + } + mark_priorities_dirty end # call the save method for each active plugin @@ -458,44 +659,72 @@ module Plugins end def status(short=false) - list = "" + output = [] if self.core_length > 0 - list << "#{self.core_length} core module#{'s' if core_length > 1}" if short - list << " loaded" + output << n_("%{count} core module loaded", "%{count} core modules loaded", + self.core_length) % {:count => self.core_length} else - list << ": " + core_modules.collect{ |p| p.name}.sort.join(", ") + output << n_("%{count} core module: %{list}", + "%{count} core modules: %{list}", self.core_length) % + { :count => self.core_length, + :list => core_modules.collect{ |p| p.name}.sort.join(", ") } end else - list << "no core botmodules loaded" + output << _("no core botmodules loaded") end # Active plugins first if(self.length > 0) - list << "; #{self.length} plugin#{'s' if length > 1}" if short - list << " loaded" + output << n_("%{count} plugin loaded", "%{count} plugins loaded", + self.length) % {:count => self.length} else - list << ": " + plugins.collect{ |p| p.name}.sort.join(", ") + output << n_("%{count} plugin: %{list}", + "%{count} plugins: %{list}", self.length) % + { :count => self.length, + :list => plugins.collect{ |p| p.name}.sort.join(", ") } end else - list << "no plugins active" + output << "no plugins active" end # Ignored plugins next - unless @ignored.empty? - list << "; #{Underline}#{@ignored.length} plugin#{'s' if @ignored.length > 1} ignored#{Underline}" - list << ": use #{Bold}help ignored plugins#{Bold} to see why" unless short + unless @ignored.empty? or @failures_shown + if short + output << n_("%{highlight}%{count} plugin ignored%{highlight}", + "%{highlight}%{count} plugins ignored%{highlight}", + @ignored.length) % + { :count => @ignored.length, :highlight => Underline } + else + output << n_("%{highlight}%{count} plugin ignored%{highlight}: use %{bold}%{command}%{bold} to see why", + "%{highlight}%{count} plugins ignored%{highlight}: use %{bold}%{command}%{bold} to see why", + @ignored.length) % + { :count => @ignored.length, :highlight => Underline, + :bold => Bold, :command => "help ignored plugins"} + end end # Failed plugins next - unless @failed.empty? - 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 + unless @failed.empty? or @failures_shown + if short + output << n_("%{highlight}%{count} plugin failed to load%{highlight}", + "%{highlight}%{count} plugins failed to load%{highlight}", + @failed.length) % + { :count => @failed.length, :highlight => Reverse } + else + output << n_("%{highlight}%{count} plugin failed to load%{highlight}: use %{bold}%{command}%{bold} to see why", + "%{highlight}%{count} plugins failed to load%{highlight}: use %{bold}%{command}%{bold} to see why", + @failed.length) % + { :count => @failed.length, :highlight => Reverse, + :bold => Bold, :command => "help failed plugins"} + end end - list + output.join '; ' end # return list of help topics (plugin names) def helptopics - return status + rv = status + @failures_shown = true + rv end def length @@ -511,31 +740,40 @@ module Plugins case topic when /fail(?:ed)?\s*plugins?.*(trace(?:back)?s?)?/ # debug "Failures: #{@failed.inspect}" - return "no plugins failed to load" if @failed.empty? - return @failed.inject(Array.new) { |list, p| - list << "#{Bold}#{p[:name]}#{Bold} in #{p[:dir]} failed" - list << "with error #{p[:reason].class}: #{p[:reason]}" - list << "at #{p[:reason].backtrace.join(', ')}" if $1 and not p[:reason].backtrace.empty? - list + return _("no plugins failed to load") if @failed.empty? + return @failed.collect { |p| + _('%{highlight}%{plugin}%{highlight} in %{dir} failed with error %{exception}: %{reason}') % { + :highlight => Bold, :plugin => p[:name], :dir => p[:dir], + :exception => p[:reason].class, :reason => p[:reason], + } + if $1 && !p[:reason].backtrace.empty? + _('at %{backtrace}') % {:backtrace => p[:reason].backtrace.join(', ')} + else + '' + end }.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(", ") + return _('no plugins were ignored') if @ignored.empty? + + 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| + # FIXME get rid of these string concatenations to make gettext easier + s = reasons.map { |r, list| + list.map { |_| _.sub(/\.rb$/, '') }.join(', ') + " (#{r})" + }.join('; ') + "in #{dir}: #{s}" + end.join('; ') when /^(\S+)\s*(.*)$/ key = $1 params = $2 - # Let's see if we can match a plugin by the given name + # Let's see if we can match a plugin by the given name (core_modules + plugins).each { |p| - next unless p.name == key + next unless p.name == key begin return p.help(key, params) rescue Exception => err @@ -544,10 +782,10 @@ module Plugins end } - # Nope, let's see if it's a command, and ask for help at the corresponding botmodule + # 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] + p = commands[k][:botmodule] begin return p.help(key, params) rescue Exception => err @@ -559,16 +797,95 @@ module Plugins return false end - # see if each plugin handles +method+, and if so, call it, passing - # +message+ as a parameter + def sort_modules + @sorted_modules = (core_modules + plugins).sort do |a, b| + a.priority <=> b.priority + end || [] + + @delegate_list.each_value do |list| + list.sort! {|a,b| a.priority <=> b.priority} + end + end + + # call-seq: delegate(method, m, opts={}) + # delegate(method, opts={}) + # + # see if each plugin handles _method_, and if so, call it, passing + # _m_ as a parameter (if present). BotModules are called in order of + # priority from lowest to highest. + # + # If the passed _m_ is a BasicUserMessage and is marked as #ignored?, it + # will only be delegated to plugins with negative priority. Conversely, if + # it's a fake message (see BotModule#fake_message), it will only be + # delegated to plugins with positive priority. + # + # Note that _m_ can also be an exploded Array, but in this case the last + # element of it cannot be a Hash, or it will be interpreted as the options + # Hash for delegate itself. The last element can be a subclass of a Hash, though. + # To be on the safe side, you can add an empty Hash as last parameter for delegate + # when calling it with an exploded Array: + # @bot.plugins.delegate(method, *(args.push Hash.new)) + # + # Currently supported options are the following: + # :above :: + # if specified, the delegation will only consider plugins with a priority + # higher than the specified value + # :below :: + # if specified, the delegation will only consider plugins with a priority + # lower than the specified value + # def delegate(method, *args) + # if the priorities order of the delegate list is dirty, + # meaning some modules have been added or priorities have been + # changed, then the delegate list will need to be sorted before + # delegation. This should always be true for the first delegation. + sort_modules unless @sorted_modules + + opts = {} + opts.merge(args.pop) if args.last.class == Hash + + m = args.first + if BasicUserMessage === m + # ignored messages should not be delegated + # to plugins with positive priority + opts[:below] ||= 0 if m.ignored? + # fake messages should not be delegated + # to plugins with negative priority + opts[:above] ||= 0 if m.recurse_depth > 0 + end + + above = opts[:above] + below = opts[:below] + # debug "Delegating #{method.inspect}" - [core_modules, plugins].each { |pl| - pl.each {|p| + ret = Array.new + if method.match(DEFAULT_DELEGATE_PATTERNS) + debug "fast-delegating #{method}" + m = method.to_sym + debug "no-one to delegate to" unless @delegate_list.has_key?(m) + return [] unless @delegate_list.has_key?(m) + @delegate_list[m].each { |p| + begin + prio = p.priority + unless (above and above >= prio) or (below and below <= prio) + ret.push p.send(method, *args) + end + rescue Exception => err + raise if err.kind_of?(SystemExit) + error report_error("#{p.botmodule_class} #{p.name} #{method}() failed:", err) + raise if err.kind_of?(BDB::Fatal) + end + } + else + debug "slow-delegating #{method}" + @sorted_modules.each { |p| if(p.respond_to? method) begin # debug "#{p.botmodule_class} #{p.name} responds" - p.send method, *args + prio = p.priority + unless (above and above >= prio) or (below and below <= prio) + ret.push p.send(method, *args) + end rescue Exception => err raise if err.kind_of?(SystemExit) error report_error("#{p.botmodule_class} #{p.name} #{method}() failed:", err) @@ -576,14 +893,15 @@ module Plugins end end } - } + end + return ret # debug "Finished delegating #{method.inspect}" end # see if we have a plugin that wants to handle this message, if so, pass # it to the plugin and return true, otherwise false def privmsg(m) - # debug "Delegating privmsg #{m.message.inspect} from #{m.source} to #{m.replyto} with pluginkey #{m.plugin.inspect}" + debug "Delegating privmsg #{m.inspect} with pluginkey #{m.plugin.inspect}" return unless m.plugin k = m.plugin.to_sym if commands.has_key?(k) @@ -591,37 +909,53 @@ module Plugins a = commands[k][:auth] # We check here for things that don't check themselves # (e.g. mapped things) - # debug "Checking auth ..." + debug "Checking auth ..." if a.nil? || @bot.auth.allow?(a, m.source, m.replyto) - # debug "Checking response ..." + debug "Checking response ..." if p.respond_to?("privmsg") begin - # debug "#{p.botmodule_class} #{p.name} responds" + 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}" + debug "Successfully delegated #{m.inspect}" return true else - # debug "#{p.botmodule_class} #{p.name} is registered, but it doesn't respond to privmsg()" + debug "#{p.botmodule_class} #{p.name} is registered, but it doesn't respond to privmsg()" end 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 #{m.source} isn't allowed to call #{m.plugin.inspect} on #{m.replyto}" end + else + debug "Command #{k} isn't handled" 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 + + # delegate IRC messages, by delegating 'listen' first, and the actual method + # afterwards. Delegating 'privmsg' also delegates ctcp_listen and message + # as appropriate. + def irc_delegate(method, m) + delegate('listen', m) + if method.to_sym == :privmsg + delegate('ctcp_listen', m) if m.ctcp + delegate('message', m) + privmsg(m) if m.address? and not m.ignored? + delegate('unreplied', m) unless m.replied + else + delegate(method, m) + end end end # Returns the only PluginManagerClass instance - def Plugins.pluginmanager + def Plugins.manager return PluginManagerClass.instance end end end +end