diff options
Diffstat (limited to 'lib/rbot')
-rw-r--r-- | lib/rbot/botuser.rb | 48 | ||||
-rw-r--r-- | lib/rbot/ircbot.rb | 326 | ||||
-rw-r--r-- | lib/rbot/plugins.rb | 264 | ||||
-rw-r--r-- | lib/rbot/rbotconfig.rb | 30 | ||||
-rw-r--r-- | lib/rbot/rfc2812.rb | 3 |
5 files changed, 323 insertions, 348 deletions
diff --git a/lib/rbot/botuser.rb b/lib/rbot/botuser.rb index db33cd27..28a54108 100644 --- a/lib/rbot/botuser.rb +++ b/lib/rbot/botuser.rb @@ -165,6 +165,8 @@ # <tango_> permissions are in the form [ [channel, {command => bool, ...}] ...]
#++
+require 'singleton'
+
module Irc
# This method raises a TypeError if _user_ is not of class User
@@ -264,7 +266,7 @@ module Irc # Tells if command _cmd_ is permitted. We do this by returning
# the value of the deepest Command#path that matches.
#
- def permit?(cmd)
+ def allow?(cmd)
error_if_not_command(cmd)
allow = nil
cmd.path.reverse.each { |k|
@@ -311,7 +313,7 @@ module Irc # Checks if BotUser is allowed to do something on channel _chan_,
# or on all channels if _chan_ is nil
#
- def permit?(cmd, chan=nil)
+ def allow?(cmd, chan=nil)
if chan
k = chan.to_s.to_sym
else
@@ -319,7 +321,7 @@ module Irc end
allow = nil
if @perm.has_key?(k)
- allow = @perm[k].permit?(cmd)
+ allow = @perm[k].allow?(cmd)
end
return allow
end
@@ -435,7 +437,7 @@ module Irc # Returns the only instance of AnonBotUserClass
#
- def anonbotuser
+ def Auth::anonbotuser
return AnonBotUserClass.instance
end
@@ -447,15 +449,15 @@ module Irc super("owner")
end
- def permit?(cmd, chan=nil)
+ def allow?(cmd, chan=nil)
return true
end
end
# Returns the only instance of BotOwnerClass
#
- def botowner
- return BotOwneClass.instance
+ def Auth::botowner
+ return BotOwnerClass.instance
end
@@ -469,8 +471,17 @@ module Irc # <code>Irc::User</code>s onto <code>BotUser</code>s, and the other that maps
# usernames onto <code>BotUser</code>
def initialize
+ bot_associate(nil)
+ end
+
+ def bot_associate(bot)
+ raise "Cannot associate with a new bot! Save first" if defined?(@has_changes) && @has_changes
+
reset_hashes
+ # Associated bot
+ @bot = bot
+
# This variable is set to true when there have been changes
# to the botusers list, so that we know when to save
@has_changes = false
@@ -480,7 +491,7 @@ module Irc def reset_hashes
@botusers = Hash.new
@allbotusers = Hash.new
- [anonbotuser, botowner].each { |x| @allbotusers[x.username.to_sym] = x }
+ [Auth::anonbotuser, Auth::botowner].each { |x| @allbotusers[x.username.to_sym] = x }
end
# load botlist from userfile
@@ -558,28 +569,37 @@ module Irc # * anonbotuser on _chan_
# * anonbotuser on all channels
#
- def permit?(user, cmdtxt, chan=nil)
+ def allow?(user, cmdtxt, chan=nil)
error_if_not_user(user)
cmd = Command.new(cmdtxt)
allow = nil
botuser = @botusers[user]
- allow = botuser.permit?(cmd, chan) if chan
+ case chan
+ when User
+ chan = "?"
+ when Channel
+ chan = chan.name
+ end
+
+ allow = botuser.allow?(cmd, chan) if chan
return allow unless allow.nil?
- allow = botuser.permit?(cmd)
+ allow = botuser.allow?(cmd)
return allow unless allow.nil?
+
unless botuser == anonbotuser
- allow = anonbotuser.permit?(cmd, chan) if chan
+ allow = anonbotuser.allow?(cmd, chan) if chan
return allow unless allow.nil?
- allow = anonbotuser.permit?(cmd)
+ allow = anonbotuser.allow?(cmd)
return allow unless allow.nil?
end
+
raise "Could not check permission for user #{user.inspect} to run #{cmdtxt.inspect} on #{chan.inspect}"
end
end
# Returns the only instance of AuthManagerClass
#
- def authmanager
+ def Auth.authmanager
return AuthManagerClass.instance
end
end
diff --git a/lib/rbot/ircbot.rb b/lib/rbot/ircbot.rb index 65b94172..a42d27ba 100644 --- a/lib/rbot/ircbot.rb +++ b/lib/rbot/ircbot.rb @@ -75,7 +75,8 @@ require 'rbot/irc' require 'rbot/rfc2812' require 'rbot/keywords' require 'rbot/ircsocket' -require 'rbot/auth' +# require 'rbot/auth' +require 'rbot/botuser' require 'rbot/timer' require 'rbot/plugins' # require 'rbot/channel' @@ -110,6 +111,10 @@ class IrcBot # TODO multiserver attr_reader :server + # the client personality of the bot + # TODO multiserver + attr_reader :client + # bot's irc socket # TODO multiserver attr_reader :socket @@ -217,6 +222,11 @@ class IrcBot @argv = params[:argv] + unless FileTest.directory? Config::coredir + error "core directory '#{Config::coredir}' not found, did you setup.rb?" + exit 2 + end + unless FileTest.directory? Config::datadir error "data directory '#{Config::datadir}' not found, did you setup.rb?" exit 2 @@ -324,10 +334,12 @@ class IrcBot @lang = Language::Language.new(@config['core.language']) - @keywords = Keywords.new(self) + # @keywords = Keywords.new(self) begin - @auth = IrcAuth.new(self) + @auth = Auth::authmanager + @auth.bot_associate(self) + # @auth.load("#{botclass}/botusers.yaml") rescue => e fatal e.inspect fatal e.backtrace.join("\n") @@ -336,7 +348,10 @@ class IrcBot end Dir.mkdir("#{botclass}/plugins") unless File.exist?("#{botclass}/plugins") - @plugins = Plugins::Plugins.new(self, ["#{botclass}/plugins"]) + @plugins = Plugins::pluginmanager + @plugins.bot_associate(self) + @plugins.load_core(Config::coredir) + @plugins.load_plugins(["#{botclass}/plugins"]) @socket = IrcSocket.new(@config['server.name'], @config['server.port'], @config['server.bindhost'], @config['server.sendq_delay'], @config['server.sendq_burst']) @client = IrcClient.new @@ -379,8 +394,15 @@ class IrcBot end } @client[:privmsg] = proc { |data| - message = PrivMessage.new(self, @server, data[:source], data[:target], data[:message]) - onprivmsg(message) + m = PrivMessage.new(self, @server, data[:source], data[:target], data[:message]) + + # TODO use the new Netmask class + # @config['irc.ignore_users'].each { |mask| return if Irc.netmaskmatch(mask,m.source) } + + irclogprivmsg(m) + + @plugins.delegate "listen", m + @plugins.privmsg(m) } @client[:notice] = proc { |data| message = NoticeMessage.new(self, @server, data[:source], data[:target], data[:message]) @@ -400,7 +422,7 @@ class IrcBot warning "bad nick (#{data[:nick]})" } @client[:ping] = proc {|data| - @socket.queue "PONG #{data[:pingid]}" + sendq "PONG #{data[:pingid]}" } @client[:pong] = proc {|data| @last_ping = nil @@ -414,7 +436,7 @@ class IrcBot debug "my nick is now #{new}" end data[:is_on].each { |ch| - irclog "@ #{data[:old]} is now known as #{data[:new]}", ch + irclog "@ #{old} is now known as #{new}", ch } @plugins.delegate("listen", m) @plugins.delegate("nick", m) @@ -422,25 +444,34 @@ class IrcBot @client[:quit] = proc {|data| m = QuitMessage.new(self, @server, data[:source], data[:source], data[:message]) data[:was_on].each { |ch| - irclog "@ Quit: #{sourcenick}: #{message}", ch + irclog "@ Quit: #{data[:source].nick}: #{message}", ch } @plugins.delegate("listen", m) @plugins.delegate("quit", m) } @client[:mode] = proc {|data| - irclog "@ Mode #{data[:modestring]} by #{data[:sourcenick]}", data[:channel] + irclog "@ Mode #{data[:modestring]} by #{data[:source]}", data[:channel] } @client[:join] = proc {|data| m = JoinMessage.new(self, @server, data[:source], data[:channel], data[:message]) - onjoin(m) + irclogjoin(m) + + @plugins.delegate("listen", m) + @plugins.delegate("join", m) } @client[:part] = proc {|data| m = PartMessage.new(self, @server, data[:source], data[:channel], data[:message]) - onpart(m) + irclogpart(m) + + @plugins.delegate("listen", m) + @plugins.delegate("part", m) } @client[:kick] = proc {|data| m = KickMessage.new(self, @server, data[:source], data[:target], data[:channel],data[:message]) - onkick(m) + irclogkick(m) + + @plugins.delegate("listen", m) + @plugins.delegate("kick", m) } @client[:invite] = proc {|data| if data[:target] == myself @@ -448,23 +479,23 @@ class IrcBot end } @client[:changetopic] = proc {|data| - channel = data[:channel] - source = data[:source] - topic = data[:topic] - if source == myself - irclog "@ I set topic \"#{topic}\"", channel - else - irclog "@ #{source} set topic \"#{topic}\"", channel - end m = TopicMessage.new(self, @server, data[:source], data[:channel], data[:topic]) + irclogtopic(m) - ontopic(m) @plugins.delegate("listen", m) @plugins.delegate("topic", m) } - @client[:topic] = @client[:topicinfo] = proc { |data| - m = TopicMessage.new(self, @server, data[:source], data[:channel], data[:channel].topic) - ontopic(m) + @client[:topic] = proc { |data| + irclog "@ Topic is \"#{data[:topic]}\"", data[:channel] + } + @client[:topicinfo] = proc { |data| + channel = data[:channel] + topic = channel.topic + irclog "@ Topic set by #{topic.set_by} on #{topic.set_on}", channel + m = TopicMessage.new(self, @server, data[:source], channel, topic) + + @plugins.delegate("listen", m) + @plugins.delegate("topic", m) } @client[:names] = proc { |data| @plugins.delegate "names", data[:channel], data[:users] @@ -793,7 +824,7 @@ class IrcBot # call the save method for bot's config, keywords, auth and all plugins def save @config.save - @keywords.save + # @keywords.save @auth.save @plugins.save DBTree.cleanup_logs @@ -803,7 +834,7 @@ class IrcBot def rescan @lang.rescan @plugins.rescan - @keywords.rescan + # @keywords.rescan end # channel:: channel to join @@ -832,35 +863,35 @@ class IrcBot sendq "MODE #{channel} #{mode} #{target}", channel, 2 end - # m:: message asking for help - # topic:: optional topic help is requested for - # respond to online help requests - def help(topic=nil) - topic = nil if topic == "" - case topic - when nil - helpstr = "help topics: core, auth, keywords" - helpstr += @plugins.helptopics - helpstr += " (help <topic> for more info)" - when /^core$/i - helpstr = corehelp - when /^core\s+(.+)$/i - helpstr = corehelp $1 - when /^auth$/i - helpstr = @auth.help - when /^auth\s+(.+)$/i - helpstr = @auth.help $1 - when /^keywords$/i - helpstr = @keywords.help - when /^keywords\s+(.+)$/i - helpstr = @keywords.help $1 - else - unless(helpstr = @plugins.help(topic)) - helpstr = "no help for topic #{topic}" - end - end - return helpstr - end + # # m:: message asking for help + # # topic:: optional topic help is requested for + # # respond to online help requests + # def help(topic=nil) + # topic = nil if topic == "" + # case topic + # when nil + # helpstr = "help topics: core, auth, keywords" + # helpstr += @plugins.helptopics + # helpstr += " (help <topic> for more info)" + # when /^core$/i + # helpstr = corehelp + # when /^core\s+(.+)$/i + # helpstr = corehelp $1 + # when /^auth$/i + # helpstr = @auth.help + # when /^auth\s+(.+)$/i + # helpstr = @auth.help $1 + # when /^keywords$/i + # helpstr = @keywords.help + # when /^keywords\s+(.+)$/i + # helpstr = @keywords.help $1 + # else + # unless(helpstr = @plugins.help(topic)) + # helpstr = "no help for topic #{topic}" + # end + # end + # return helpstr + # end # returns a string describing the current status of the bot (uptime etc) def status @@ -912,49 +943,7 @@ class IrcBot private - # handle help requests for "core" topics - def corehelp(topic="") - case topic - when "quit" - return "quit [<message>] => quit IRC with message <message>" - when "restart" - return "restart => completely stop and restart the bot (including reconnect)" - when "join" - return "join <channel> [<key>] => join channel <channel> with secret key <key> if specified. #{myself} also responds to invites if you have the required access level" - when "part" - return "part <channel> => part channel <channel>" - when "hide" - return "hide => part all channels" - when "save" - return "save => save current dynamic data and configuration" - when "rescan" - return "rescan => reload modules and static facts" - when "nick" - return "nick <nick> => attempt to change nick to <nick>" - when "say" - return "say <channel>|<nick> <message> => say <message> to <channel> or in private message to <nick>" - when "action" - return "action <channel>|<nick> <message> => does a /me <message> to <channel> or in private message to <nick>" - # when "topic" - # return "topic <channel> <message> => set topic of <channel> to <message>" - when "quiet" - return "quiet [in here|<channel>] => with no arguments, stop speaking in all channels, if \"in here\", stop speaking in this channel, or stop speaking in <channel>" - when "talk" - return "talk [in here|<channel>] => with no arguments, resume speaking in all channels, if \"in here\", resume speaking in this channel, or resume speaking in <channel>" - when "version" - return "version => describes software version" - when "botsnack" - return "botsnack => reward #{myself} for being good" - when "hello" - return "hello|hi|hey|yo [#{myself}] => greet the bot" - else - return "Core help topics: quit, restart, config, join, part, hide, save, rescan, nick, say, action, topic, quiet, talk, version, botsnack, hello" - end - end - - # handle incoming IRC PRIVMSG +m+ - def onprivmsg(m) - # log it first + def irclogprivmsg(m) if(m.action?) if(m.private?) irclog "* [#{m.sourcenick}(#{m.sourceaddress})] #{m.message}", m.sourcenick @@ -968,115 +957,6 @@ class IrcBot irclog "[#{m.sourcenick}(#{m.sourceaddress})] #{m.message}", m.sourcenick end end - - @config['irc.ignore_users'].each { |mask| return if Irc.netmaskmatch(mask,m.source) } - - # pass it off to plugins that want to hear everything - @plugins.delegate "listen", m - - if(m.private? && m.message =~ /^\001PING\s+(.+)\001/) - notice m.sourcenick, "\001PING #$1\001" - irclog "@ #{m.sourcenick} pinged me" - return - end - - if(m.address?) - delegate_privmsg(m) - case m.message - when (/^join\s+(\S+)\s+(\S+)$/i) - join $1, $2 if(@auth.allow?("join", m.source, m.replyto)) - when (/^join\s+(\S+)$/i) - join $1 if(@auth.allow?("join", m.source, m.replyto)) - when (/^part$/i) - part m.target if(m.public? && @auth.allow?("join", m.source, m.replyto)) - when (/^part\s+(\S+)$/i) - part $1 if(@auth.allow?("join", m.source, m.replyto)) - when (/^quit(?:\s+(.*))?$/i) - quit $1 if(@auth.allow?("quit", m.source, m.replyto)) - when (/^restart(?:\s+(.*))?$/i) - restart $1 if(@auth.allow?("quit", m.source, m.replyto)) - when (/^hide$/i) - join 0 if(@auth.allow?("join", m.source, m.replyto)) - when (/^save$/i) - if(@auth.allow?("config", m.source, m.replyto)) - save - m.okay - end - when (/^nick\s+(\S+)$/i) - nickchg($1) if(@auth.allow?("nick", m.source, m.replyto)) - when (/^say\s+(\S+)\s+(.*)$/i) - say $1, $2 if(@auth.allow?("say", m.source, m.replyto)) - when (/^action\s+(\S+)\s+(.*)$/i) - action $1, $2 if(@auth.allow?("say", m.source, m.replyto)) - # when (/^topic\s+(\S+)\s+(.*)$/i) - # topic $1, $2 if(@auth.allow?("topic", m.source, m.replyto)) - when (/^mode\s+(\S+)\s+(\S+)\s+(.*)$/i) - mode $1, $2, $3 if(@auth.allow?("mode", m.source, m.replyto)) - when (/^ping$/i) - say m.replyto, "pong" - when (/^rescan$/i) - if(@auth.allow?("config", m.source, m.replyto)) - m.reply "saving ..." - save - m.reply "rescanning ..." - rescan - m.reply "done. #{@plugins.status(true)}" - end - when (/^quiet$/i) - if(auth.allow?("talk", m.source, m.replyto)) - m.okay - set_quiet - end - when (/^quiet in (\S+)$/i) - where = $1 - if(auth.allow?("talk", m.source, m.replyto)) - m.okay - where.gsub!(/^here$/, m.target) if m.public? - set_quiet(where) - end - when (/^talk$/i) - if(auth.allow?("talk", m.source, m.replyto)) - reset_quiet - m.okay - end - when (/^talk in (\S+)$/i) - where = $1 - if(auth.allow?("talk", m.source, m.replyto)) - where.gsub!(/^here$/, m.target) if m.public? - reset_quiet(where) - m.okay - end - when (/^status\??$/i) - m.reply status if auth.allow?("status", m.source, m.replyto) - when (/^registry stats$/i) - if auth.allow?("config", m.source, m.replyto) - m.reply @registry.stat.inspect - end - when (/^(help\s+)?config(\s+|$)/) - @config.privmsg(m) - when (/^(version)|(introduce yourself)$/i) - say m.replyto, "I'm a v. #{$version} rubybot, (c) Tom Gilbert - http://linuxbrit.co.uk/rbot/" - when (/^help(?:\s+(.*))?$/i) - say m.replyto, help($1) - #TODO move these to a "chatback" plugin - when (/^(botsnack|ciggie)$/i) - say m.replyto, @lang.get("thanks_X") % m.sourcenick if(m.public?) - say m.replyto, @lang.get("thanks") if(m.private?) - when (/^(hello|howdy|hola|salut|bonjour|sup|niihau|hey|hi(\W|$)|yo(\W|$)).*/i) - say m.replyto, @lang.get("hello_X") % m.sourcenick if(m.public?) - say m.replyto, @lang.get("hello") if(m.private?) - end - else - # stuff to handle when not addressed - case m.message - when (/^\s*(hello|howdy|hola|salut|bonjour|sup|niihau|hey|hi|yo(\W|$))[\s,-.]+#{Regexp.escape(self.nick)}$/i) - say m.replyto, @lang.get("hello_X") % m.sourcenick - when (/^#{Regexp.escape(self.nick)}!*$/) - say m.replyto, @lang.get("hello_X") % m.sourcenick - else - @keywords.privmsg(m) - end - end end # log a message. Internal use only. @@ -1103,54 +983,42 @@ class IrcBot end end - def onjoin(m) + def irclogjoin(m) if m.address? debug "joined channel #{m.channel}" irclog "@ Joined channel #{m.channel}", m.channel else irclog "@ #{m.sourcenick} joined channel #{m.channel}", m.channel end - - @plugins.delegate("listen", m) - @plugins.delegate("join", m) end - def onpart(m) + def irclogpart(m) if(m.address?) debug "left channel #{m.channel}" irclog "@ Left channel #{m.channel} (#{m.message})", m.channel else irclog "@ #{m.sourcenick} left channel #{m.channel} (#{m.message})", m.channel end - - # delegate to plugins - @plugins.delegate("listen", m) - @plugins.delegate("part", m) end # respond to being kicked from a channel - def onkick(m) + def irclogkick(m) if(m.address?) debug "kicked from channel #{m.channel}" irclog "@ You have been kicked from #{m.channel} by #{m.sourcenick} (#{m.message})", m.channel else irclog "@ #{m.target} has been kicked from #{m.channel} by #{m.sourcenick} (#{m.message})", m.channel end - - @plugins.delegate("listen", m) - @plugins.delegate("kick", m) end - def ontopic(m) - debug "topic of channel #{m.channel} is now #{m.topic}" + def irclogtopic(m) + if source == myself + irclog "@ I set topic \"#{topic}\"", channel + else + irclog "@ #{source} set topic \"#{topic}\"", channel + end end - # delegate a privmsg to auth, keyword or plugin handlers - def delegate_privmsg(message) - [@auth, @plugins, @keywords].each {|m| - break if m.privmsg(message) - } - end end end 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 diff --git a/lib/rbot/rbotconfig.rb b/lib/rbot/rbotconfig.rb index 212df990..32bd66d4 100644 --- a/lib/rbot/rbotconfig.rb +++ b/lib/rbot/rbotconfig.rb @@ -1,17 +1,23 @@ module Irc module Config @@datadir = nil + @@coredir = nil - # first try for the default path to the data dir - defaultdir = File.expand_path(File.dirname($0) + '/../data') + # first try for the default path to the data dir + defaultdatadir = File.expand_path(File.dirname($0) + '/../data/rbot') + defaultcoredir = File.expand_path(File.dirname($0) + '/../lib/rbot/core') - if File.directory? "#{defaultdir}/rbot" - @@datadir = "#{defaultdir}/rbot" + if File.directory? defaultdatadir + @@datadir = defaultdatadir end - + + if File.directory? defaultcoredir + @@coredir = defaultcoredir + end + # setup pkg-based configuration - i.e. where were we installed to, where # are our data files, etc. - if @@datadir.nil? + if @@datadir.nil? or @@coredir.nil? begin debug "trying to load rubygems" require 'rubygems' @@ -26,6 +32,7 @@ module Irc if gem && path = gem.full_gem_path debug "installed via rubygems to #{path}" @@datadir = "#{path}/data/rbot" + @@datadir = "#{path}/lib/rbot/core" else debug "not installed via rubygems" end @@ -34,18 +41,23 @@ module Irc end end - if @@datadir.nil? + if @@datadir.nil? or @@coredir.nil? begin require 'rbot/pkgconfig' @@datadir = PKGConfig::DATADIR + @@coredir = PKGConfig::COREDIR rescue LoadError - error "fatal - no way to determine data dir" + error "fatal - no way to determine data or core dir" exit 2 end end - + def Config.datadir @@datadir end + + def Config.coredir + @@coredir + end end end diff --git a/lib/rbot/rfc2812.rb b/lib/rbot/rfc2812.rb index dee2920f..c8e2e410 100644 --- a/lib/rbot/rfc2812.rb +++ b/lib/rbot/rfc2812.rb @@ -1167,6 +1167,7 @@ module Irc data[:newnick] = argv[0] data[:oldnick] = data[:source].nick.dup data[:source].nick = data[:nick] + debug "#{data[:oldnick]} (now #{data[:newnick]}) was on #{data[:is_on].join(', ')}" handle(:nick, data) when 'MODE' @@ -1189,7 +1190,7 @@ module Irc data[:modes] = [] # array of indices in data[:modes] where parameters # are needed - who_want_params = [] + who_wants_params = [] argv[1..-1].each { |arg| setting = arg[0].chr |