summaryrefslogtreecommitdiff
path: root/lib/rbot/plugins.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rbot/plugins.rb')
-rw-r--r--lib/rbot/plugins.rb264
1 files changed, 169 insertions, 95 deletions
diff --git a/lib/rbot/plugins.rb b/lib/rbot/plugins.rb
index 50b5576b..546a9b30 100644
--- a/lib/rbot/plugins.rb
+++ b/lib/rbot/plugins.rb
@@ -1,3 +1,5 @@
+require 'singleton'
+
module Irc
BotConfig.register BotConfigArrayValue.new('plugins.blacklist',
:default => [], :wizard => false, :requires_restart => true,
@@ -94,13 +96,13 @@ module Plugins
files/connections or flush caches here
=end
- class Plugin
+ class BotModule
attr_reader :bot # the associated bot
- # initialise your plugin. Always call super if you override this method,
+ # initialise your bot module. Always call super if you override this method,
# as important variables are set up for you
def initialize
- @bot = Plugins.bot
- @names = Array.new
+ @bot = Plugins.pluginmanager.bot
+ @botmodule_triggers = Array.new
@handler = MessageMapper.new(self)
@registry = BotRegistryAccessor.new(@bot, self.class.to_s.gsub(/^.*::/, ""))
end
@@ -146,7 +148,7 @@ module Plugins
# return an identifier for this plugin, defaults to a list of the message
# prefixes handled (used for error messages etc)
def name
- @names.join("|")
+ self.class.downcase.sub(/(plugin)?$/,"")
end
# return a help string for your module. for complex modules, you may wish
@@ -161,11 +163,11 @@ module Plugins
# register the plugin as a handler for messages prefixed +name+
# this can be called multiple times for a plugin to handle multiple
# message prefixes
- def register(name,opts={})
- raise ArgumentError, "Second argument must be a hash!" unless opts.kind_of?(Hash)
- return if Plugins.plugins.has_key?(name)
- Plugins.plugins[name] = self
- @names << name unless opts.fetch(:hidden, false)
+ def register(name, kl, opts={})
+ raise ArgumentError, "Third argument must be a hash!" unless opts.kind_of?(Hash)
+ return if Plugins.pluginmanager.botmodules[kl].has_key?(name)
+ Plugins.pluginmanager.botmodules[kl][name] = self
+ @botmodule_triggers << name unless opts.fetch(:hidden, false)
end
# default usage method provided as a utility for simple plugins. The
@@ -176,46 +178,140 @@ module Plugins
end
+ class CoreBotModule < BotModule
+ def register(name, opts={})
+ raise ArgumentError, "Second argument must be a hash!" unless opts.kind_of?(Hash)
+ super(name, :core, opts)
+ end
+ end
+
+ class Plugin < BotModule
+ def register(name, opts={})
+ raise ArgumentError, "Second argument must be a hash!" unless opts.kind_of?(Hash)
+ super(name, :plugin, opts)
+ end
+ end
+
# class to manage multiple plugins and delegate messages to them for
# handling
- class Plugins
- # hash of registered message prefixes and associated plugins
- @@plugins = Hash.new
- # associated IrcBot class
- @@bot = nil
+ class PluginManagerClass
+ include Singleton
+ attr_reader :bot
+ attr_reader :botmodules
+
+ def initialize
+ bot_associate(nil)
+ end
+
+ # Associate with bot _bot_
+ def bot_associate(bot)
+ @botmodules = {
+ :core => Hash.new,
+ :plugin => Hash.new
+ }
+
+ # associated IrcBot class
+ @bot = bot
+ end
+
+ # Returns a hash of the registered message prefixes and associated
+ # plugins
+ def plugins
+ @botmodules[:plugin]
+ end
+
+ # Returns a hash of the registered message prefixes and associated
+ # core modules
+ def core_modules
+ @botmodules[:core]
+ end
+
+ # Makes a string of error _err_ by adding text _str_
+ def report_error(str, err)
+ ([str, err.inspect] + err.backtrace).join("\n")
+ end
+
+ # This method is the one that actually loads a module from the
+ # file _fname_
+ #
+ # _desc_ is a simple description of what we are loading (plugin/botmodule/whatever)
+ #
+ # It returns the Symbol :loaded on success, and an Exception
+ # on failure
+ #
+ def load_botmodule_file(fname, desc=nil)
+ # create a new, anonymous module to "house" the plugin
+ # the idea here is to prevent namespace pollution. perhaps there
+ # is another way?
+ plugin_module = Module.new
+
+ desc = desc.to_s + " " if desc
+ begin
+ plugin_string = IO.readlines(fname).join("")
+ 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)
+ bt = err.backtrace.select { |line|
+ line.match(/^(\(eval\)|#{fname}):\d+/)
+ }
+ bt.map! { |el|
+ el.gsub(/^\(eval\)(:\d+)(:in `.*')?(:.*)?/) { |m|
+ "#{fname}#{$1}#{$3}"
+ }
+ }
+ msg = err.to_str.gsub(/^\(eval\)(:\d+)(:in `.*')?(:.*)?/) { |m|
+ "#{fname}#{$1}#{$3}"
+ }
+ newerr = err.class.new(msg)
+ newerr.set_backtrace(bt)
+ return newerr
+ end
+ end
+ private :load_botmodule_file
+
+ # Load core botmodules
+ def load_core(dir)
+ # TODO FIXME should this be hardcoded?
+ if(FileTest.directory?(dir))
+ d = Dir.new(dir)
+ d.sort.each { |file|
+ next unless(file =~ /[^.]\.rb$/)
+
+ did_it = load_botmodule_file("#{dir}/#{file}", "core module")
+ case did_it
+ when Symbol
+ # debug "loaded core botmodule #{dir}/#{file}"
+ when Exception
+ raise "failed to load core botmodule #{dir}/#{file}!"
+ end
+ }
+ end
+ end
- # bot:: associated IrcBot class
# dirlist:: array of directories to scan (in order) for plugins
#
# create a new plugin handler, scanning for plugins in +dirlist+
- def initialize(bot, dirlist)
- @@bot = bot
+ def load_plugins(dirlist)
@dirs = dirlist
scan
end
- # access to associated bot
- def Plugins.bot
- @@bot
- end
-
- # access to list of plugins
- def Plugins.plugins
- @@plugins
- end
-
# load plugins from pre-assigned list of directories
def scan
@failed = Array.new
@ignored = Array.new
processed = Hash.new
- @@bot.config['plugins.blacklist'].each { |p|
+ @bot.config['plugins.blacklist'].each { |p|
pn = p + ".rb"
processed[pn.intern] = :blacklisted
}
dirs = Array.new
+ # TODO FIXME should this be hardcoded?
dirs << Config::datadir + "/plugins"
dirs += @dirs
dirs.reverse.each {|dir|
@@ -242,40 +338,14 @@ module Plugins
next unless(file =~ /\.rb$/)
- tmpfilename = "#{dir}/#{file}"
-
- # create a new, anonymous module to "house" the plugin
- # the idea here is to prevent namespace pollution. perhaps there
- # is another way?
- plugin_module = Module.new
-
- begin
- plugin_string = IO.readlines(tmpfilename).join("")
- debug "loading plugin #{tmpfilename}"
- plugin_module.module_eval(plugin_string, tmpfilename)
- processed[file.intern] = :loaded
- rescue Exception => err
- # rescue TimeoutError, StandardError, NameError, LoadError, SyntaxError => err
- warning "plugin #{tmpfilename} load failed\n" + err.inspect
- debug err.backtrace.join("\n")
- bt = err.backtrace.select { |line|
- line.match(/^(\(eval\)|#{tmpfilename}):\d+/)
- }
- bt.map! { |el|
- el.gsub(/^\(eval\)(:\d+)(:in `.*')?(:.*)?/) { |m|
- "#{tmpfilename}#{$1}#{$3}"
- }
- }
- msg = err.to_str.gsub(/^\(eval\)(:\d+)(:in `.*')?(:.*)?/) { |m|
- "#{tmpfilename}#{$1}#{$3}"
- }
- newerr = err.class.new(msg)
- newerr.set_backtrace(bt)
- # debug "Simplified error: " << newerr.inspect
- # debug newerr.backtrace.join("\n")
- @failed << { :name => file, :dir => dir, :reason => newerr }
- # debug "Failures: #{@failed.inspect}"
+ did_it = load_botmodule_file("#{dir}/#{file}", "plugin")
+ case did_it
+ when Symbol
+ processed[file.intern] = did_it
+ when Exception
+ @failed << { :name => file, :dir => dir, :reason => did_it }
end
+
}
end
}
@@ -297,15 +367,14 @@ module Plugins
def rescan
save
cleanup
- @@plugins = Hash.new
+ plugins.clear
scan
-
end
def status(short=false)
# Active plugins first
- if(@@plugins.length > 0)
- list = "#{length} plugin#{'s' if length > 1}"
+ if(self.length > 0)
+ list = "#{self.length} plugin#{'s' if length > 1}"
if short
list << " loaded"
else
@@ -333,7 +402,7 @@ module Plugins
end
def length
- @@plugins.values.uniq.length
+ plugins.values.uniq.length
end
# return help for +topic+ (call associated plugin's help method)
@@ -367,8 +436,7 @@ module Plugins
return @@plugins[key].help(key, params)
rescue Exception => err
#rescue TimeoutError, StandardError, NameError, SyntaxError => err
- error "plugin #{@@plugins[key].name} help() failed: #{err.class}: #{err}"
- error err.backtrace.join("\n")
+ error report_error("plugin #{@@plugins[key].name} help() failed:", err)
end
else
return false
@@ -379,42 +447,48 @@ module Plugins
# see if each plugin handles +method+, and if so, call it, passing
# +message+ as a parameter
def delegate(method, *args)
- @@plugins.values.uniq.each {|p|
- if(p.respond_to? method)
- begin
- p.send method, *args
- rescue Exception => err
- #rescue TimeoutError, StandardError, NameError, SyntaxError => err
- error "plugin #{p.name} #{method}() failed: #{err.class}: #{err}"
- error err.backtrace.join("\n")
+ [core_modules, plugins].each { |pl|
+ pl.values.uniq.each {|p|
+ if(p.respond_to? method)
+ begin
+ p.send method, *args
+ rescue Exception => err
+ #rescue TimeoutError, StandardError, NameError, SyntaxError => err
+ error report_error("plugin #{p.name} #{method}() failed:", err)
+ end
end
- end
+ }
}
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)
- return unless(m.plugin)
- if (@@plugins.has_key?(m.plugin) &&
- @@plugins[m.plugin].respond_to?("privmsg") &&
- @@bot.auth.allow?(m.plugin, m.source, m.replyto))
- begin
- @@plugins[m.plugin].privmsg(m)
- rescue BDB::Fatal => err
- error "plugin #{@@plugins[m.plugin].name} privmsg() failed: #{err.class}: #{err}"
- error err.backtrace.join("\n")
- raise
- rescue Exception => err
- #rescue TimeoutError, StandardError, NameError, SyntaxError => err
- error "plugin #{@@plugins[m.plugin].name} privmsg() failed: #{err.class}: #{err}"
- error err.backtrace.join("\n")
+ [core_modules, plugins].each { |pl|
+ return unless(m.plugin)
+ if (pl.has_key?(m.plugin) &&
+ pl[m.plugin].respond_to?("privmsg") &&
+ @bot.auth.allow?(m.plugin, m.source, m.replyto))
+ begin
+ pl[m.plugin].privmsg(m)
+ rescue BDB::Fatal => err
+ error error_report("plugin #{pl[m.plugin].name} privmsg() failed:", err)
+ raise
+ rescue Exception => err
+ #rescue TimeoutError, StandardError, NameError, SyntaxError => err
+ error "plugin #{pl[m.plugin].name} privmsg() failed: #{err.class}: #{err}\n#{error err.backtrace.join("\n")}"
+ end
+ return true
end
- return true
- end
- return false
+ return false
+ }
end
end
+ # Returns the only PluginManagerClass instance
+ def Plugins.pluginmanager
+ return PluginManagerClass.instance
+ end
+
end
end