module Irc
+ BotConfig.register BotConfigArrayValue.new('plugins.blacklist',
+ :default => [], :wizard => false, :requires_restart => true,
+ :desc => "Plugins that should not be loaded")
module Plugins
require 'rbot/messagemapper'
# the following methods, it will be called as appropriate:
#
# 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. examples:
+ # 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.
+ #
+ # Examples:
#
# plugin.map 'karmastats', :action => 'karma_stats'
#
# def karma_stats(m, params)
# m.reply "..."
# end
- #
+ #
# # the default action is the first component
# plugin.map 'karma'
#
# item = params[:key]
# m.reply 'karma for #{item}'
# end
- #
+ #
# # you can setup defaults, to make parameters optional
# plugin.map 'karma :key', :defaults => {:key => 'defaultvalue'}
- #
+ #
# # the default auth check is also against the first component
# # but that can be changed
# plugin.map 'karmastats', :auth => 'karma'
# plugin.map 'karmastats', :public false,
# end
#
- # To activate your maps, you simply register them
- # plugin.register_maps
- # This also sets the privmsg handler to use the map lookups for
- # handling messages. You can still use listen(), kick() etc methods
- #
# listen(UserMessage)::
# Called for all messages of any type. To
# differentiate them, use message.kind_of? It'll be
# either a PrivMessage, NoticeMessage, KickMessage,
# QuitMessage, PartMessage, JoinMessage, NickMessage,
# etc.
- #
+ #
# privmsg(PrivMessage)::
# called for a PRIVMSG if the first word matches one
# the plugin register()d for. Use m.plugin to get
# part(PartMessage)::
# Called when a user (or the bot) parts a channel
#
- # quit(QuitMessage)::
+ # quit(QuitMessage)::
# Called when a user (or the bot) quits IRC
#
# nick(NickMessage)::
#
# connect():: Called when a server is joined successfully, but
# before autojoin channels are joined (no params)
- #
+ #
# save:: Called when you are required to save your plugin's
# state, if you maintain data between sessions
#
@registry = BotRegistryAccessor.new(@bot, self.class.to_s.gsub(/^.*::/, ""))
end
+ def flush_registry
+ # debug "Flushing #{@registry}"
+ @registry.flush
+ end
+
+ def cleanup
+ # debug "Closing #{@registry}"
+ @registry.close
+ end
+
def map(*args)
@handler.map(*args)
# register this map
end
end
+ def map!(*args)
+ @handler.map(*args)
+ # register this map
+ name = @handler.last.items[0]
+ self.register name, {:hidden => true}
+ unless self.respond_to?('privmsg')
+ def self.privmsg(m)
+ @handler.handle(m)
+ end
+ end
+ end
+
# return an identifier for this plugin, defaults to a list of the message
# prefixes handled (used for error messages etc)
def name
@names.join("|")
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
def help(plugin, topic)
"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
- def register(name)
+ 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
+ @names << name unless opts.fetch(:hidden, false)
end
# default usage method provided as a utility for simple plugins. The
@dirs = dirlist
scan
end
-
+
# access to associated bot
def Plugins.bot
@@bot
# load plugins from pre-assigned list of directories
def scan
- processed = Array.new
+ @blacklist = Array.new
+ @@bot.config['plugins.blacklist'].each { |p|
+ @blacklist << p+".rb"
+ }
+ @failed = Array.new
+ processed = @blacklist.dup
dirs = Array.new
dirs << Config::datadir + "/plugins"
dirs += @dirs
# 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)
processed << file
- rescue TimeoutError, StandardError, NameError, LoadError, SyntaxError => err
- puts "warning: plugin #{tmpfilename} load failed: " + err
- puts err.backtrace.join("\n")
+ 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 => tmpfilename, :err => newerr }
+ # debug "Failures: #{@failed.inspect}"
end
}
end
# call the save method for each active plugin
def save
+ delegate 'flush_registry'
delegate 'save'
end
# return list of help topics (plugin names)
def helptopics
if(@@plugins.length > 0)
- # return " [plugins: " + @@plugins.keys.sort.join(", ") + "]"
- return " [#{length} plugins: " + @@plugins.values.uniq.collect{|p| p.name}.sort.join(", ") + "]"
+ list = " [#{length} plugin#{'s' if length > 1}: " + @@plugins.values.uniq.collect{|p| p.name}.sort.join(", ")
else
- return " [no plugins active]"
+ list = " [no plugins active"
end
+ list << "; #{Reverse}#{@failed.length} plugin#{'s' if @failed.length > 1} failed to load#{Reverse}: use #{Bold}help pluginfailures#{Bold} to see why" unless @failed.empty?
+ list << "]"
+ return list
end
def length
# return help for +topic+ (call associated plugin's help method)
def help(topic="")
+ if topic =~ /plugin\s*fail(?:ure)?s?\s*(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} failed with #{p[:err].class}: #{p[:err]}"
+ list << "#{Bold}#{p[:name]}#{Bold} failed at #{p[:err].backtrace.join(', ')}" if $1 and not p[:err].backtrace.empty?
+ list
+ }).join("\n")
+ end
if(topic =~ /^(\S+)\s*(.*)$/)
key = $1
params = $2
if(@@plugins.has_key?(key))
begin
return @@plugins[key].help(key, params)
- rescue TimeoutError, StandardError, NameError, SyntaxError => err
- puts "plugin #{@@plugins[key].name} help() failed: " + err
- puts err.backtrace.join("\n")
+ rescue Exception => err
+ #rescue TimeoutError, StandardError, NameError, SyntaxError => err
+ error "plugin #{@@plugins[key].name} help() failed: #{err.class}: #{err}"
+ error err.backtrace.join("\n")
end
else
return false
end
end
end
-
+
# see if each plugin handles +method+, and if so, call it, passing
# +message+ as a parameter
def delegate(method, *args)
if(p.respond_to? method)
begin
p.send method, *args
- rescue TimeoutError, StandardError, NameError, SyntaxError => err
- puts "plugin #{p.name} #{method}() failed: " + err
- puts err.backtrace.join("\n")
+ rescue Exception => err
+ #rescue TimeoutError, StandardError, NameError, SyntaxError => err
+ error "plugin #{p.name} #{method}() failed: #{err.class}: #{err}"
+ error err.backtrace.join("\n")
end
end
}
@@bot.auth.allow?(m.plugin, m.source, m.replyto))
begin
@@plugins[m.plugin].privmsg(m)
- rescue TimeoutError, StandardError, NameError, SyntaxError => err
- puts "plugin #{@@plugins[m.plugin].name} privmsg() failed: " + err
- puts err.backtrace.join("\n")
+ 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")
end
return true
end