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 [1/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/
Author: jvanzyl
Date: Fri Dec 16 14:30:38 2005
New Revision: 357245
URL: http://svn.apache.org/viewcvs?rev=357245&view=rev
Log: (empty)
Added:
maven/sandbox/issue/rbot/dist/
maven/sandbox/issue/rbot/dist/bin/
maven/sandbox/issue/rbot/dist/bin/rbot (with props)
maven/sandbox/issue/rbot/dist/conf.yaml
maven/sandbox/issue/rbot/dist/filters.yaml (with props)
maven/sandbox/issue/rbot/dist/keyword.db (with props)
maven/sandbox/issue/rbot/dist/levels.rbot
maven/sandbox/issue/rbot/dist/lib/
maven/sandbox/issue/rbot/dist/lib/site_ruby/
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/issue.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/jira4r.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/auth.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/channel.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/config.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/dbhash.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/httputil.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/ircbot.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/ircsocket.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/keywords.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/language.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/message.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/messagemapper.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/pkgconfig.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/plugins.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/rbotconfig.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/registry.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/rfc2812.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/timer.rb (with props)
maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/utils.rb (with props)
maven/sandbox/issue/rbot/dist/plugin_registry.db (with props)
maven/sandbox/issue/rbot/dist/plugins/
maven/sandbox/issue/rbot/dist/plugins/jira.rb (with props)
maven/sandbox/issue/rbot/dist/rbot.sh (with props)
maven/sandbox/issue/rbot/dist/share/
maven/sandbox/issue/rbot/dist/share/rbot/
maven/sandbox/issue/rbot/dist/share/rbot/languages/
maven/sandbox/issue/rbot/dist/share/rbot/languages/dutch.lang
maven/sandbox/issue/rbot/dist/share/rbot/languages/english.lang
maven/sandbox/issue/rbot/dist/share/rbot/languages/french.lang
maven/sandbox/issue/rbot/dist/share/rbot/languages/german.lang
maven/sandbox/issue/rbot/dist/share/rbot/templates/
maven/sandbox/issue/rbot/dist/share/rbot/templates/keywords.rbot
maven/sandbox/issue/rbot/dist/share/rbot/templates/lart/
maven/sandbox/issue/rbot/dist/share/rbot/templates/lart/larts
maven/sandbox/issue/rbot/dist/share/rbot/templates/lart/praises
maven/sandbox/issue/rbot/dist/share/rbot/templates/levels.rbot
maven/sandbox/issue/rbot/dist/share/rbot/templates/users.rbot
maven/sandbox/issue/rbot/dist/users.rbot
Added: maven/sandbox/issue/rbot/dist/bin/rbot
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/bin/rbot?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/bin/rbot (added)
+++ maven/sandbox/issue/rbot/dist/bin/rbot Fri Dec 16 14:30:38 2005
@@ -0,0 +1,81 @@
+#!/usr/bin/env ruby
+
+# Copyright (C) 2002 Tom Gilbert.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies of the Software and its documentation and acknowledgment shall be
+# given in the documentation and software packages that this Software was
+# used.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+$VERBOSE=true
+
+require 'etc'
+require 'getoptlong'
+require 'fileutils'
+
+$version="0.9.9"
+$opts = Hash.new
+
+orig_opts = ARGV.dup
+
+opts = GetoptLong.new(
+ ["--debug", "-d", GetoptLong::NO_ARGUMENT],
+ ["--help", "-h", GetoptLong::NO_ARGUMENT],
+ ["--trace", "-t", GetoptLong::REQUIRED_ARGUMENT],
+ ["--version", "-v", GetoptLong::NO_ARGUMENT]
+)
+
+$debug = false
+opts.each {|opt, arg|
+ $debug = true if(opt == "--debug")
+ $opts[opt.sub(/^-+/, "")] = arg
+}
+
+if ($opts["trace"])
+ set_trace_func proc { |event, file, line, id, binding, classname|
+ if classname.to_s == $opts["trace"]
+ printf "TRACE: %8s %s:%-2d %10s %8s\n", event, File.basename(file), line, id, classname
+ end
+ }
+end
+
+begin
+ require 'rbot/ircbot'
+rescue LoadError => e
+ puts "Error: couldn't find the rbot/ircbot module for loading\n - did you install rbot using setup.rb?"
+ exit 2
+end
+
+if ($opts["version"])
+ puts "rbot #{$version}"
+ exit 0
+end
+
+if ($opts["help"])
+ puts "usage: rbot [options] [config directory]"
+ puts " -h, --help this message"
+ puts " -v, --version version information"
+ puts " -d, --debug enable debug messages"
+ puts "config directory defaults to ~/.rbot"
+ exit 0
+end
+
+if(bot = Irc::IrcBot.new(ARGV.shift, ARGV.shift, :argv => orig_opts))
+ # just run the bot
+ bot.mainloop
+end
+
Propchange: maven/sandbox/issue/rbot/dist/bin/rbot
------------------------------------------------------------------------------
svn:executable = *
Added: maven/sandbox/issue/rbot/dist/conf.yaml
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/conf.yaml?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/conf.yaml (added)
+++ maven/sandbox/issue/rbot/dist/conf.yaml Fri Dec 16 14:30:38 2005
@@ -0,0 +1,7 @@
+core.address_prefix:
+- "!"
+irc.nick: faqbot
+server.name: irc.codehaus.org
+irc.join_channels:
+- "#foo"
+datadir: /home/jvanzyl/js/org/apache/maven/sandbox/issue/rbot/dist/share/rbot
Added: maven/sandbox/issue/rbot/dist/filters.yaml
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/filters.yaml?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/filters.yaml (added)
+++ maven/sandbox/issue/rbot/dist/filters.yaml Fri Dec 16 14:30:38 2005
@@ -0,0 +1 @@
+link /home/jvanzyl/js/org/apache/maven/sandbox/issue/rissue/filters.yaml
\ No newline at end of file
Propchange: maven/sandbox/issue/rbot/dist/filters.yaml
------------------------------------------------------------------------------
svn:special = *
Added: maven/sandbox/issue/rbot/dist/keyword.db
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/keyword.db?rev=357245&view=auto
==============================================================================
Binary file - no diff available.
Propchange: maven/sandbox/issue/rbot/dist/keyword.db
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: maven/sandbox/issue/rbot/dist/levels.rbot
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/levels.rbot?rev=357245&view=auto
==============================================================================
(empty)
Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/issue.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/issue.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/issue.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/issue.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1 @@
+link /home/jvanzyl/js/org/apache/maven/sandbox/issue/rissue/issue.rb
\ No newline at end of file
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/issue.rb
------------------------------------------------------------------------------
svn:special = *
Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/jira4r.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/jira4r.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/jira4r.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/jira4r.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1 @@
+link /home/jvanzyl/js/org/apache/maven/sandbox/issue/rissue/jira4r.rb
\ No newline at end of file
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/jira4r.rb
------------------------------------------------------------------------------
svn:special = *
Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/auth.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/auth.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/auth.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/auth.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,203 @@
+module Irc
+
+ # globmask:: glob to test with
+ # netmask:: netmask to test against
+ # Compare a netmask with a standard IRC glob, e.g foo!bar@baz.com would
+ # match *!*@baz.com, foo!*@*, *!bar@*, etc.
+ def Irc.netmaskmatch(globmask, netmask)
+ regmask = globmask.gsub(/\*/, ".*?")
+ return true if(netmask =~ /#{regmask}/i)
+ return false
+ end
+
+ # check if a string is an actual IRC hostmask
+ def Irc.ismask(mask)
+ mask =~ /^.+!.+@.+$/
+ end
+
+
+ # User-level authentication to allow/disallow access to bot commands based
+ # on hostmask and userlevel.
+ class IrcAuth
+ BotConfig.register BotConfigStringValue.new('auth.password',
+ :default => "rbotauth", :wizard => true,
+ :desc => "Your password for maxing your auth with the bot (used to associate new hostmasks with your owner-status etc)")
+
+ # create a new IrcAuth instance.
+ # bot:: associated bot class
+ def initialize(bot)
+ @bot = bot
+ @users = Hash.new(0)
+ @levels = Hash.new(0)
+ if(File.exist?("#{@bot.botclass}/users.rbot"))
+ IO.foreach("#{@bot.botclass}/users.rbot") do |line|
+ if(line =~ /\s*(\d+)\s*(\S+)/)
+ level = $1.to_i
+ mask = $2
+ @users[mask] = level
+ end
+ end
+ end
+ if(File.exist?("#{@bot.botclass}/levels.rbot"))
+ IO.foreach("#{@bot.botclass}/levels.rbot") do |line|
+ if(line =~ /\s*(\d+)\s*(\S+)/)
+ level = $1.to_i
+ command = $2
+ @levels[command] = level
+ end
+ end
+ end
+ end
+
+ # save current users and levels to files.
+ # levels are written to #{botclass}/levels.rbot
+ # users are written to #{botclass}/users.rbot
+ def save
+ Dir.mkdir("#{@bot.botclass}") if(!File.exist?("#{@bot.botclass}"))
+ File.open("#{@bot.botclass}/users.rbot", "w") do |file|
+ @users.each do |key, value|
+ file.puts "#{value} #{key}"
+ end
+ end
+ File.open("#{@bot.botclass}/levels.rbot", "w") do |file|
+ @levels.each do |key, value|
+ file.puts "#{value} #{key}"
+ end
+ end
+ end
+
+ # command:: command user wishes to perform
+ # mask:: hostmask of user
+ # tell:: optional recipient for "insufficient auth" message
+ #
+ # returns true if user with hostmask +mask+ is permitted to perform
+ # +command+ optionally pass tell as the target for the "insufficient auth"
+ # message, if the user is not authorised
+ def allow?(command, mask, tell=nil)
+ auth = userlevel(mask)
+ if(auth >= @levels[command])
+ return true
+ else
+ debug "#{mask} is not allowed to perform #{command}"
+ @bot.say tell, "insufficient \"#{command}\" auth (have #{auth}, need #{@levels[command]})" if tell
+ return false
+ end
+ end
+
+ # add user with hostmask matching +mask+ with initial auth level +level+
+ def useradd(mask, level)
+ if(Irc.ismask(mask))
+ @users[mask] = level
+ end
+ end
+
+ # mask:: mask of user to remove
+ # remove user with mask +mask+
+ def userdel(mask)
+ if(Irc.ismask(mask))
+ @users.delete(mask)
+ end
+ end
+
+ # command:: command to adjust
+ # level:: new auth level for the command
+ # set required auth level of +command+ to +level+
+ def setlevel(command, level)
+ @levels[command] = level
+ end
+
+ # specific users.
+ # mask:: mask of user
+ # returns the authlevel of user with mask +mask+
+ # finds the matching user which has the highest authlevel (so you can have
+ # a default level of 5 for *!*@*, and yet still give higher levels to
+ def userlevel(mask)
+ # go through hostmask list, find match with _highest_ level (all users
+ # will match *!*@*)
+ level = 0
+ @users.each {|user,userlevel|
+ if(Irc.netmaskmatch(user, mask))
+ level = userlevel if userlevel > level
+ end
+ }
+ level
+ end
+
+ # return all currently defined commands (for which auth is required) and
+ # their required authlevels
+ def showlevels
+ reply = "Current levels are:"
+ @levels.sort.each {|a|
+ key = a[0]
+ value = a[1]
+ reply += " #{key}(#{value})"
+ }
+ reply
+ end
+
+ # return all currently defined users and their authlevels
+ def showusers
+ reply = "Current users are:"
+ @users.sort.each {|a|
+ key = a[0]
+ value = a[1]
+ reply += " #{key}(#{value})"
+ }
+ reply
+ end
+
+ # module help
+ def help(topic="")
+ case topic
+ when "setlevel"
+ return "setlevel <command> <level> => Sets required level for <command> to <level> (private addressing only)"
+ when "useradd"
+ return "useradd <mask> <level> => Add user <mask> at level <level> (private addressing only)"
+ when "userdel"
+ return "userdel <mask> => Remove user <mask> (private addressing only)"
+ when "auth"
+ return "auth <masterpw> => Recognise your hostmask as bot master (private addressing only)"
+ when "levels"
+ return "levels => list commands and their required levels (private addressing only)"
+ when "users"
+ return "users => list users and their levels (private addressing only)"
+ else
+ return "Auth module (User authentication) topics: setlevel, useradd, userdel, auth, levels, users"
+ end
+ end
+
+ # privmsg handler
+ def privmsg(m)
+ if(m.address? && m.private?)
+ case m.message
+ when (/^setlevel\s+(\S+)\s+(\d+)$/)
+ if(@bot.auth.allow?("auth", m.source, m.replyto))
+ @bot.auth.setlevel($1, $2.to_i)
+ m.reply "level for #$1 set to #$2"
+ end
+ when (/^useradd\s+(\S+)\s+(\d+)/)
+ if(@bot.auth.allow?("auth", m.source, m.replyto))
+ @bot.auth.useradd($1, $2.to_i)
+ m.reply "added user #$1 at level #$2"
+ end
+ when (/^userdel\s+(\S+)/)
+ if(@bot.auth.allow?("auth", m.source, m.replyto))
+ @bot.auth.userdel($1)
+ m.reply "user #$1 is gone"
+ end
+ when (/^auth\s+(\S+)/)
+ if($1 == @bot.config["auth.password"])
+ @bot.auth.useradd(Regexp.escape(m.source), 1000)
+ m.reply "Identified, security level maxed out"
+ else
+ m.reply "incorrect password"
+ end
+ when ("levels")
+ m.reply @bot.auth.showlevels if(@bot.auth.allow?("config", m.source, m.replyto))
+ when ("users")
+ m.reply @bot.auth.showusers if(@bot.auth.allow?("config", m.source, m.replyto))
+ end
+ end
+ end
+ end
+end
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/auth.rb
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/auth.rb
------------------------------------------------------------------------------
svn:keywords = "Author Date Id Revision"
Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/channel.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/channel.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/channel.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/channel.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,54 @@
+module Irc
+
+ # class to store IRC channel data (users, topic, per-channel configurations)
+ class IRCChannel
+ # name of channel
+ attr_reader :name
+
+ # current channel topic
+ attr_reader :topic
+
+ # hash containing users currently in the channel
+ attr_accessor :users
+
+ # if true, bot won't talk in this channel
+ attr_accessor :quiet
+
+ # name:: channel name
+ # create a new IRCChannel
+ def initialize(name)
+ @name = name
+ @users = Hash.new
+ @quiet = false
+ @topic = Topic.new
+ end
+
+ # eg @bot.channels[chan].topic = topic
+ def topic=(name)
+ @topic.name = name
+ end
+
+ # class to store IRC channel topic information
+ class Topic
+ # topic name
+ attr_accessor :name
+
+ # timestamp
+ attr_accessor :timestamp
+
+ # topic set by
+ attr_accessor :by
+
+ def initialize
+ @name = ""
+ end
+
+ # when called like "puts @bots.channels[chan].topic"
+ def to_s
+ @name
+ end
+ end
+
+ end
+
+end
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/channel.rb
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/channel.rb
------------------------------------------------------------------------------
svn:keywords = "Author Date Id Revision"
Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/config.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/config.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/config.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/config.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,381 @@
+module Irc
+
+ require 'yaml'
+ require 'rbot/messagemapper'
+
+ unless YAML.respond_to?(:load_file)
+ def YAML.load_file( filepath )
+ File.open( filepath ) do |f|
+ YAML::load( f )
+ end
+ end
+ end
+
+ class BotConfigValue
+ # allow the definition order to be preserved so that sorting by
+ # definition order is possible. The BotConfigWizard does this to allow
+ # the :wizard questions to be in a sensible order.
+ @@order = 0
+ attr_reader :type
+ attr_reader :desc
+ attr_reader :key
+ attr_reader :wizard
+ attr_reader :requires_restart
+ attr_reader :order
+ def initialize(key, params)
+ unless key =~ /^.+\..+$/
+ raise ArgumentError,"key must be of the form 'module.name'"
+ end
+ @order = @@order
+ @@order += 1
+ @key = key
+ if params.has_key? :default
+ @default = params[:default]
+ else
+ @default = false
+ end
+ @desc = params[:desc]
+ @type = params[:type] || String
+ @on_change = params[:on_change]
+ @validate = params[:validate]
+ @wizard = params[:wizard]
+ @requires_restart = params[:requires_restart]
+ end
+ def default
+ if @default.instance_of?(Proc)
+ @default.call
+ else
+ @default
+ end
+ end
+ def get
+ return BotConfig.config[@key] if BotConfig.config.has_key?(@key)
+ return @default
+ end
+ alias :value :get
+ def set(value, on_change = true)
+ BotConfig.config[@key] = value
+ @on_change.call(BotConfig.bot, value) if on_change && @on_change
+ end
+ def unset
+ BotConfig.config.delete(@key)
+ end
+
+ # set string will raise ArgumentErrors on failed parse/validate
+ def set_string(string, on_change = true)
+ value = parse string
+ if validate value
+ set value, on_change
+ else
+ raise ArgumentError, "invalid value: #{string}"
+ end
+ end
+
+ # override this. the default will work for strings only
+ def parse(string)
+ string
+ end
+
+ def to_s
+ get.to_s
+ end
+
+ private
+ def validate(value)
+ return true unless @validate
+ if @validate.instance_of?(Proc)
+ return @validate.call(value)
+ elsif @validate.instance_of?(Regexp)
+ raise ArgumentError, "validation via Regexp only supported for strings!" unless value.instance_of? String
+ return @validate.match(value)
+ else
+ raise ArgumentError, "validation type #{@validate.class} not supported"
+ end
+ end
+ end
+
+ class BotConfigStringValue < BotConfigValue
+ end
+ class BotConfigBooleanValue < BotConfigValue
+ def parse(string)
+ return true if string == "true"
+ return false if string == "false"
+ raise ArgumentError, "#{string} does not match either 'true' or 'false'"
+ end
+ end
+ class BotConfigIntegerValue < BotConfigValue
+ def parse(string)
+ raise ArgumentError, "not an integer: #{string}" unless string =~ /^-?\d+$/
+ string.to_i
+ end
+ end
+ class BotConfigFloatValue < BotConfigValue
+ def parse(string)
+ raise ArgumentError, "not a float #{string}" unless string =~ /^-?[\d.]+$/
+ string.to_f
+ end
+ end
+ class BotConfigArrayValue < BotConfigValue
+ def parse(string)
+ string.split(/,\s+/)
+ end
+ def to_s
+ get.join(", ")
+ end
+ end
+ class BotConfigEnumValue < BotConfigValue
+ def initialize(key, params)
+ super
+ @values = params[:values]
+ end
+ def values
+ if @values.instance_of?(Proc)
+ return @values.call(BotConfig.bot)
+ else
+ return @values
+ end
+ end
+ def parse(string)
+ unless values.include?(string)
+ raise ArgumentError, "invalid value #{string}, allowed values are: " + @values.join(", ")
+ end
+ string
+ end
+ def desc
+ "#{@desc} [valid values are: " + values.join(", ") + "]"
+ end
+ end
+
+ # container for bot configuration
+ class BotConfig
+ # Array of registered BotConfigValues for defaults, types and help
+ @@items = Hash.new
+ def BotConfig.items
+ @@items
+ end
+ # Hash containing key => value pairs for lookup and serialisation
+ @@config = Hash.new(false)
+ def BotConfig.config
+ @@config
+ end
+ def BotConfig.bot
+ @@bot
+ end
+
+ def BotConfig.register(item)
+ unless item.kind_of?(BotConfigValue)
+ raise ArgumentError,"item must be a BotConfigValue"
+ end
+ @@items[item.key] = item
+ end
+
+ # currently we store values in a hash but this could be changed in the
+ # future. We use hash semantics, however.
+ # components that register their config keys and setup defaults are
+ # supported via []
+ def [](key)
+ return @@items[key].value if @@items.has_key?(key)
+ # try to still support unregistered lookups
+ return @@config[key] if @@config.has_key?(key)
+ return false
+ end
+
+ # TODO should I implement this via BotConfigValue or leave it direct?
+ # def []=(key, value)
+ # end
+
+ # pass everything else through to the hash
+ def method_missing(method, *args, &block)
+ return @@config.send(method, *args, &block)
+ end
+
+ def handle_list(m, params)
+ modules = []
+ if params[:module]
+ @@items.each_key do |key|
+ mod, name = key.split('.')
+ next unless mod == params[:module]
+ modules.push key unless modules.include?(name)
+ end
+ if modules.empty?
+ m.reply "no such module #{params[:module]}"
+ else
+ m.reply modules.join(", ")
+ end
+ else
+ @@items.each_key do |key|
+ name = key.split('.').first
+ modules.push name unless modules.include?(name)
+ end
+ m.reply "modules: " + modules.join(", ")
+ end
+ end
+
+ def handle_get(m, params)
+ key = params[:key]
+ unless @@items.has_key?(key)
+ m.reply "no such config key #{key}"
+ return
+ end
+ value = @@items[key].to_s
+ m.reply "#{key}: #{value}"
+ end
+
+ def handle_desc(m, params)
+ key = params[:key]
+ unless @@items.has_key?(key)
+ m.reply "no such config key #{key}"
+ end
+ puts @@items[key].inspect
+ m.reply "#{key}: #{@@items[key].desc}"
+ end
+
+ def handle_unset(m, params)
+ key = params[:key]
+ unless @@items.has_key?(key)
+ m.reply "no such config key #{key}"
+ end
+ @@items[key].unset
+ handle_get(m, params)
+ end
+
+ def handle_set(m, params)
+ key = params[:key]
+ value = params[:value].to_s
+ unless @@items.has_key?(key)
+ m.reply "no such config key #{key}"
+ return
+ end
+ begin
+ @@items[key].set_string(value)
+ rescue ArgumentError => e
+ m.reply "failed to set #{key}: #{e.message}"
+ return
+ end
+ if @@items[key].requires_restart
+ m.reply "this config change will take effect on the next restart"
+ else
+ m.okay
+ end
+ end
+
+ def handle_help(m, params)
+ topic = params[:topic]
+ case topic
+ when false
+ m.reply "config module - bot configuration. usage: list, desc, get, set, unset"
+ when "list"
+ m.reply "config list => list configuration modules, config list <module> => list configuration keys for module <module>"
+ when "get"
+ m.reply "config get <key> => get configuration value for key <key>"
+ when "unset"
+ m.reply "reset key <key> to the default"
+ when "set"
+ m.reply "config set <key> <value> => set configuration value for key <key> to <value>"
+ when "desc"
+ m.reply "config desc <key> => describe what key <key> configures"
+ else
+ m.reply "no help for config #{topic}"
+ end
+ end
+ def usage(m,params)
+ m.reply "incorrect usage, try '#{@@bot.nick}: help config'"
+ end
+
+ # bot:: parent bot class
+ # create a new config hash from #{botclass}/conf.rbot
+ def initialize(bot)
+ @@bot = bot
+
+ # respond to config messages, to provide runtime configuration
+ # management
+ # messages will be:
+ # get
+ # set
+ # unset
+ # desc
+ # and for arrays:
+ # add TODO
+ # remove TODO
+ @handler = MessageMapper.new(self)
+ @handler.map 'config list :module', :action => 'handle_list',
+ :defaults => {:module => false}
+ @handler.map 'config get :key', :action => 'handle_get'
+ @handler.map 'config desc :key', :action => 'handle_desc'
+ @handler.map 'config describe :key', :action => 'handle_desc'
+ @handler.map 'config set :key *value', :action => 'handle_set'
+ @handler.map 'config unset :key', :action => 'handle_unset'
+ @handler.map 'config help :topic', :action => 'handle_help',
+ :defaults => {:topic => false}
+ @handler.map 'help config :topic', :action => 'handle_help',
+ :defaults => {:topic => false}
+
+ if(File.exist?("#{@@bot.botclass}/conf.yaml"))
+ begin
+ newconfig = YAML::load_file("#{@@bot.botclass}/conf.yaml")
+ @@config.update newconfig
+ return
+ rescue
+ $stderr.puts "failed to read conf.yaml: #{$!}"
+ end
+ end
+ # if we got here, we need to run the first-run wizard
+ BotConfigWizard.new(@@bot).run
+ # save newly created config
+ save
+ end
+
+ # write current configuration to #{botclass}/conf.rbot
+ def save
+ begin
+ File.open("#{@@bot.botclass}/conf.yaml.new", "w") do |file|
+ file.puts @@config.to_yaml
+ end
+ File.rename("#{@@bot.botclass}/conf.yaml.new",
+ "#{@@bot.botclass}/conf.yaml")
+ rescue
+ $stderr.puts "failed to write configuration file conf.yaml! #{$!}"
+ end
+ end
+
+ def privmsg(m)
+ @handler.handle(m)
+ end
+ end
+
+ class BotConfigWizard
+ def initialize(bot)
+ @bot = bot
+ @questions = BotConfig.items.values.find_all {|i| i.wizard }
+ end
+
+ def run()
+ puts "First time rbot configuration wizard"
+ puts "===================================="
+ puts "This is the first time you have run rbot with a config directory of:"
+ puts @bot.botclass
+ puts "This wizard will ask you a few questions to get you started."
+ puts "The rest of rbot's configuration can be manipulated via IRC once"
+ puts "rbot is connected and you are auth'd."
+ puts "-----------------------------------"
+
+ return unless @questions
+ @questions.sort{|a,b| a.order <=> b.order }.each do |q|
+ puts q.desc
+ begin
+ print q.key + " [#{q.to_s}]: "
+ response = STDIN.gets
+ response.chop!
+ unless response.empty?
+ q.set_string response, false
+ end
+ puts "configured #{q.key} => #{q.to_s}"
+ puts "-----------------------------------"
+ rescue ArgumentError => e
+ puts "failed to set #{q.key}: #{e.message}"
+ retry
+ end
+ end
+ end
+ end
+end
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/config.rb
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/config.rb
------------------------------------------------------------------------------
svn:keywords = "Author Date Id Revision"
Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/dbhash.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/dbhash.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/dbhash.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/dbhash.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,112 @@
+begin
+ require 'bdb'
+rescue Exception => e
+ puts "Got exception: "+e
+ puts "rbot couldn't load the bdb module, perhaps you need to install it? try: http://www.ruby-lang.org/en/raa-list.rhtml?name=bdb"
+ exit 2
+end
+
+# make BTree lookups case insensitive
+module BDB
+ class CIBtree < Btree
+ def bdb_bt_compare(a, b)
+ a.downcase <=> b.downcase
+ end
+ end
+end
+
+module Irc
+
+ # DBHash is for tying a hash to disk (using bdb).
+ # Call it with an identifier, for example "mydata". It'll look for
+ # mydata.db, if it exists, it will load and reference that db.
+ # Otherwise it'll create and empty db called mydata.db
+ class DBHash
+
+ # absfilename:: use +key+ as an actual filename, don't prepend the bot's
+ # config path and don't append ".db"
+ def initialize(bot, key, absfilename=false)
+ @bot = bot
+ @key = key
+ if absfilename && File.exist?(key)
+ # db already exists, use it
+ @db = DBHash.open_db(key)
+ elsif File.exist?(@bot.botclass + "/#{key}.db")
+ # db already exists, use it
+ @db = DBHash.open_db(@bot.botclass + "/#{key}.db")
+ elsif absfilename
+ # create empty db
+ @db = DBHash.create_db(key)
+ else
+ # create empty db
+ @db = DBHash.create_db(@bot.botclass + "/#{key}.db")
+ end
+ end
+
+ def method_missing(method, *args, &block)
+ return @db.send(method, *args, &block)
+ end
+
+ def DBHash.create_db(name)
+ debug "DBHash: creating empty db #{name}"
+ return BDB::Hash.open(name, nil,
+ BDB::CREATE | BDB::EXCL | BDB::TRUNCATE,
+ 0600, "set_pagesize" => 1024,
+ "set_cachesize" => [(0), (32 * 1024), (0)])
+ end
+
+ def DBHash.open_db(name)
+ debug "DBHash: opening existing db #{name}"
+ return BDB::Hash.open(name, nil,
+ "r+", 0600, "set_pagesize" => 1024,
+ "set_cachesize" => [(0), (32 * 1024), (0)])
+ end
+
+ end
+
+
+ # DBTree is a BTree equivalent of DBHash, with case insensitive lookups.
+ class DBTree
+
+ # absfilename:: use +key+ as an actual filename, don't prepend the bot's
+ # config path and don't append ".db"
+ def initialize(bot, key, absfilename=false)
+ @bot = bot
+ @key = key
+ if absfilename && File.exist?(key)
+ # db already exists, use it
+ @db = DBTree.open_db(key)
+ elsif absfilename
+ # create empty db
+ @db = DBTree.create_db(key)
+ elsif File.exist?(@bot.botclass + "/#{key}.db")
+ # db already exists, use it
+ @db = DBTree.open_db(@bot.botclass + "/#{key}.db")
+ else
+ # create empty db
+ @db = DBTree.create_db(@bot.botclass + "/#{key}.db")
+ end
+ end
+
+ def method_missing(method, *args, &block)
+ return @db.send(method, *args, &block)
+ end
+
+ def DBTree.create_db(name)
+ debug "DBTree: creating empty db #{name}"
+ return BDB::CIBtree.open(name, nil,
+ BDB::CREATE | BDB::EXCL | BDB::TRUNCATE,
+ 0600, "set_pagesize" => 1024,
+ "set_cachesize" => [(0), (32 * 1024), (0)])
+ end
+
+ def DBTree.open_db(name)
+ debug "DBTree: opening existing db #{name}"
+ return BDB::CIBtree.open(name, nil,
+ "r+", 0600, "set_pagesize" => 1024,
+ "set_cachesize" => [0, 32 * 1024, 0])
+ end
+
+ end
+
+end
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/dbhash.rb
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/dbhash.rb
------------------------------------------------------------------------------
svn:keywords = "Author Date Id Revision"
Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/httputil.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/httputil.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/httputil.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/httputil.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,141 @@
+module Irc
+module Utils
+
+require 'resolv'
+require 'net/http'
+Net::HTTP.version_1_2
+
+# class for making http requests easier (mainly for plugins to use)
+# this class can check the bot proxy configuration to determine if a proxy
+# needs to be used, which includes support for per-url proxy configuration.
+class HttpUtil
+ BotConfig.register BotConfigBooleanValue.new('http.use_proxy',
+ :default => false, :desc => "should a proxy be used for HTTP requests?")
+ BotConfig.register BotConfigStringValue.new('http.proxy_uri', :default => false,
+ :desc => "Proxy server to use for HTTP requests (URI, e.g http://proxy.host:port)")
+ BotConfig.register BotConfigStringValue.new('http.proxy_user',
+ :default => nil,
+ :desc => "User for authenticating with the http proxy (if required)")
+ BotConfig.register BotConfigStringValue.new('http.proxy_pass',
+ :default => nil,
+ :desc => "Password for authenticating with the http proxy (if required)")
+ BotConfig.register BotConfigArrayValue.new('http.proxy_include',
+ :default => [],
+ :desc => "List of regexps to check against a URI's hostname/ip to see if we should use the proxy to access this URI. All URIs are proxied by default if the proxy is set, so this is only required to re-include URIs that might have been excluded by the exclude list. e.g. exclude /.*\.foo\.com/, include bar\.foo\.com")
+ BotConfig.register BotConfigArrayValue.new('http.proxy_exclude',
+ :default => [],
+ :desc => "List of regexps to check against a URI's hostname/ip to see if we should use avoid the proxy to access this URI and access it directly")
+
+ def initialize(bot)
+ @bot = bot
+ @headers = {
+ 'User-Agent' => "rbot http util #{$version} (http://linuxbrit.co.uk/rbot/)",
+ }
+ end
+
+ # if http_proxy_include or http_proxy_exclude are set, then examine the
+ # uri to see if this is a proxied uri
+ # the in/excludes are a list of regexps, and each regexp is checked against
+ # the server name, and its IP addresses
+ def proxy_required(uri)
+ use_proxy = true
+ if @bot.config["http.proxy_exclude"].empty? && @bot.config["http.proxy_include"].empty?
+ return use_proxy
+ end
+
+ list = [uri.host]
+ begin
+ list.concat Resolv.getaddresses(uri.host)
+ rescue StandardError => err
+ puts "warning: couldn't resolve host uri.host"
+ end
+
+ unless @bot.config["http.proxy_exclude"].empty?
+ re = @bot.config["http.proxy_exclude"].collect{|r| Regexp.new(r)}
+ re.each do |r|
+ list.each do |item|
+ if r.match(item)
+ use_proxy = false
+ break
+ end
+ end
+ end
+ end
+ unless @bot.config["http.proxy_include"].empty?
+ re = @bot.config["http.proxy_include"].collect{|r| Regexp.new(r)}
+ re.each do |r|
+ list.each do |item|
+ if r.match(item)
+ use_proxy = true
+ break
+ end
+ end
+ end
+ end
+ debug "using proxy for uri #{uri}?: #{use_proxy}"
+ return use_proxy
+ end
+
+ # uri:: Uri to create a proxy for
+ #
+ # return a net/http Proxy object, which is configured correctly for
+ # proxying based on the bot's proxy configuration.
+ # This will include per-url proxy configuration based on the bot config
+ # +http_proxy_include/exclude+ options.
+ def get_proxy(uri)
+ proxy = nil
+ proxy_host = nil
+ proxy_port = nil
+ proxy_user = nil
+ proxy_pass = nil
+
+ if @bot.config["http.use_proxy"]
+ if (ENV['http_proxy'])
+ proxy = URI.parse ENV['http_proxy'] rescue nil
+ end
+ if (@bot.config["http.proxy_uri"])
+ proxy = URI.parse @bot.config["http.proxy_uri"] rescue nil
+ end
+ if proxy
+ debug "proxy is set to #{proxy.uri}"
+ if proxy_required(uri)
+ proxy_host = proxy.host
+ proxy_port = proxy.port
+ proxy_user = @bot.config["http.proxy_user"]
+ proxy_pass = @bot.config["http.proxy_pass"]
+ end
+ end
+ end
+
+ return Net::HTTP.new(uri.host, uri.port, proxy_host, proxy_port, proxy_user, proxy_port)
+ end
+
+ # uri:: uri to query (Uri object)
+ # readtimeout:: timeout for reading the response
+ # opentimeout:: timeout for opening the connection
+ #
+ # simple get request, returns response body if the status code is 200 and
+ # the request doesn't timeout.
+ def get(uri, readtimeout=10, opentimeout=5)
+ proxy = get_proxy(uri)
+ proxy.open_timeout = opentimeout
+ proxy.read_timeout = readtimeout
+
+ begin
+ proxy.start() {|http|
+ resp = http.get(uri.request_uri(), @headers)
+ if resp.code == "200"
+ return resp.body
+ else
+ puts "HttpUtil.get return code #{resp.code} #{resp.body}"
+ end
+ return nil
+ }
+ rescue StandardError, Timeout::Error => e
+ $stderr.puts "HttpUtil.get exception: #{e}, while trying to get #{uri}"
+ end
+ return nil
+ end
+end
+end
+end
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/httputil.rb
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/httputil.rb
------------------------------------------------------------------------------
svn:keywords = "Author Date Id Revision"
Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/ircbot.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/ircbot.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/ircbot.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/ircbot.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,864 @@
+require 'thread'
+require 'etc'
+require 'fileutils'
+
+$debug = false unless $debug
+# print +message+ if debugging is enabled
+def debug(message=nil)
+ stamp = Time.now.strftime("%Y/%m/%d %H:%M:%S")
+ print "D: [#{stamp}] #{message}\n" if($debug && message)
+ #yield
+end
+
+# these first
+require 'rbot/rbotconfig'
+require 'rbot/config'
+require 'rbot/utils'
+
+require 'rbot/rfc2812'
+require 'rbot/keywords'
+require 'rbot/ircsocket'
+require 'rbot/auth'
+require 'rbot/timer'
+require 'rbot/plugins'
+require 'rbot/channel'
+require 'rbot/message'
+require 'rbot/language'
+require 'rbot/dbhash'
+require 'rbot/registry'
+require 'rbot/httputil'
+
+module Irc
+
+# Main bot class, which manages the various components, receives messages,
+# handles them or passes them to plugins, and contains core functionality.
+class IrcBot
+ # the bot's current nickname
+ attr_reader :nick
+
+ # the bot's IrcAuth data
+ attr_reader :auth
+
+ # the bot's BotConfig data
+ attr_reader :config
+
+ # the botclass for this bot (determines configdir among other things)
+ attr_reader :botclass
+
+ # used to perform actions periodically (saves configuration once per minute
+ # by default)
+ attr_reader :timer
+
+ # bot's Language data
+ attr_reader :lang
+
+ # channel info for channels the bot is in
+ attr_reader :channels
+
+ # bot's irc socket
+ attr_reader :socket
+
+ # bot's object registry, plugins get an interface to this for persistant
+ # storage (hash interface tied to a bdb file, plugins use Accessors to store
+ # and restore objects in their own namespaces.)
+ attr_reader :registry
+
+ # bot's httputil help object, for fetching resources via http. Sets up
+ # proxies etc as defined by the bot configuration/environment
+ attr_reader :httputil
+
+ # create a new IrcBot with botclass +botclass+
+ def initialize(botclass, datadir, params = {})
+ # BotConfig for the core bot
+ BotConfig.register BotConfigStringValue.new('server.name',
+ :default => "localhost", :requires_restart => true,
+ :desc => "What server should the bot connect to?",
+ :wizard => true)
+ BotConfig.register BotConfigIntegerValue.new('server.port',
+ :default => 6667, :type => :integer, :requires_restart => true,
+ :desc => "What port should the bot connect to?",
+ :validate => Proc.new {|v| v > 0}, :wizard => true)
+ BotConfig.register BotConfigStringValue.new('server.password',
+ :default => false, :requires_restart => true,
+ :desc => "Password for connecting to this server (if required)",
+ :wizard => true)
+ BotConfig.register BotConfigStringValue.new('server.bindhost',
+ :default => false, :requires_restart => true,
+ :desc => "Specific local host or IP for the bot to bind to (if required)",
+ :wizard => true)
+ BotConfig.register BotConfigIntegerValue.new('server.reconnect_wait',
+ :default => 5, :validate => Proc.new{|v| v >= 0},
+ :desc => "Seconds to wait before attempting to reconnect, on disconnect")
+ BotConfig.register BotConfigStringValue.new('irc.nick', :default => "rbot",
+ :desc => "IRC nickname the bot should attempt to use", :wizard => true,
+ :on_change => Proc.new{|bot, v| bot.sendq "NICK #{v}" })
+ BotConfig.register BotConfigStringValue.new('irc.user', :default => "rbot",
+ :requires_restart => true,
+ :desc => "local user the bot should appear to be", :wizard => true)
+ BotConfig.register BotConfigArrayValue.new('irc.join_channels',
+ :default => [], :wizard => true,
+ :desc => "What channels the bot should always join at startup. List multiple channels using commas to separate. If a channel requires a password, use a space after the channel name. e.g: '#chan1, #chan2, #secretchan secritpass, #chan3'")
+ BotConfig.register BotConfigIntegerValue.new('core.save_every',
+ :default => 60, :validate => Proc.new{|v| v >= 0},
+ # TODO change timer via on_change proc
+ :desc => "How often the bot should persist all configuration to disk (in case of a server crash, for example")
+ BotConfig.register BotConfigFloatValue.new('server.sendq_delay',
+ :default => 2.0, :validate => Proc.new{|v| v >= 0},
+ :desc => "(flood prevention) the delay between sending messages to the server (in seconds)",
+ :on_change => Proc.new {|bot, v| bot.socket.sendq_delay = v })
+ BotConfig.register BotConfigIntegerValue.new('server.sendq_burst',
+ :default => 4, :validate => Proc.new{|v| v >= 0},
+ :desc => "(flood prevention) max lines to burst to the server before throttling. Most ircd's allow bursts of up 5 lines, with non-burst limits of 512 bytes/2 seconds",
+ :on_change => Proc.new {|bot, v| bot.socket.sendq_burst = v })
+ BotConfig.register BotConfigIntegerValue.new('server.ping_timeout',
+ :default => 10, :validate => Proc.new{|v| v >= 0},
+ :on_change => Proc.new {|bot, v| bot.start_server_pings},
+ :desc => "reconnect if server doesn't respond to PING within this many seconds (set to 0 to disable)")
+
+ @argv = params[:argv]
+
+ #unless FileTest.directory? Config::datadir
+ # puts "data directory '#{Config::datadir}' not found, did you setup.rb?"
+ # exit 2
+ #end
+
+ botclass = "#{Etc.getpwuid(Process::Sys.geteuid)[:dir]}/.rbot" unless botclass
+ #botclass = "#{ENV['HOME']}/.rbot" unless botclass
+ @botclass = botclass.gsub(/\/$/, "")
+
+ unless FileTest.directory? botclass
+ puts "no #{botclass} directory found, creating from templates.."
+ if FileTest.exist? botclass
+ puts "Error: file #{botclass} exists but isn't a directory"
+ exit 2
+ end
+ FileUtils.cp_r Config::datadir+'/templates', botclass
+ end
+
+ Dir.mkdir("#{botclass}/logs") unless File.exist?("#{botclass}/logs")
+
+ @ping_timer = nil
+ @pong_timer = nil
+ @last_ping = nil
+ @startup_time = Time.new
+ @config = BotConfig.new(self)
+ @config['datadir'] = datadir
+ # TODO background self after botconfig has a chance to run wizard
+ @timer = Timer::Timer.new(1.0) # only need per-second granularity
+ @registry = BotRegistry.new self
+ @timer.add(@config['core.save_every']) { save } if @config['core.save_every']
+ @channels = Hash.new
+ @logs = Hash.new
+ @httputil = Utils::HttpUtil.new(self)
+ @lang = Language::Language.new(@config['core.language'],@config['datadir'])
+ @keywords = Keywords.new(self)
+ @auth = IrcAuth.new(self)
+
+ Dir.mkdir("#{botclass}/plugins") unless File.exist?("#{botclass}/plugins")
+ @plugins = Plugins::Plugins.new(self, ["#{botclass}/plugins"], @config['datadir'])
+
+ @socket = IrcSocket.new(@config['server.name'], @config['server.port'], @config['server.bindhost'], @config['server.sendq_delay'], @config['server.sendq_burst'])
+ @nick = @config['irc.nick']
+
+ @client = IrcClient.new
+ @client[:privmsg] = proc { |data|
+ message = PrivMessage.new(self, data[:source], data[:target], data[:message])
+ onprivmsg(message)
+ }
+ @client[:notice] = proc { |data|
+ message = NoticeMessage.new(self, data[:source], data[:target], data[:message])
+ # pass it off to plugins that want to hear everything
+ @plugins.delegate "listen", message
+ }
+ @client[:motd] = proc { |data|
+ data[:motd].each_line { |line|
+ log "MOTD: #{line}", "server"
+ }
+ }
+ @client[:nicktaken] = proc { |data|
+ nickchg "#{data[:nick]}_"
+ }
+ @client[:badnick] = proc {|data|
+ puts "WARNING, bad nick (#{data[:nick]})"
+ }
+ @client[:ping] = proc {|data|
+ # (jump the queue for pongs)
+ @socket.puts "PONG #{data[:pingid]}"
+ }
+ @client[:pong] = proc {|data|
+ @last_ping = nil
+ }
+ @client[:nick] = proc {|data|
+ sourcenick = data[:sourcenick]
+ nick = data[:nick]
+ m = NickMessage.new(self, data[:source], data[:sourcenick], data[:nick])
+ if(sourcenick == @nick)
+ debug "my nick is now #{nick}"
+ @nick = nick
+ end
+ @channels.each {|k,v|
+ if(v.users.has_key?(sourcenick))
+ log "@ #{sourcenick} is now known as #{nick}", k
+ v.users[nick] = v.users[sourcenick]
+ v.users.delete(sourcenick)
+ end
+ }
+ @plugins.delegate("listen", m)
+ @plugins.delegate("nick", m)
+ }
+ @client[:quit] = proc {|data|
+ source = data[:source]
+ sourcenick = data[:sourcenick]
+ sourceurl = data[:sourceaddress]
+ message = data[:message]
+ m = QuitMessage.new(self, data[:source], data[:sourcenick], data[:message])
+ if(data[:sourcenick] =~ /#{Regexp.escape(@nick)}/i)
+ else
+ @channels.each {|k,v|
+ if(v.users.has_key?(sourcenick))
+ log "@ Quit: #{sourcenick}: #{message}", k
+ v.users.delete(sourcenick)
+ end
+ }
+ end
+ @plugins.delegate("listen", m)
+ @plugins.delegate("quit", m)
+ }
+ @client[:mode] = proc {|data|
+ source = data[:source]
+ sourcenick = data[:sourcenick]
+ sourceurl = data[:sourceaddress]
+ channel = data[:channel]
+ targets = data[:targets]
+ modestring = data[:modestring]
+ log "@ Mode #{modestring} #{targets} by #{sourcenick}", channel
+ }
+ @client[:welcome] = proc {|data|
+ log "joined server #{data[:source]} as #{data[:nick]}", "server"
+ debug "I think my nick is #{@nick}, server thinks #{data[:nick]}"
+ if data[:nick] && data[:nick].length > 0
+ @nick = data[:nick]
+ end
+
+ @plugins.delegate("connect")
+
+ @config['irc.join_channels'].each {|c|
+ debug "autojoining channel #{c}"
+ if(c =~ /^(\S+)\s+(\S+)$/i)
+ join $1, $2
+ else
+ join c if(c)
+ end
+ }
+ }
+ @client[:join] = proc {|data|
+ m = JoinMessage.new(self, data[:source], data[:channel], data[:message])
+ onjoin(m)
+ }
+ @client[:part] = proc {|data|
+ m = PartMessage.new(self, data[:source], data[:channel], data[:message])
+ onpart(m)
+ }
+ @client[:kick] = proc {|data|
+ m = KickMessage.new(self, data[:source], data[:target],data[:channel],data[:message])
+ onkick(m)
+ }
+ @client[:invite] = proc {|data|
+ if(data[:target] =~ /^#{Regexp.escape(@nick)}$/i)
+ join data[:channel] if (@auth.allow?("join", data[:source], data[:sourcenick]))
+ end
+ }
+ @client[:changetopic] = proc {|data|
+ channel = data[:channel]
+ sourcenick = data[:sourcenick]
+ topic = data[:topic]
+ timestamp = data[:unixtime] || Time.now.to_i
+ if(sourcenick == @nick)
+ log "@ I set topic \"#{topic}\"", channel
+ else
+ log "@ #{sourcenick} set topic \"#{topic}\"", channel
+ end
+ m = TopicMessage.new(self, data[:source], data[:channel], timestamp, data[:topic])
+
+ ontopic(m)
+ @plugins.delegate("listen", m)
+ @plugins.delegate("topic", m)
+ }
+ @client[:topic] = @client[:topicinfo] = proc {|data|
+ channel = data[:channel]
+ m = TopicMessage.new(self, data[:source], data[:channel], data[:unixtime], data[:topic])
+ ontopic(m)
+ }
+ @client[:names] = proc {|data|
+ channel = data[:channel]
+ users = data[:users]
+ unless(@channels[channel])
+ puts "bug: got names for channel '#{channel}' I didn't think I was in\n"
+ exit 2
+ end
+ @channels[channel].users.clear
+ users.each {|u|
+ @channels[channel].users[u[0].sub(/^[@&~+]/, '')] = ["mode", u[1]]
+ }
+ }
+ @client[:unknown] = proc {|data|
+ #debug "UNKNOWN: #{data[:serverstring]}"
+ log data[:serverstring], ".unknown"
+ }
+ end
+
+ # connect the bot to IRC
+ def connect
+ begin
+ trap("SIGINT") { quit }
+ trap("SIGTERM") { quit }
+ trap("SIGHUP") { quit }
+ rescue
+ debug "failed to trap signals, probably running on windows?"
+ end
+ begin
+ @socket.connect
+ rescue => e
+ raise "failed to connect to IRC server at #{@config['server.name']} #{@config['server.port']}: " + e
+ end
+ @socket.puts "PASS " + @config['server.password'] if @config['server.password']
+ @socket.puts "NICK #{@nick}\nUSER #{@config['irc.user']} 4 #{@config['server.name']} :Ruby bot. (c) Tom Gilbert"
+ start_server_pings
+ end
+
+ # begin event handling loop
+ def mainloop
+ while true
+ begin
+ connect
+ @timer.start
+
+ while true
+ if @socket.select
+ break unless reply = @socket.gets
+ @client.process reply
+ end
+ end
+ # I despair of this. Some of my users get "connection reset by peer"
+ # exceptions that ARENT SocketError's. How am I supposed to handle
+ # that?
+ #rescue TimeoutError, SocketError => e
+ rescue SystemExit
+ exit 0
+ rescue Exception => e
+ puts "network exception: connection closed: #{e.inspect}"
+ puts e.backtrace.join("\n")
+ @socket.shutdown # now we reconnect
+ rescue => e
+ puts "unexpected exception: connection closed: #{e.inspect}"
+ puts e.backtrace.join("\n")
+ exit 2
+ end
+
+ puts "disconnected"
+ @last_ping = nil
+ @channels.clear
+ @socket.clearq
+
+ puts "waiting to reconnect"
+ sleep @config['server.reconnect_wait']
+ end
+ end
+
+ # type:: message type
+ # where:: message target
+ # message:: message text
+ # send message +message+ of type +type+ to target +where+
+ # Type can be PRIVMSG, NOTICE, etc, but those you should really use the
+ # relevant say() or notice() methods. This one should be used for IRCd
+ # extensions you want to use in modules.
+ def sendmsg(type, where, message)
+ # limit it 440 chars + CRLF.. so we have to split long lines
+ left = 440 - type.length - where.length - 3
+ begin
+ if(left >= message.length)
+ sendq("#{type} #{where} :#{message}")
+ log_sent(type, where, message)
+ return
+ end
+ line = message.slice!(0, left)
+ lastspace = line.rindex(/\s+/)
+ if(lastspace)
+ message = line.slice!(lastspace, line.length) + message
+ message.gsub!(/^\s+/, "")
+ end
+ sendq("#{type} #{where} :#{line}")
+ log_sent(type, where, line)
+ end while(message.length > 0)
+ end
+
+ # queue an arbitraty message for the server
+ def sendq(message="")
+ # temporary
+ @socket.queue(message)
+ end
+
+ # send a notice message to channel/nick +where+
+ def notice(where, message)
+ message.each_line { |line|
+ line.chomp!
+ next unless(line.length > 0)
+ sendmsg("NOTICE", where, line)
+ }
+ end
+
+ # say something (PRIVMSG) to channel/nick +where+
+ def say(where, message)
+ message.to_s.gsub(/[\r\n]+/, "\n").each_line { |line|
+ line.chomp!
+ next unless(line.length > 0)
+ unless((where =~ /^#/) && (@channels.has_key?(where) && @channels[where].quiet))
+ sendmsg("PRIVMSG", where, line)
+ end
+ }
+ end
+
+ # perform a CTCP action with message +message+ to channel/nick +where+
+ def action(where, message)
+ sendq("PRIVMSG #{where} :\001ACTION #{message}\001")
+ if(where =~ /^#/)
+ log "* #{@nick} #{message}", where
+ elsif (where =~ /^(\S*)!.*$/)
+ log "* #{@nick}[#{where}] #{message}", $1
+ else
+ log "* #{@nick}[#{where}] #{message}", where
+ end
+ end
+
+ # quick way to say "okay" (or equivalent) to +where+
+ def okay(where)
+ say where, @lang.get("okay")
+ end
+
+ # log message +message+ to a file determined by +where+. +where+ can be a
+ # channel name, or a nick for private message logging
+ def log(message, where="server")
+ message.chomp!
+ stamp = Time.now.strftime("%Y/%m/%d %H:%M:%S")
+ where.gsub!(/[:!?$*()\/\\<>|"']/, "_")
+ unless(@logs.has_key?(where))
+ @logs[where] = File.new("#{@botclass}/logs/#{where}", "a")
+ @logs[where].sync = true
+ end
+ @logs[where].puts "[#{stamp}] #{message}"
+ #debug "[#{stamp}] <#{where}> #{message}"
+ end
+
+ # set topic of channel +where+ to +topic+
+ def topic(where, topic)
+ sendq "TOPIC #{where} :#{topic}"
+ end
+
+ # disconnect from the server and cleanup all plugins and modules
+ def shutdown(message = nil)
+ begin
+ trap("SIGINT", "DEFAULT")
+ trap("SIGTERM", "DEFAULT")
+ trap("SIGHUP", "DEFAULT")
+ rescue
+ debug "failed to trap signals, probably running on windows?"
+ end
+ message = @lang.get("quit") if (message.nil? || message.empty?)
+ @socket.clearq
+ save
+ @plugins.cleanup
+ @channels.each_value {|v|
+ log "@ quit (#{message})", v.name
+ }
+ @registry.close
+ @socket.puts "QUIT :#{message}"
+ @socket.flush
+ @socket.shutdown
+ puts "rbot quit (#{message})"
+ end
+
+ # message:: optional IRC quit message
+ # quit IRC, shutdown the bot
+ def quit(message=nil)
+ begin
+ shutdown(message)
+ ensure
+ exit 0
+ end
+ end
+
+ # totally shutdown and respawn the bot
+ def restart(message = false)
+ msg = message ? message : "restarting, back in #{@config['server.reconnect_wait']}..."
+ shutdown(msg)
+ sleep @config['server.reconnect_wait']
+ # now we re-exec
+ exec($0, *@argv)
+ end
+
+ # call the save method for bot's config, keywords, auth and all plugins
+ def save
+ @registry.flush
+ @config.save
+ @keywords.save
+ @auth.save
+ @plugins.save
+ end
+
+ # call the rescan method for the bot's lang, keywords and all plugins
+ def rescan
+ @lang.rescan
+ @plugins.rescan
+ @keywords.rescan
+ end
+
+ # channel:: channel to join
+ # key:: optional channel key if channel is +s
+ # join a channel
+ def join(channel, key=nil)
+ if(key)
+ sendq "JOIN #{channel} :#{key}"
+ else
+ sendq "JOIN #{channel}"
+ end
+ end
+
+ # part a channel
+ def part(channel, message="")
+ sendq "PART #{channel} :#{message}"
+ end
+
+ # attempt to change bot's nick to +name+
+ def nickchg(name)
+ sendq "NICK #{name}"
+ end
+
+ # changing mode
+ def mode(channel, mode, target)
+ sendq "MODE #{channel} #{mode} #{target}"
+ 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
+ secs_up = Time.new - @startup_time
+ uptime = Utils.secs_to_string secs_up
+ return "Uptime #{uptime}, #{@plugins.length} plugins active, #{@registry.length} items stored in registry, #{@socket.lines_sent} lines sent, #{@socket.lines_received} received."
+ end
+
+ # we'll ping the server every 30 seconds or so, and expect a response
+ # before the next one come around..
+ def start_server_pings
+ @last_ping = nil
+ # stop existing timers if running
+ unless @ping_timer.nil?
+ @timer.remove @ping_timer
+ @ping_timer = nil
+ end
+ unless @pong_timer.nil?
+ @timer.remove @pong_timer
+ @pong_timer = nil
+ end
+ return unless @config['server.ping_timeout'] > 0
+ # we want to respond to a hung server within 30 secs or so
+ @ping_timer = @timer.add(30) {
+ @last_ping = Time.now
+ @socket.puts "PING :rbot"
+ }
+ @pong_timer = @timer.add(10) {
+ unless @last_ping.nil?
+ diff = Time.now - @last_ping
+ unless diff < @config['server.ping_timeout']
+ debug "no PONG from server for #{diff} seconds, reconnecting"
+ begin
+ @socket.shutdown
+ # TODO
+ # raise an exception to get back to the mainloop
+ rescue
+ debug "couldn't shutdown connection (already shutdown?)"
+ end
+ @last_ping = nil
+ end
+ end
+ }
+ end
+
+ 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. #{@nick} 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 #{@nick} for being good"
+ when "hello"
+ return "hello|hi|hey|yo [#{@nick}] => 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
+ if(m.action?)
+ if(m.private?)
+ log "* [#{m.sourcenick}(#{m.sourceaddress})] #{m.message}", m.sourcenick
+ else
+ log "* #{m.sourcenick} #{m.message}", m.target
+ end
+ else
+ if(m.public?)
+ log "<#{m.sourcenick}> #{m.message}", m.target
+ else
+ log "[#{m.sourcenick}(#{m.sourceaddress})] #{m.message}", m.sourcenick
+ end
+ end
+
+ # 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"
+ log "@ #{m.sourcenick} pinged me"
+ return
+ end
+
+ if(m.address?)
+ 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.okay
+ rescan
+ end
+ when (/^quiet$/i)
+ if(auth.allow?("talk", m.source, m.replyto))
+ m.okay
+ @channels.each_value {|c| c.quiet = true }
+ 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?
+ @channels[where].quiet = true if(@channels.has_key?(where))
+ end
+ when (/^talk$/i)
+ if(auth.allow?("talk", m.source, m.replyto))
+ @channels.each_value {|c| c.quiet = false }
+ 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?
+ @channels[where].quiet = false if(@channels.has_key?(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?)
+ else
+ delegate_privmsg(m)
+ 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(@nick)}$/i)
+ say m.replyto, @lang.get("hello_X") % m.sourcenick
+ when (/^#{Regexp.escape(@nick)}!*$/)
+ say m.replyto, @lang.get("hello_X") % m.sourcenick
+ else
+ @keywords.privmsg(m)
+ end
+ end
+ end
+
+ # log a message. Internal use only.
+ def log_sent(type, where, message)
+ case type
+ when "NOTICE"
+ if(where =~ /^#/)
+ log "-=#{@nick}=- #{message}", where
+ elsif (where =~ /(\S*)!.*/)
+ log "[-=#{where}=-] #{message}", $1
+ else
+ log "[-=#{where}=-] #{message}"
+ end
+ when "PRIVMSG"
+ if(where =~ /^#/)
+ log "<#{@nick}> #{message}", where
+ elsif (where =~ /^(\S*)!.*$/)
+ log "[msg(#{where})] #{message}", $1
+ else
+ log "[msg(#{where})] #{message}", where
+ end
+ end
+ end
+
+ def onjoin(m)
+ @channels[m.channel] = IRCChannel.new(m.channel) unless(@channels.has_key?(m.channel))
+ if(m.address?)
+ debug "joined channel #{m.channel}"
+ log "@ Joined channel #{m.channel}", m.channel
+ else
+ log "@ #{m.sourcenick} joined channel #{m.channel}", m.channel
+ @channels[m.channel].users[m.sourcenick] = Hash.new
+ @channels[m.channel].users[m.sourcenick]["mode"] = ""
+ end
+
+ @plugins.delegate("listen", m)
+ @plugins.delegate("join", m)
+ end
+
+ def onpart(m)
+ if(m.address?)
+ debug "left channel #{m.channel}"
+ log "@ Left channel #{m.channel} (#{m.message})", m.channel
+ @channels.delete(m.channel)
+ else
+ log "@ #{m.sourcenick} left channel #{m.channel} (#{m.message})", m.channel
+ @channels[m.channel].users.delete(m.sourcenick)
+ end
+
+ # delegate to plugins
+ @plugins.delegate("listen", m)
+ @plugins.delegate("part", m)
+ end
+
+ # respond to being kicked from a channel
+ def onkick(m)
+ if(m.address?)
+ debug "kicked from channel #{m.channel}"
+ @channels.delete(m.channel)
+ log "@ You have been kicked from #{m.channel} by #{m.sourcenick} (#{m.message})", m.channel
+ else
+ @channels[m.channel].users.delete(m.sourcenick)
+ log "@ #{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)
+ @channels[m.channel] = IRCChannel.new(m.channel) unless(@channels.has_key?(m.channel))
+ @channels[m.channel].topic = m.topic if !m.topic.nil?
+ @channels[m.channel].topic.timestamp = m.timestamp if !m.timestamp.nil?
+ @channels[m.channel].topic.by = m.source if !m.source.nil?
+
+ debug "topic of channel #{m.channel} is now #{@channels[m.channel].topic}"
+ 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
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/ircbot.rb
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/ircbot.rb
------------------------------------------------------------------------------
svn:keywords = "Author Date Id Revision"
Added: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/ircsocket.rb
URL: http://svn.apache.org/viewcvs/maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/ircsocket.rb?rev=357245&view=auto
==============================================================================
--- maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/ircsocket.rb (added)
+++ maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/ircsocket.rb Fri Dec 16 14:30:38 2005
@@ -0,0 +1,188 @@
+module Irc
+
+ require 'socket'
+ require 'thread'
+ require 'rbot/timer'
+
+ # wrapped TCPSocket for communication with the server.
+ # emulates a subset of TCPSocket functionality
+ class IrcSocket
+ # total number of lines sent to the irc server
+ attr_reader :lines_sent
+
+ # total number of lines received from the irc server
+ attr_reader :lines_received
+
+ # delay between lines sent
+ attr_reader :sendq_delay
+
+ # max lines to burst
+ attr_reader :sendq_burst
+
+ # server:: server to connect to
+ # port:: IRCd port
+ # host:: optional local host to bind to (ruby 1.7+ required)
+ # create a new IrcSocket
+ def initialize(server, port, host, sendq_delay=2, sendq_burst=4)
+ @timer = Timer::Timer.new
+ @timer.add(0.2) do
+ spool
+ end
+ @server = server.dup
+ @port = port.to_i
+ @host = host
+ @sock = nil
+ @spooler = false
+ @lines_sent = 0
+ @lines_received = 0
+ if sendq_delay
+ @sendq_delay = sendq_delay.to_f
+ else
+ @sendq_delay = 2
+ end
+ @last_send = Time.new - @sendq_delay
+ @burst = 0
+ if sendq_burst
+ @sendq_burst = sendq_burst.to_i
+ else
+ @sendq_burst = 4
+ end
+ end
+
+ # open a TCP connection to the server
+ def connect
+ @sock = nil
+ if(@host)
+ begin
+ @sock=TCPSocket.new(@server, @port, @host)
+ rescue ArgumentError => e
+ $stderr.puts "Your version of ruby does not support binding to a "
+ $stderr.puts "specific local address, please upgrade if you wish "
+ $stderr.puts "to use HOST = foo"
+ $stderr.puts "(this option has been disabled in order to continue)"
+ @sock=TCPSocket.new(@server, @port)
+ end
+ else
+ @sock=TCPSocket.new(@server, @port)
+ end
+ @qthread = false
+ @qmutex = Mutex.new
+ @sendq = Array.new
+ end
+
+ def sendq_delay=(newfreq)
+ debug "changing sendq frequency to #{newfreq}"
+ @qmutex.synchronize do
+ @sendq_delay = newfreq
+ if newfreq == 0
+ clearq
+ @timer.stop
+ else
+ @timer.start
+ end
+ end
+ end
+
+ def sendq_burst=(newburst)
+ @qmutex.synchronize do
+ @sendq_burst = newburst
+ end
+ end
+
+ # used to send lines to the remote IRCd
+ # message: IRC message to send
+ def puts(message)
+ @qmutex.synchronize do
+ # debug "In puts - got mutex"
+ puts_critical(message)
+ end
+ end
+
+ # get the next line from the server (blocks)
+ def gets
+ reply = @sock.gets
+ @lines_received += 1
+ reply.strip! if reply
+ debug "RECV: #{reply.inspect}"
+ reply
+ end
+
+ def queue(msg)
+ if @sendq_delay > 0
+ @qmutex.synchronize do
+ @sendq.push msg
+ end
+ @timer.start
+ else
+ # just send it if queueing is disabled
+ self.puts(msg)
+ end
+ end
+
+ # pop a message off the queue, send it
+ def spool
+ if @sendq.empty?
+ @timer.stop
+ return
+ end
+ now = Time.new
+ if (now >= (@last_send + @sendq_delay))
+ # reset burst counter after @sendq_delay has passed
+ @burst = 0
+ debug "in spool, resetting @burst"
+ elsif (@burst >= @sendq_burst)
+ # nope. can't send anything, come back to us next tick...
+ @timer.start
+ return
+ end
+ @qmutex.synchronize do
+ debug "(can send #{@sendq_burst - @burst} lines, there are #{@sendq.length} to send)"
+ (@sendq_burst - @burst).times do
+ break if @sendq.empty?
+ puts_critical(@sendq.shift)
+ end
+ end
+ if @sendq.empty?
+ @timer.stop
+ end
+ end
+
+ def clearq
+ unless @sendq.empty?
+ @qmutex.synchronize do
+ @sendq.clear
+ end
+ end
+ end
+
+ # flush the TCPSocket
+ def flush
+ @sock.flush
+ end
+
+ # Wraps Kernel.select on the socket
+ def select(timeout=nil)
+ Kernel.select([@sock], nil, nil, timeout)
+ end
+
+ # shutdown the connection to the server
+ def shutdown(how=2)
+ @sock.shutdown(how) unless @sock.nil?
+ @sock = nil
+ end
+
+ private
+
+ # same as puts, but expects to be called with a mutex held on @qmutex
+ def puts_critical(message)
+ # debug "in puts_critical"
+ debug "SEND: #{message.inspect}"
+ @sock.send(message + "\n",0)
+ @last_send = Time.new
+ @lines_sent += 1
+ @burst += 1
+ end
+
+ end
+
+end
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/ircsocket.rb
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: maven/sandbox/issue/rbot/dist/lib/site_ruby/1.8/rbot/ircsocket.rb
------------------------------------------------------------------------------
svn:keywords = "Author Date Id Revision"