From e7558ab501d89ed4d04ff69b58344aec8de50844 Mon Sep 17 00:00:00 2001 From: Tom Gilbert Date: Wed, 3 Aug 2005 23:12:39 +0000 Subject: Thu Aug 04 00:11:52 BST 2005 Tom Gilbert * Tweaked the debug() stuff a bit. Need to do this more cleanly really * Added a fair bit of documentation for some of the new features --- ChangeLog | 5 ++ Rakefile | 2 +- bin/rbot | 8 --- lib/rbot/dbhash.rb | 21 -------- lib/rbot/ircbot.rb | 10 ++++ lib/rbot/messagemapper.rb | 121 ++++++++++++++++++++++++++++++++++++++++------ lib/rbot/plugins.rb | 4 +- lib/rbot/registry.rb | 21 -------- 8 files changed, 125 insertions(+), 67 deletions(-) diff --git a/ChangeLog b/ChangeLog index 40ede6d6..92396298 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +Thu Aug 04 00:11:52 BST 2005 Tom Gilbert + + * Tweaked the debug() stuff a bit. Need to do this more cleanly really + * Added a fair bit of documentation for some of the new features + Wed Aug 03 15:25:07 BST 2005 Tom Gilbert * Added french language file (TODO most of the plugins just talk english) diff --git a/Rakefile b/Rakefile index 9f9157de..6e796512 100644 --- a/Rakefile +++ b/Rakefile @@ -16,7 +16,7 @@ spec = Gem::Specification.new do |s| s.requirements << 'Ruby, version 1.8.0 (or newer)' # s.files = Dir.glob("**/*").delete_if { |item| item.include?(".svn") } - s.files = FileList['lib/**/*.rb', 'bin/*', 'data/**/*', 'AUTHORS', 'COPYING', 'README', 'REQUIREMENTS', 'TODO', 'ChangeLog', 'INSTALL', 'rbot.gemspec', 'Usage_en.txt', 'setup.rb'].to_a + s.files = FileList['lib/**/*.rb', 'bin/*', 'data/**/*', 'AUTHORS', 'COPYING', 'README', 'REQUIREMENTS', 'TODO', 'ChangeLog', 'INSTALL', 'Usage_en.txt', 'setup.rb'].to_a s.executables << 'rbot' s.autorequire = 'rbot/ircbot' diff --git a/bin/rbot b/bin/rbot index a5c71492..d2863756 100755 --- a/bin/rbot +++ b/bin/rbot @@ -22,13 +22,6 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. $VERBOSE=true -$debug = false - -# print +message+ if debugging is enabled -def debug(message=nil) - print "DEBUG: #{message}\n" if($debug && message) - #yield -end require 'etc' require 'getoptlong' @@ -65,7 +58,6 @@ rescue LoadError => e puts "Error: couldn't find the rbot/ircbot module for loading\n - did you install rbot using install.rb?" exit 2 end - if ($opts["version"]) puts "rbot #{$version}" diff --git a/lib/rbot/dbhash.rb b/lib/rbot/dbhash.rb index 5ae2ba87..611ec087 100644 --- a/lib/rbot/dbhash.rb +++ b/lib/rbot/dbhash.rb @@ -1,24 +1,3 @@ -# 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. - begin require 'bdb' rescue Exception => e diff --git a/lib/rbot/ircbot.rb b/lib/rbot/ircbot.rb index 11b03f50..f99dd940 100644 --- a/lib/rbot/ircbot.rb +++ b/lib/rbot/ircbot.rb @@ -2,6 +2,13 @@ require 'thread' require 'etc' require 'fileutils' +$debug = false unless $debug +# print +message+ if debugging is enabled +def debug(message=nil) + print "DEBUG: #{message}\n" if($debug && message) + #yield +end + # these first require 'rbot/rbotconfig' require 'rbot/config' @@ -369,6 +376,7 @@ class IrcBot end while(message.length > 0) end + # queue an arbitraty message for the server def sendq(message="") # temporary @socket.queue(message) @@ -429,6 +437,7 @@ class IrcBot sendq "TOPIC #{where} :#{topic}" end + # disconnect from the server and cleanup all plugins and modules def shutdown(message = nil) trap("SIGTERM", "DEFAULT") trap("SIGHUP", "DEFAULT") @@ -539,6 +548,7 @@ class IrcBot 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 diff --git a/lib/rbot/messagemapper.rb b/lib/rbot/messagemapper.rb index c8e2b6ba..b079acd6 100644 --- a/lib/rbot/messagemapper.rb +++ b/lib/rbot/messagemapper.rb @@ -1,38 +1,131 @@ 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 - @routes = Array.new + @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) - @routes << Template.new(*args) + @templates << Template.new(*args) end def each - @routes.each {|route| yield route} + @templates.each {|tmpl| yield tmpl} end def last - @routes.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 @routes.empty? + return false if @templates.empty? failures = [] - @routes.each do |route| - options, failure = route.recognize(m) + @templates.each do |tmpl| + options, failure = tmpl.recognize(m) if options.nil? - failures << [route, failure] + failures << [tmpl, failure] else - action = route.options[:action] ? route.options[:action] : route.items[0] + action = tmpl.options[:action] ? tmpl.options[:action] : tmpl.items[0] next unless @parent.respond_to?(action) - auth = route.options[:auth] ? route.options[:auth] : route.items[0] + 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 "route found and auth'd: #{action.inspect} #{options.inspect}" + debug "template match found and auth'd: #{action.inspect} #{options.inspect}" @parent.send(action, m, options) return true end @@ -124,8 +217,8 @@ module Irc return nil, "Unused components were left: #{components.join '/'}" unless components.empty? - return nil, "route is not configured for private messages" if @options.has_key?(:private) && !@options[:private] && m.private? - return nil, "route is not configured for public messages" if @options.has_key?(:public) && !@options[:public] && !m.private? + 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 @@ -137,7 +230,7 @@ module Irc "<#{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 route's requirements + # 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 diff --git a/lib/rbot/plugins.rb b/lib/rbot/plugins.rb index d98630e1..4e618f61 100644 --- a/lib/rbot/plugins.rb +++ b/lib/rbot/plugins.rb @@ -8,8 +8,8 @@ module Plugins # # map(template, options):: # map is the new, cleaner way to respond to specific message formats - # without littering your plugin code with regexps - # examples: + # without littering your plugin code with regexps. examples: + # # plugin.map 'karmastats', :action => 'karma_stats' # # # while in the plugin... diff --git a/lib/rbot/registry.rb b/lib/rbot/registry.rb index cd78dcbf..5228d223 100644 --- a/lib/rbot/registry.rb +++ b/lib/rbot/registry.rb @@ -1,24 +1,3 @@ -# 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. - require 'rbot/dbhash' module Irc -- cgit v1.2.3