You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by jv...@apache.org on 2005/12/16 23:30:52 UTC
svn commit: r357245 [2/3] - in /maven/sandbox/issue/rbot/dist: ./ bin/ lib/
lib/site_ruby/ lib/site_ruby/1.8/ lib/site_ruby/1.8/rbot/ plugins/ share/
share/rbot/ share/rbot/languages/ share/rbot/templates/
share/rbot/templates/lart/
Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/keywords.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/keywords.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/keywords.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/keywords.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,437 @@
+require 'pp'
+
+module Irc
+
+ # Keyword class
+ #
+ # Encapsulates a keyword ("foo is bar" is a keyword called foo, with type
+ # is, and has a single value of bar).
+ # Keywords can have multiple values, to_s() will choose one at random
+ class Keyword
+
+ # type of keyword (e.g. "is" or "are")
+ attr_reader :type
+
+ # type:: type of keyword (e.g "is" or "are")
+ # values:: array of values
+ #
+ # create a keyword of type +type+ with values +values+
+ def initialize(type, values)
+ @type = type.downcase
+ @values = values
+ end
+
+ # pick a random value for this keyword and return it
+ def to_s
+ if(@values.length > 1)
+ Keyword.unescape(@values[rand(@values.length)])
+ else
+ Keyword.unescape(@values[0])
+ end
+ end
+
+ # describe the keyword (show all values without interpolation)
+ def desc
+ @values.join(" | ")
+ end
+
+ # return the keyword in a stringified form ready for storage
+ def dump
+ @type + "/" + Keyword.unescape(@values.join("<=or=>"))
+ end
+
+ # deserialize the stringified form to an object
+ def Keyword.restore(str)
+ if str =~ /^(\S+?)\/(.*)$/
+ type = $1
+ vals = $2.split("<=or=>")
+ return Keyword.new(type, vals)
+ end
+ return nil
+ end
+
+ # values:: array of values to add
+ # add values to a keyword
+ def <<(values)
+ if(@values.length > 1 || values.length > 1)
+ values.each {|v|
+ @values << v
+ }
+ else
+ @values[0] += " or " + values[0]
+ end
+ end
+
+ # unescape special words/characters in a keyword
+ def Keyword.unescape(str)
+ str.gsub(/\\\|/, "|").gsub(/ \\is /, " is ").gsub(/ \\are /, " are ").gsub(/\\\?(\s*)$/, "?\1")
+ end
+
+ # escape special words/characters in a keyword
+ def Keyword.escape(str)
+ str.gsub(/\|/, "\\|").gsub(/ is /, " \\is ").gsub(/ are /, " \\are ").gsub(/\?(\s*)$/, "\\?\1")
+ end
+ end
+
+ # keywords class.
+ #
+ # Handles all that stuff like "bot: foo is bar", "bot: foo?"
+ #
+ # Fallback after core and auth have had a look at a message and refused to
+ # handle it, checks for a keyword command or lookup, otherwise the message
+ # is delegated to plugins
+ class Keywords
+ BotConfig.register BotConfigBooleanValue.new('keyword.listen',
+ :default => false,
+ :desc => "Should the bot listen to all chat and attempt to automatically detect keywords? (e.g. by spotting someone say 'foo is bar')")
+ BotConfig.register BotConfigBooleanValue.new('keyword.address',
+ :default => true,
+ :desc => "Should the bot require that keyword lookups are addressed to it? If not, the bot will attempt to lookup foo if someone says 'foo?' in channel")
+
+ # create a new Keywords instance, associated to bot +bot+
+ def initialize(bot)
+ @bot = bot
+ @statickeywords = Hash.new
+ upgrade_data
+ @keywords = DBTree.new bot, "keyword"
+
+ scan
+
+ # import old format keywords into DBHash
+ if(File.exist?("#{@bot.botclass}/keywords.rbot"))
+ puts "auto importing old keywords.rbot"
+ IO.foreach("#{@bot.botclass}/keywords.rbot") do |line|
+ if(line =~ /^(.*?)\s*<=(is|are)?=?>\s*(.*)$/)
+ lhs = $1
+ mhs = $2
+ rhs = $3
+ mhs = "is" unless mhs
+ rhs = Keyword.escape rhs
+ values = rhs.split("<=or=>")
+ @keywords[lhs] = Keyword.new(mhs, values).dump
+ end
+ end
+ File.delete("#{@bot.botclass}/keywords.rbot")
+ end
+ end
+
+ # drop static keywords and reload them from files, picking up any new
+ # keyword files that have been added
+ def rescan
+ @statickeywords = Hash.new
+ scan
+ end
+
+ # load static keywords from files, picking up any new keyword files that
+ # have been added
+ def scan
+ # first scan for old DBHash files, and convert them
+ Dir["#{@bot.botclass}/keywords/*"].each {|f|
+ next unless f =~ /\.db$/
+ puts "upgrading keyword db #{f} (rbot 0.9.5 or prior) database format"
+ newname = f.gsub(/\.db$/, ".kdb")
+ old = BDB::Hash.open f, nil,
+ "r+", 0600, "set_pagesize" => 1024,
+ "set_cachesize" => [0, 32 * 1024, 0]
+ new = BDB::CIBtree.open newname, nil,
+ BDB::CREATE | BDB::EXCL | BDB::TRUNCATE,
+ 0600, "set_pagesize" => 1024,
+ "set_cachesize" => [0, 32 * 1024, 0]
+ old.each {|k,v|
+ new[k] = v
+ }
+ old.close
+ new.close
+ File.delete(f)
+ }
+
+ # then scan for current DBTree files, and load them
+ Dir["#{@bot.botclass}/keywords/*"].each {|f|
+ next unless f =~ /\.kdb$/
+ hsh = DBTree.new @bot, f, true
+ key = File.basename(f).gsub(/\.kdb$/, "")
+ debug "keywords module: loading DBTree file #{f}, key #{key}"
+ @statickeywords[key] = hsh
+ }
+
+ # then scan for non DB files, and convert/import them and delete
+ Dir["#{@bot.botclass}/keywords/*"].each {|f|
+ next if f =~ /\.kdb$/
+ next if f =~ /CVS$/
+ puts "auto converting keywords from #{f}"
+ key = File.basename(f)
+ unless @statickeywords.has_key?(key)
+ @statickeywords[key] = DBHash.new @bot, "#{f}.db", true
+ end
+ IO.foreach(f) {|line|
+ if(line =~ /^(.*?)\s*<?=(is|are)?=?>\s*(.*)$/)
+ lhs = $1
+ mhs = $2
+ rhs = $3
+ # support infobot style factfiles, by fixing them up here
+ rhs.gsub!(/\$who/, "<who>")
+ mhs = "is" unless mhs
+ rhs = Keyword.escape rhs
+ values = rhs.split("<=or=>")
+ @statickeywords[key][lhs] = Keyword.new(mhs, values).dump
+ end
+ }
+ File.delete(f)
+ @statickeywords[key].flush
+ }
+ end
+
+ # upgrade data files found in old rbot formats to current
+ def upgrade_data
+ if File.exist?("#{@bot.botclass}/keywords.db")
+ puts "upgrading old keywords (rbot 0.9.5 or prior) database format"
+ old = BDB::Hash.open "#{@bot.botclass}/keywords.db", nil,
+ "r+", 0600, "set_pagesize" => 1024,
+ "set_cachesize" => [0, 32 * 1024, 0]
+ new = BDB::CIBtree.open "#{@bot.botclass}/keyword.db", nil,
+ BDB::CREATE | BDB::EXCL | BDB::TRUNCATE,
+ 0600, "set_pagesize" => 1024,
+ "set_cachesize" => [0, 32 * 1024, 0]
+ old.each {|k,v|
+ new[k] = v
+ }
+ old.close
+ new.close
+ File.delete("#{@bot.botclass}/keywords.db")
+ end
+ end
+
+ # save dynamic keywords to file
+ def save
+ @keywords.flush
+ end
+ def oldsave
+ File.open("#{@bot.botclass}/keywords.rbot", "w") do |file|
+ @keywords.each do |key, value|
+ file.puts "#{key}<=#{value.type}=>#{value.dump}"
+ end
+ end
+ end
+
+ # lookup keyword +key+, return it or nil
+ def [](key)
+ return nil if key.nil?
+ debug "keywords module: looking up key #{key}"
+ if(@keywords.has_key?(key))
+ return Keyword.restore(@keywords[key])
+ else
+ # key name order for the lookup through these
+ @statickeywords.keys.sort.each {|k|
+ v = @statickeywords[k]
+ if v.has_key?(key)
+ return Keyword.restore(v[key])
+ end
+ }
+ end
+ return nil
+ end
+
+ # does +key+ exist as a keyword?
+ def has_key?(key)
+ if @keywords.has_key?(key) && Keyword.restore(@keywords[key]) != nil
+ return true
+ end
+ @statickeywords.each {|k,v|
+ if v.has_key?(key) && Keyword.restore(v[key]) != nil
+ return true
+ end
+ }
+ return false
+ end
+
+ # m:: PrivMessage containing message info
+ # key:: key being queried
+ # dunno:: optional, if true, reply "dunno" if +key+ not found
+ #
+ # handle a message asking about a keyword
+ def keyword(m, key, dunno=true)
+ return if key.nil?
+ unless(kw = self[key])
+ m.reply @bot.lang.get("dunno") if (dunno)
+ return
+ end
+ response = kw.to_s
+ response.gsub!(/<who>/, m.sourcenick)
+ if(response =~ /^<reply>\s*(.*)/)
+ m.reply "#$1"
+ elsif(response =~ /^<action>\s*(.*)/)
+ @bot.action m.replyto, "#$1"
+ elsif(m.public? && response =~ /^<topic>\s*(.*)/)
+ topic = $1
+ @bot.topic m.target, topic
+ else
+ m.reply "#{key} #{kw.type} #{response}"
+ end
+ end
+
+
+ # m:: PrivMessage containing message info
+ # target:: channel/nick to tell about the keyword
+ # key:: key being queried
+ #
+ # handle a message asking the bot to tell someone about a keyword
+ def keyword_tell(m, target, key)
+ unless(kw = self[key])
+ @bot.say m.sourcenick, @bot.lang.get("dunno_about_X") % key
+ return
+ end
+ response = kw.to_s
+ response.gsub!(/<who>/, m.sourcenick)
+ if(response =~ /^<reply>\s*(.*)/)
+ @bot.say target, "#{m.sourcenick} wanted me to tell you: (#{key}) #$1"
+ m.reply "okay, I told #{target}: (#{key}) #$1"
+ elsif(response =~ /^<action>\s*(.*)/)
+ @bot.action target, "#$1 (#{m.sourcenick} wanted me to tell you)"
+ m.reply "okay, I told #{target}: * #$1"
+ else
+ @bot.say target, "#{m.sourcenick} wanted me to tell you that #{key} #{kw.type} #{response}"
+ m.reply "okay, I told #{target} that #{key} #{kw.type} #{response}"
+ end
+ end
+
+ # handle a message which alters a keyword
+ # like "foo is bar", or "no, foo is baz", or "foo is also qux"
+ def keyword_command(sourcenick, target, lhs, mhs, rhs, quiet=false)
+ debug "got keyword command #{lhs}, #{mhs}, #{rhs}"
+ overwrite = false
+ overwrite = true if(lhs.gsub!(/^no,\s*/, ""))
+ also = true if(rhs.gsub!(/^also\s+/, ""))
+ values = rhs.split(/\s+\|\s+/)
+ lhs = Keyword.unescape lhs
+ if(overwrite || also || !has_key?(lhs))
+ if(also && has_key?(lhs))
+ kw = self[lhs]
+ kw << values
+ @keywords[lhs] = kw.dump
+ else
+ @keywords[lhs] = Keyword.new(mhs, values).dump
+ end
+ @bot.okay target if !quiet
+ elsif(has_key?(lhs))
+ kw = self[lhs]
+ @bot.say target, "but #{lhs} #{kw.type} #{kw.desc}" if kw && !quiet
+ end
+ end
+
+ # return help string for Keywords with option topic +topic+
+ def help(topic="")
+ case topic
+ when "overview"
+ return "set: <keyword> is <definition>, overide: no, <keyword> is <definition>, add to definition: <keyword> is also <definition>, random responses: <keyword> is <definition> | <definition> [| ...], plurals: <keyword> are <definition>, escaping: \\is, \\are, \\|, specials: <reply>, <action>, <who>"
+ when "set"
+ return "set => <keyword> is <definition>"
+ when "plurals"
+ return "plurals => <keywords> are <definition>"
+ when "override"
+ return "overide => no, <keyword> is <definition>"
+ when "also"
+ return "also => <keyword> is also <definition>"
+ when "random"
+ return "random responses => <keyword> is <definition> | <definition> [| ...]"
+ when "get"
+ return "asking for keywords => (with addressing) \"<keyword>?\", (without addressing) \"'<keyword>\""
+ when "tell"
+ return "tell <nick> about <keyword> => if <keyword> is known, tell <nick>, via /msg, its definition"
+ when "forget"
+ return "forget <keyword> => forget fact <keyword>"
+ when "keywords"
+ return "keywords => show current keyword counts"
+ when "<reply>"
+ return "<reply> => normal response is \"<keyword> is <definition>\", but if <definition> begins with <reply>, the response will be \"<definition>\""
+ when "<action>"
+ return "<action> => makes keyword respnse \"/me <definition>\""
+ when "<who>"
+ return "<who> => replaced with questioner in reply"
+ when "<topic>"
+ return "<topic> => respond by setting the topic to the rest of the definition"
+ when "search"
+ return "keywords search [--all] [--full] <regexp> => search keywords for <regexp>. If --all is set, search static keywords too, if --full is set, search definitions too."
+ else
+ return "Keyword module (Fact learning and regurgitation) topics: overview, set, plurals, override, also, random, get, tell, forget, keywords, keywords search, <reply>, <action>, <who>, <topic>"
+ end
+ end
+
+ # privmsg handler
+ def privmsg(m)
+ return if m.replied?
+ if(m.address?)
+ if(!(m.message =~ /\\\?\s*$/) && m.message =~ /^(.*\S)\s*\?\s*$/)
+ keyword m, $1 if(@bot.auth.allow?("keyword", m.source, m.replyto))
+ elsif(m.message =~ /^(.*?)\s+(is|are)\s+(.*)$/)
+ keyword_command(m.sourcenick, m.replyto, $1, $2, $3) if(@bot.auth.allow?("keycmd", m.source, m.replyto))
+ elsif (m.message =~ /^tell\s+(\S+)\s+about\s+(.+)$/)
+ keyword_tell(m, $1, $2) if(@bot.auth.allow?("keyword", m.source, m.replyto))
+ elsif (m.message =~ /^forget\s+(.*)$/)
+ key = $1
+ if((@bot.auth.allow?("keycmd", m.source, m.replyto)) && @keywords.has_key?(key))
+ @keywords.delete(key)
+ @bot.okay m.replyto
+ end
+ elsif (m.message =~ /^keywords$/)
+ if(@bot.auth.allow?("keyword", m.source, m.replyto))
+ length = 0
+ @statickeywords.each {|k,v|
+ length += v.length
+ }
+ m.reply "There are currently #{@keywords.length} keywords, #{length} static facts defined."
+ end
+ elsif (m.message =~ /^keywords search\s+(.*)$/)
+ str = $1
+ all = false
+ all = true if str.gsub!(/--all\s+/, "")
+ full = false
+ full = true if str.gsub!(/--full\s+/, "")
+
+ re = Regexp.new(str, Regexp::IGNORECASE)
+ if(@bot.auth.allow?("keyword", m.source, m.replyto))
+ matches = Array.new
+ @keywords.each {|k,v|
+ kw = Keyword.restore(v)
+ if re.match(k) || (full && re.match(kw.desc))
+ matches << [k,kw]
+ end
+ }
+ if all
+ @statickeywords.each {|k,v|
+ v.each {|kk,vv|
+ kw = Keyword.restore(vv)
+ if re.match(kk) || (full && re.match(kw.desc))
+ matches << [kk,kw]
+ end
+ }
+ }
+ end
+ if matches.length == 1
+ rkw = matches[0]
+ m.reply "#{rkw[0]} #{rkw[1].type} #{rkw[1].desc}"
+ elsif matches.length > 0
+ i = 0
+ matches.each {|rkw|
+ m.reply "[#{i+1}/#{matches.length}] #{rkw[0]} #{rkw[1].type} #{rkw[1].desc}"
+ i += 1
+ break if i == 3
+ }
+ else
+ m.reply "no keywords match #{str}"
+ end
+ end
+ end
+ else
+ # in channel message, not to me
+ # TODO option to do if(m.message =~ /^(.*)$/, ie try any line as a
+ # keyword lookup.
+ if(m.message =~ /^'(.*)$/ || (!@bot.config["keyword.address"] && m.message =~ /^(.*\S)\s*\?\s*$/))
+ keyword m, $1, false if(@bot.auth.allow?("keyword", m.source))
+ elsif(@bot.config["keyword.listen"] == true && (m.message =~ /^(.*?)\s+(is|are)\s+(.*)$/))
+ # TODO MUCH more selective on what's allowed here
+ keyword_command(m.sourcenick, m.replyto, $1, $2, $3, true) if(@bot.auth.allow?("keycmd", m.source))
+ end
+ end
+ end
+ end
+end
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/keywords.rb
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/keywords.rb
------------------------------------------------------------------------------
svn:keywords = "Author Date Id Revision"
Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/language.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/language.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/language.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/language.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,72 @@
+module Irc
+module Language
+
+ class Language
+ @@datadir = ""
+
+ BotConfig.register BotConfigEnumValue.new('core.language',
+ :default => "english", :wizard => true,
+ :values => Proc.new{|bot|
+ Dir.new(Config::datadir + "/languages").collect {|f|
+ f =~ /\.lang$/ ? f.gsub(/\.lang$/, "") : nil
+ }.compact
+ },
+ :on_change => Proc.new {|bot, v| bot.lang.set_language v},
+ :desc => "Which language file the bot should use")
+
+ def initialize(language,datadir)
+ @@datadir = datadir
+ set_language language
+ end
+
+ def set_language(language)
+ file = @@datadir + "/languages/#{language}.lang"
+ unless(FileTest.exist?(file))
+ raise "no such language: #{language} (no such file #{file})"
+ end
+ @language = language
+ @file = file
+ scan
+ end
+
+ def scan
+ @strings = Hash.new
+ current_key = nil
+ IO.foreach(@file) {|l|
+ next if l =~ /^$/
+ next if l =~ /^\s*#/
+ if(l =~ /^(\S+):$/)
+ @strings[$1] = Array.new
+ current_key = $1
+ elsif(l =~ /^\s*(.*)$/)
+ @strings[current_key] << $1
+ end
+ }
+ end
+
+ def rescan
+ scan
+ end
+
+ def get(key)
+ if(@strings.has_key?(key))
+ return @strings[key][rand(@strings[key].length)]
+ else
+ raise "undefined language key"
+ end
+ end
+
+ def save
+ File.open(@file, "w") {|file|
+ @strings.each {|key,val|
+ file.puts "#{key}:"
+ val.each_value {|v|
+ file.puts " #{v}"
+ }
+ }
+ }
+ end
+ end
+
+end
+end
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/language.rb
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/language.rb
------------------------------------------------------------------------------
svn:keywords = "Author Date Id Revision"
Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/message.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/message.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/message.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/message.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,260 @@
+module Irc
+ BotConfig.register BotConfigArrayValue.new('core.address_prefix',
+ :default => [], :wizard => true,
+ :desc => "what non nick-matching prefixes should the bot respond to as if addressed (e.g !, so that '!foo' is treated like 'rbot: foo')"
+ )
+
+ # base user message class, all user messages derive from this
+ # (a user message is defined as having a source hostmask, a target
+ # nick/channel and a message part)
+ class BasicUserMessage
+
+ # associated bot
+ attr_reader :bot
+
+ # when the message was received
+ attr_reader :time
+
+ # hostmask of message source
+ attr_reader :source
+
+ # nick of message source
+ attr_reader :sourcenick
+
+ # url part of message source
+ attr_reader :sourceaddress
+
+ # nick/channel message was sent to
+ attr_reader :target
+
+ # contents of the message
+ attr_accessor :message
+
+ # has the message been replied to/handled by a plugin?
+ attr_accessor :replied
+
+ # instantiate a new Message
+ # bot:: associated bot class
+ # source:: hostmask of the message source
+ # target:: nick/channel message is destined for
+ # message:: message part
+ def initialize(bot, source, target, message)
+ @time = Time.now
+ @bot = bot
+ @source = source
+ @address = false
+ @target = target
+ @message = BasicUserMessage.stripcolour message
+ @replied = false
+
+ # split source into consituent parts
+ if source =~ /^((\S+)!(\S+))$/
+ @sourcenick = $2
+ @sourceaddress = $3
+ end
+
+ if target && target.downcase == @bot.nick.downcase
+ @address = true
+ end
+
+ end
+
+ # returns true if the message was addressed to the bot.
+ # This includes any private message to the bot, or any public message
+ # which looks like it's addressed to the bot, e.g. "bot: foo", "bot, foo",
+ # a kick message when bot was kicked etc.
+ def address?
+ return @address
+ end
+
+ # has this message been replied to by a plugin?
+ def replied?
+ return @replied
+ end
+
+ # strip mIRC colour escapes from a string
+ def BasicUserMessage.stripcolour(string)
+ return "" unless string
+ ret = string.gsub(/\cC\d\d?(?:,\d\d?)?/, "")
+ #ret.tr!("\x00-\x1f", "")
+ ret
+ end
+
+ end
+
+ # class for handling IRC user messages. Includes some utilities for handling
+ # the message, for example in plugins.
+ # The +message+ member will have any bot addressing "^bot: " removed
+ # (address? will return true in this case)
+ class UserMessage < BasicUserMessage
+
+ # for plugin messages, the name of the plugin invoked by the message
+ attr_reader :plugin
+
+ # for plugin messages, the rest of the message, with the plugin name
+ # removed
+ attr_reader :params
+
+ # convenience member. Who to reply to (i.e. would be sourcenick for a
+ # privately addressed message, or target (the channel) for a publicly
+ # addressed message
+ attr_reader :replyto
+
+ # channel the message was in, nil for privately addressed messages
+ attr_reader :channel
+
+ # for PRIVMSGs, true if the message was a CTCP ACTION (CTCP stuff
+ # will be stripped from the message)
+ attr_reader :action
+
+ # instantiate a new UserMessage
+ # bot:: associated bot class
+ # source:: hostmask of the message source
+ # target:: nick/channel message is destined for
+ # message:: message part
+ def initialize(bot, source, target, message)
+ super(bot, source, target, message)
+ @target = target
+ @private = false
+ @plugin = nil
+ @action = false
+
+ if target.downcase == @bot.nick.downcase
+ @private = true
+ @address = true
+ @channel = nil
+ @replyto = @sourcenick
+ else
+ @replyto = @target
+ @channel = @target
+ end
+
+ # check for option extra addressing prefixes, e.g "|search foo", or
+ # "!version" - first match wins
+ bot.config['core.address_prefix'].each {|mprefix|
+ if @message.gsub!(/^#{Regexp.escape(mprefix)}\s*/, "")
+ @address = true
+ break
+ end
+ }
+
+ # even if they used above prefixes, we allow for silly people who
+ # combine all possible types, e.g. "|rbot: hello", or
+ # "/msg rbot rbot: hello", etc
+ if @message.gsub!(/^\s*#{Regexp.escape(bot.nick)}\s*([:;,>]|\s)\s*/i, "")
+ @address = true
+ end
+
+ if(@message =~ /^\001ACTION\s(.+)\001/)
+ @message = $1
+ @action = true
+ end
+
+ # free splitting for plugins
+ @params = @message.dup
+ if @params.gsub!(/^\s*(\S+)[\s$]*/, "")
+ @plugin = $1.downcase
+ @params = nil unless @params.length > 0
+ end
+ end
+
+ # returns true for private messages, e.g. "/msg bot hello"
+ def private?
+ return @private
+ end
+
+ # returns true if the message was in a channel
+ def public?
+ return !@private
+ end
+
+ def action?
+ return @action
+ end
+
+ # convenience method to reply to a message, useful in plugins. It's the
+ # same as doing:
+ # <tt>@bot.say m.replyto, string</tt>
+ # So if the message is private, it will reply to the user. If it was
+ # in a channel, it will reply in the channel.
+ def reply(string)
+ @bot.say @replyto, string
+ @replied = true
+ end
+
+ # convenience method to reply "okay" in the current language to the
+ # message
+ def okay
+ @bot.say @replyto, @bot.lang.get("okay")
+ end
+
+ end
+
+ # class to manage IRC PRIVMSGs
+ class PrivMessage < UserMessage
+ end
+
+ # class to manage IRC NOTICEs
+ class NoticeMessage < UserMessage
+ end
+
+ # class to manage IRC KICKs
+ # +address?+ can be used as a shortcut to see if the bot was kicked,
+ # basically, +target+ was kicked from +channel+ by +source+ with +message+
+ class KickMessage < BasicUserMessage
+ # channel user was kicked from
+ attr_reader :channel
+
+ def initialize(bot, source, target, channel, message="")
+ super(bot, source, target, message)
+ @channel = channel
+ end
+ end
+
+ # class to pass IRC Nick changes in. @message contains the old nickame,
+ # @sourcenick contains the new one.
+ class NickMessage < BasicUserMessage
+ def initialize(bot, source, oldnick, newnick)
+ super(bot, source, oldnick, newnick)
+ end
+ end
+
+ class QuitMessage < BasicUserMessage
+ def initialize(bot, source, target, message="")
+ super(bot, source, target, message)
+ end
+ end
+
+ class TopicMessage < BasicUserMessage
+ # channel topic
+ attr_reader :topic
+ # topic set at (unixtime)
+ attr_reader :timestamp
+ # topic set on channel
+ attr_reader :channel
+
+ def initialize(bot, source, channel, timestamp, topic="")
+ super(bot, source, channel, topic)
+ @topic = topic
+ @timestamp = timestamp
+ @channel = channel
+ end
+ end
+
+ # class to manage channel joins
+ class JoinMessage < BasicUserMessage
+ # channel joined
+ attr_reader :channel
+ def initialize(bot, source, channel, message="")
+ super(bot, source, channel, message)
+ @channel = channel
+ # in this case sourcenick is the nick that could be the bot
+ @address = (sourcenick.downcase == @bot.nick.downcase)
+ end
+ end
+
+ # class to manage channel parts
+ # same as a join, but can have a message too
+ class PartMessage < JoinMessage
+ end
+end
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/message.rb
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/message.rb
------------------------------------------------------------------------------
svn:keywords = "Author Date Id Revision"
Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/messagemapper.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/messagemapper.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/messagemapper.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/messagemapper.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,267 @@
+module Irc
+
+ # +MessageMapper+ is a class designed to reduce the amount of regexps and
+ # string parsing plugins and bot modules need to do, in order to process
+ # and respond to messages.
+ #
+ # You add templates to the MessageMapper which are examined by the handle
+ # method when handling a message. The templates tell the mapper which
+ # method in its parent class (your class) to invoke for that message. The
+ # string is split, optionally defaulted and validated before being passed
+ # to the matched method.
+ #
+ # A template such as "foo :option :otheroption" will match the string "foo
+ # bar baz" and, by default, result in method +foo+ being called, if
+ # present, in the parent class. It will receive two parameters, the
+ # Message (derived from BasicUserMessage) and a Hash containing
+ # {:option => "bar", :otheroption => "baz"}
+ # See the #map method for more details.
+ class MessageMapper
+ # used to set the method name used as a fallback for unmatched messages.
+ # The default fallback is a method called "usage".
+ attr_writer :fallback
+
+ # parent:: parent class which will receive mapped messages
+ #
+ # create a new MessageMapper with parent class +parent+. This class will
+ # receive messages from the mapper via the handle() method.
+ def initialize(parent)
+ @parent = parent
+ @templates = Array.new
+ @fallback = 'usage'
+ end
+
+ # args:: hash format containing arguments for this template
+ #
+ # map a template string to an action. example:
+ # map 'myplugin :parameter1 :parameter2'
+ # (other examples follow). By default, maps a matched string to an
+ # action with the name of the first word in the template. The action is
+ # a method which takes a message and a parameter hash for arguments.
+ #
+ # The :action => 'method_name' option can be used to override this
+ # default behaviour. Example:
+ # map 'myplugin :parameter1 :parameter2', :action => 'mymethod'
+ #
+ # By default whether a handler is fired depends on an auth check. The
+ # first component of the string is used for the auth check, unless
+ # overridden via the :auth => 'auth_name' option.
+ #
+ # Static parameters (not prefixed with ':' or '*') must match the
+ # respective component of the message exactly. Example:
+ # map 'myplugin :foo is :bar'
+ # will only match messages of the form "myplugin something is
+ # somethingelse"
+ #
+ # Dynamic parameters can be specified by a colon ':' to match a single
+ # component (whitespace seperated), or a * to such up all following
+ # parameters into an array. Example:
+ # map 'myplugin :parameter1 *rest'
+ #
+ # You can provide defaults for dynamic components using the :defaults
+ # parameter. If a component has a default, then it is optional. e.g:
+ # map 'myplugin :foo :bar', :defaults => {:bar => 'qux'}
+ # would match 'myplugin param param2' and also 'myplugin param'. In the
+ # latter case, :bar would be provided from the default.
+ #
+ # Components can be validated before being allowed to match, for
+ # example if you need a component to be a number:
+ # map 'myplugin :param', :requirements => {:param => /^\d+$/}
+ # will only match strings of the form 'myplugin 1234' or some other
+ # number.
+ #
+ # Templates can be set not to match public or private messages using the
+ # :public or :private boolean options.
+ #
+ # Further examples:
+ #
+ # # match 'karmastats' and call my stats() method
+ # map 'karmastats', :action => 'stats'
+ # # match 'karma' with an optional 'key' and call my karma() method
+ # map 'karma :key', :defaults => {:key => false}
+ # # match 'karma for something' and call my karma() method
+ # map 'karma for :key'
+ #
+ # # two matches, one for public messages in a channel, one for
+ # # private messages which therefore require a channel argument
+ # map 'urls search :channel :limit :string', :action => 'search',
+ # :defaults => {:limit => 4},
+ # :requirements => {:limit => /^\d+$/},
+ # :public => false
+ # plugin.map 'urls search :limit :string', :action => 'search',
+ # :defaults => {:limit => 4},
+ # :requirements => {:limit => /^\d+$/},
+ # :private => false
+ #
+ def map(*args)
+ @templates << Template.new(*args)
+ end
+
+ def each
+ @templates.each {|tmpl| yield tmpl}
+ end
+ def last
+ @templates.last
+ end
+
+ # m:: derived from BasicUserMessage
+ #
+ # examine the message +m+, comparing it with each map()'d template to
+ # find and process a match. Templates are examined in the order they
+ # were map()'d - first match wins.
+ #
+ # returns +true+ if a match is found including fallbacks, +false+
+ # otherwise.
+ def handle(m)
+ return false if @templates.empty?
+ failures = []
+ @templates.each do |tmpl|
+ options, failure = tmpl.recognize(m)
+ if options.nil?
+ failures << [tmpl, failure]
+ else
+ action = tmpl.options[:action] ? tmpl.options[:action] : tmpl.items[0]
+ unless @parent.respond_to?(action)
+ failures << [tmpl, "class does not respond to action #{action}"]
+ next
+ end
+ auth = tmpl.options[:auth] ? tmpl.options[:auth] : tmpl.items[0]
+ debug "checking auth for #{auth}"
+ if m.bot.auth.allow?(auth, m.source, m.replyto)
+ debug "template match found and auth'd: #{action.inspect} #{options.inspect}"
+ @parent.send(action, m, options)
+ return true
+ end
+ debug "auth failed for #{auth}"
+ # if it's just an auth failure but otherwise the match is good,
+ # don't try any more handlers
+ return false
+ end
+ end
+ failures.each {|f, r|
+ debug "#{f.inspect} => #{r}"
+ }
+ debug "no handler found, trying fallback"
+ if @fallback != nil && @parent.respond_to?(@fallback)
+ if m.bot.auth.allow?(@fallback, m.source, m.replyto)
+ @parent.send(@fallback, m, {})
+ return true
+ end
+ end
+ return false
+ end
+
+ end
+
+ class Template
+ attr_reader :defaults # The defaults hash
+ attr_reader :options # The options hash
+ attr_reader :items
+ def initialize(template, hash={})
+ raise ArgumentError, "Second argument must be a hash!" unless hash.kind_of?(Hash)
+ @defaults = hash[:defaults].kind_of?(Hash) ? hash.delete(:defaults) : {}
+ @requirements = hash[:requirements].kind_of?(Hash) ? hash.delete(:requirements) : {}
+ self.items = template
+ @options = hash
+ end
+ def items=(str)
+ items = str.split(/\s+/).collect {|c| (/^(:|\*)(\w+)$/ =~ c) ? (($1 == ':' ) ? $2.intern : "*#{$2}".intern) : c} if str.kind_of?(String) # split and convert ':xyz' to symbols
+ items.shift if items.first == ""
+ items.pop if items.last == ""
+ @items = items
+
+ if @items.first.kind_of? Symbol
+ raise ArgumentError, "Illegal template -- first component cannot be dynamic\n #{str.inspect}"
+ end
+
+ # Verify uniqueness of each component.
+ @items.inject({}) do |seen, item|
+ if item.kind_of? Symbol
+ raise ArgumentError, "Illegal template -- duplicate item #{item}\n #{str.inspect}" if seen.key? item
+ seen[item] = true
+ end
+ seen
+ end
+ end
+
+ # Recognize the provided string components, returning a hash of
+ # recognized values, or [nil, reason] if the string isn't recognized.
+ def recognize(m)
+ components = m.message.split(/\s+/)
+ options = {}
+
+ @items.each do |item|
+ if /^\*/ =~ item.to_s
+ if components.empty?
+ value = @defaults.has_key?(item) ? @defaults[item].clone : []
+ else
+ value = components.clone
+ end
+ components = []
+ def value.to_s() self.join(' ') end
+ options[item.to_s.sub(/^\*/,"").intern] = value
+ elsif item.kind_of? Symbol
+ value = components.shift || @defaults[item]
+ if passes_requirements?(item, value)
+ options[item] = value
+ else
+ if @defaults.has_key?(item)
+ options[item] = @defaults[item]
+ # push the test-failed component back on the stack
+ components.unshift value
+ else
+ return nil, requirements_for(item)
+ end
+ end
+ else
+ return nil, "No value available for component #{item.inspect}" if components.empty?
+ component = components.shift
+ return nil, "Value for component #{item.inspect} doesn't match #{component}" if component != item
+ end
+ end
+
+ return nil, "Unused components were left: #{components.join '/'}" unless components.empty?
+
+ return nil, "template is not configured for private messages" if @options.has_key?(:private) && !@options[:private] && m.private?
+ return nil, "template is not configured for public messages" if @options.has_key?(:public) && !@options[:public] && !m.private?
+
+ options.delete_if {|k, v| v.nil?} # Remove nil values.
+ return options, nil
+ end
+
+ def inspect
+ when_str = @requirements.empty? ? "" : " when #{@requirements.inspect}"
+ default_str = @defaults.empty? ? "" : " || #{@defaults.inspect}"
+ "<#{self.class.to_s} #{@items.collect{|c| c.kind_of?(String) ? c : c.inspect}.join(' ').inspect}#{default_str}#{when_str}>"
+ end
+
+ # Verify that the given value passes this template's requirements
+ def passes_requirements?(name, value)
+ return @defaults.key?(name) && @defaults[name].nil? if value.nil? # Make sure it's there if it should be
+
+ case @requirements[name]
+ when nil then true
+ when Regexp then
+ value = value.to_s
+ match = @requirements[name].match(value)
+ match && match[0].length == value.length
+ else
+ @requirements[name] == value.to_s
+ end
+ end
+
+ def requirements_for(name)
+ name = name.to_s.sub(/^\*/,"").intern if (/^\*/ =~ name.inspect)
+ presence = (@defaults.key?(name) && @defaults[name].nil?)
+ requirement = case @requirements[name]
+ when nil then nil
+ when Regexp then "match #{@requirements[name].inspect}"
+ else "be equal to #{@requirements[name].inspect}"
+ end
+ if presence && requirement then "#{name} must be present and #{requirement}"
+ elsif presence || requirement then "#{name} must #{requirement || 'be present'}"
+ else "#{name} has no requirements"
+ end
+ end
+ end
+end
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/messagemapper.rb
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/messagemapper.rb
------------------------------------------------------------------------------
svn:keywords = "Author Date Id Revision"
Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/pkgconfig.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/pkgconfig.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/pkgconfig.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/pkgconfig.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,5 @@
+module Irc
+ module PKGConfig
+ DATADIR = '/usr/share/rbot'
+ end
+end
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/pkgconfig.rb
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/pkgconfig.rb
------------------------------------------------------------------------------
svn:keywords = "Author Date Id Revision"
Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/plugins.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/plugins.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/plugins.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/plugins.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,299 @@
+module Irc
+module Plugins
+ require 'rbot/messagemapper'
+
+ # 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:
+ #
+ # map(template, options)::
+ # map is the new, cleaner way to respond to specific message formats
+ # without littering your plugin code with regexps. examples:
+ #
+ # plugin.map 'karmastats', :action => 'karma_stats'
+ #
+ # # while in the plugin...
+ # def karma_stats(m, params)
+ # m.reply "..."
+ # end
+ #
+ # # the default action is the first component
+ # plugin.map 'karma'
+ #
+ # # attributes can be pulled out of the match string
+ # plugin.map 'karma for :key'
+ # plugin.map 'karma :key'
+ #
+ # # while in the plugin...
+ # def karma(m, params)
+ # 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'
+ #
+ # # maps can be restricted to public or private message:
+ # plugin.map 'karmastats', :private false,
+ # 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
+ # that word and m.params for the rest of the message,
+ # if applicable.
+ #
+ # kick(KickMessage)::
+ # Called when a user (or the bot) is kicked from a
+ # channel the bot is in.
+ #
+ # join(JoinMessage)::
+ # Called when a user (or the bot) joins a channel
+ #
+ # part(PartMessage)::
+ # Called when a user (or the bot) parts a channel
+ #
+ # quit(QuitMessage)::
+ # Called when a user (or the bot) quits IRC
+ #
+ # nick(NickMessage)::
+ # Called when a user (or the bot) changes Nick
+ # topic(TopicMessage)::
+ # Called when a user (or the bot) changes a channel
+ # topic
+ #
+ # 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
+ #
+ # cleanup:: called before your plugin is "unloaded", prior to a
+ # plugin reload or bot quit - close any open
+ # files/connections or flush caches here
+ class Plugin
+ attr_reader :bot # the associated bot
+ # initialise your plugin. Always call super if you override this method,
+ # as important variables are set up for you
+ def initialize
+ @bot = Plugins.bot
+ @names = Array.new
+ @handler = MessageMapper.new(self)
+ @registry = BotRegistryAccessor.new(@bot, self.class.to_s.gsub(/^.*::/, ""))
+ end
+
+ def map(*args)
+ @handler.map(*args)
+ # register this map
+ name = @handler.last.items[0]
+ self.register name
+ 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
+ # this message - if your plugin handles multiple prefixes, make sure your
+ # return the correct help for the prefix requested
+ 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)
+ return if Plugins.plugins.has_key?(name)
+ Plugins.plugins[name] = self
+ @names << name
+ end
+
+ # 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}'"
+ 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
+
+ # 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, datadir )
+ @@datadir = datadir
+ @@bot = bot
+ @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
+ processed = Array.new
+ dirs = Array.new
+ dirs << @@datadir + "/plugins"
+ dirs += @dirs
+ dirs.reverse.each {|dir|
+ if(FileTest.directory?(dir))
+ d = Dir.new(dir)
+ d.sort.each {|file|
+ next if(file =~ /^\./)
+ next if(processed.include?(file))
+ if(file =~ /^(.+\.rb)\.disabled$/)
+ processed << $1
+ next
+ end
+ 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)
+ processed << file
+ rescue TimeoutError, StandardError, NameError, LoadError, SyntaxError => err
+ puts "warning: plugin #{tmpfilename} load failed: " + err
+ puts err.backtrace.join("\n")
+ end
+ }
+ end
+ }
+ end
+
+ # call the save method for each active plugin
+ def save
+ delegate 'save'
+ end
+
+ # call the cleanup method for each active plugin
+ def cleanup
+ delegate 'cleanup'
+ end
+
+ # drop all plugins and rescan plugins on disk
+ # calls save and cleanup for each plugin before dropping them
+ def rescan
+ save
+ cleanup
+ @@plugins = Hash.new
+ scan
+ 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(", ") + "]"
+ else
+ return " [no plugins active]"
+ end
+ end
+
+ def length
+ @@plugins.values.uniq.length
+ end
+
+ # return help for +topic+ (call associated plugin's help method)
+ def help(topic="")
+ 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")
+ 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)
+ @@plugins.values.uniq.each {|p|
+ 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")
+ 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 TimeoutError, StandardError, NameError, SyntaxError => err
+ puts "plugin #{@@plugins[m.plugin].name} privmsg() failed: " + err
+ puts err.backtrace.join("\n")
+ end
+ return true
+ end
+ return false
+ end
+ end
+
+end
+end
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/plugins.rb
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/plugins.rb
------------------------------------------------------------------------------
svn:keywords = "Author Date Id Revision"
Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/rbotconfig.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/rbotconfig.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/rbotconfig.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/rbotconfig.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,36 @@
+module Irc
+ module Config
+ @@datadir = nil
+ # setup pkg-based configuration - i.e. where were we installed to, where
+ # are our data files, etc.
+ begin
+ debug "trying to load rubygems"
+ require 'rubygems'
+ debug "loaded rubygems, looking for rbot-#$version"
+ gemname, gem = Gem.source_index.find{|name, spec| spec.name == 'rbot' && spec.version.version == $version}
+ debug "got gem #{gem}"
+ if gem && path = gem.full_gem_path
+ debug "installed via rubygems to #{path}"
+ @@datadir = "#{path}/data/rbot"
+ else
+ debug "not installed via rubygems"
+ end
+ rescue LoadError
+ debug "no rubygems installed"
+ end
+
+ if @@datadir.nil?
+ begin
+ require 'rbot/pkgconfig'
+ @@datadir = PKGConfig::DATADIR
+ rescue LoadError
+ puts "fatal - no way to determine data dir"
+ exit 2
+ end
+ end
+
+ def Config.datadir
+ @@datadir
+ end
+ end
+end
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/rbotconfig.rb
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/rbotconfig.rb
------------------------------------------------------------------------------
svn:keywords = "Author Date Id Revision"
Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/registry.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/registry.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/registry.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/registry.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,271 @@
+require 'rbot/dbhash'
+
+module Irc
+
+ # this is the backend of the RegistryAccessor class, which ties it to a
+ # DBHash object called plugin_registry(.db). All methods are delegated to
+ # the DBHash.
+ class BotRegistry
+ def initialize(bot)
+ @bot = bot
+ upgrade_data
+ @db = DBTree.new @bot, "plugin_registry"
+ end
+
+ # delegation hack
+ def method_missing(method, *args, &block)
+ @db.send(method, *args, &block)
+ end
+
+ # check for older versions of rbot with data formats that require updating
+ # NB this function is called _early_ in init(), pretty much all you have to
+ # work with is @bot.botclass.
+ def upgrade_data
+ if File.exist?("#{@bot.botclass}/registry.db")
+ puts "upgrading old-style (rbot 0.9.5 or earlier) plugin registry to new format"
+ old = BDB::Hash.open "#{@bot.botclass}/registry.db", nil,
+ "r+", 0600, "set_pagesize" => 1024,
+ "set_cachesize" => [0, 32 * 1024, 0]
+ new = BDB::CIBtree.open "#{@bot.botclass}/plugin_registry.db", nil,
+ BDB::CREATE | BDB::EXCL | BDB::TRUNCATE,
+ 0600, "set_pagesize" => 1024,
+ "set_cachesize" => [0, 32 * 1024, 0]
+ old.each {|k,v|
+ new[k] = v
+ }
+ old.close
+ new.close
+ File.delete("#{@bot.botclass}/registry.db")
+ end
+ end
+ end
+
+ # This class provides persistent storage for plugins via a hash interface.
+ # The default mode is an object store, so you can store ruby objects and
+ # reference them with hash keys. This is because the default store/restore
+ # methods of the plugins' RegistryAccessor are calls to Marshal.dump and
+ # Marshal.restore,
+ # for example:
+ # blah = Hash.new
+ # blah[:foo] = "fum"
+ # @registry[:blah] = blah
+ # then, even after the bot is shut down and disconnected, on the next run you
+ # can access the blah object as it was, with:
+ # blah = @registry[:blah]
+ # The registry can of course be used to store simple strings, fixnums, etc as
+ # well, and should be useful to store or cache plugin data or dynamic plugin
+ # configuration.
+ #
+ # WARNING:
+ # in object store mode, don't make the mistake of treating it like a live
+ # object, e.g. (using the example above)
+ # @registry[:blah][:foo] = "flump"
+ # will NOT modify the object in the registry - remember that BotRegistry#[]
+ # returns a Marshal.restore'd object, the object you just modified in place
+ # will disappear. You would need to:
+ # blah = @registry[:blah]
+ # blah[:foo] = "flump"
+ # @registry[:blah] = blah
+
+ # If you don't need to store objects, and strictly want a persistant hash of
+ # strings, you can override the store/restore methods to suit your needs, for
+ # example (in your plugin):
+ # def initialize
+ # class << @registry
+ # def store(val)
+ # val
+ # end
+ # def restore(val)
+ # val
+ # end
+ # end
+ # end
+ # Your plugins section of the registry is private, it has its own namespace
+ # (derived from the plugin's class name, so change it and lose your data).
+ # Calls to registry.each etc, will only iterate over your namespace.
+ class BotRegistryAccessor
+ # plugins don't call this - a BotRegistryAccessor is created for them and
+ # is accessible via @registry.
+ def initialize(bot, prefix)
+ @bot = bot
+ @registry = @bot.registry
+ @orig_prefix = prefix
+ @prefix = prefix + "/"
+ @default = nil
+ # debug "initializing registry accessor with prefix #{@prefix}"
+ end
+
+ # use this to chop up your namespace into bits, so you can keep and
+ # reference separate object stores under the same registry
+ def sub_registry(prefix)
+ return BotRegistryAccessor.new(@bot, @orig_prefix + "+" + prefix)
+ end
+
+ # convert value to string form for storing in the registry
+ # defaults to Marshal.dump(val) but you can override this in your module's
+ # registry object to use any method you like.
+ # For example, if you always just handle strings use:
+ # def store(val)
+ # val
+ # end
+ def store(val)
+ Marshal.dump(val)
+ end
+
+ # restores object from string form, restore(store(val)) must return val.
+ # If you override store, you should override restore to reverse the
+ # action.
+ # For example, if you always just handle strings use:
+ # def restore(val)
+ # val
+ # end
+ def restore(val)
+ Marshal.restore(val)
+ end
+
+ # lookup a key in the registry
+ def [](key)
+ if @registry.has_key?(@prefix + key)
+ return restore(@registry[@prefix + key])
+ elsif @default != nil
+ return restore(@default)
+ else
+ return nil
+ end
+ end
+
+ # set a key in the registry
+ def []=(key,value)
+ @registry[@prefix + key] = store(value)
+ end
+
+ # set the default value for registry lookups, if the key sought is not
+ # found, the default will be returned. The default default (har) is nil.
+ def set_default (default)
+ @default = store(default)
+ end
+
+ # just like Hash#each
+ def each(&block)
+ @registry.each {|key,value|
+ if key.gsub!(/^#{Regexp.escape(@prefix)}/, "")
+ block.call(key, restore(value))
+ end
+ }
+ end
+
+ # just like Hash#each_key
+ def each_key(&block)
+ @registry.each {|key, value|
+ if key.gsub!(/^#{Regexp.escape(@prefix)}/, "")
+ block.call(key)
+ end
+ }
+ end
+
+ # just like Hash#each_value
+ def each_value(&block)
+ @registry.each {|key, value|
+ if key =~ /^#{Regexp.escape(@prefix)}/
+ block.call(restore(value))
+ end
+ }
+ end
+
+ # just like Hash#has_key?
+ def has_key?(key)
+ return @registry.has_key?(@prefix + key)
+ end
+ alias include? has_key?
+ alias member? has_key?
+
+ # just like Hash#has_both?
+ def has_both?(key, value)
+ return @registry.has_both?(@prefix + key, store(value))
+ end
+
+ # just like Hash#has_value?
+ def has_value?(value)
+ return @registry.has_value?(store(value))
+ end
+
+ # just like Hash#index?
+ def index(value)
+ ind = @registry.index(store(value))
+ if ind && ind.gsub!(/^#{Regexp.escape(@prefix)}/, "")
+ return ind
+ else
+ return nil
+ end
+ end
+
+ # delete a key from the registry
+ def delete(key)
+ return @registry.delete(@prefix + key)
+ end
+
+ # returns a list of your keys
+ def keys
+ return @registry.keys.collect {|key|
+ if key.gsub!(/^#{Regexp.escape(@prefix)}/, "")
+ key
+ else
+ nil
+ end
+ }.compact
+ end
+
+ # Return an array of all associations [key, value] in your namespace
+ def to_a
+ ret = Array.new
+ @registry.each {|key, value|
+ if key.gsub!(/^#{Regexp.escape(@prefix)}/, "")
+ ret << [key, restore(value)]
+ end
+ }
+ return ret
+ end
+
+ # Return an hash of all associations {key => value} in your namespace
+ def to_hash
+ ret = Hash.new
+ @registry.each {|key, value|
+ if key.gsub!(/^#{Regexp.escape(@prefix)}/, "")
+ ret[key] = restore(value)
+ end
+ }
+ return ret
+ end
+
+ # empties the registry (restricted to your namespace)
+ def clear
+ @registry.each_key {|key|
+ if key =~ /^#{Regexp.escape(@prefix)}/
+ @registry.delete(key)
+ end
+ }
+ end
+ alias truncate clear
+
+ # returns an array of the values in your namespace of the registry
+ def values
+ ret = Array.new
+ self.each {|k,v|
+ ret << restore(v)
+ }
+ return ret
+ end
+
+ # returns the number of keys in your registry namespace
+ def length
+ self.keys.length
+ end
+ alias size length
+
+ def flush
+ @registry.flush
+ end
+
+ end
+
+end
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/registry.rb
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/registry.rb
------------------------------------------------------------------------------
svn:keywords = "Author Date Id Revision"