]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blobdiff - lib/rbot/messagemapper.rb
message: add #thanks method, similar to okay
[user/henk/code/ruby/rbot.git] / lib / rbot / messagemapper.rb
index 1162a3a2126cb433bdf6e3f10fd223c532546c73..3966bc17f7360ec3853cda6995c479d3ab9e7590 100644 (file)
@@ -52,6 +52,57 @@ class Bot
   #   {:option => "bar", :otheroption => "baz"}
   # See the #map method for more details.
   class MessageMapper
+
+    class Failure
+      STRING   = "template %{template} failed to recognize message %{message}"
+      FRIENDLY = "I failed to understand the command"
+      attr_reader :template
+      attr_reader :message
+      def initialize(tmpl, msg)
+        @template = tmpl
+        @message = msg
+      end
+
+      def to_s
+        STRING % {
+          :template => template.template,
+          :regexp => template.regexp,
+          :message => message.message,
+          :action => template.options[:action]
+        }
+      end
+    end
+
+    # failures with a friendly message
+    class FriendlyFailure < Failure
+      def friendly
+        self.class::FRIENDLY rescue FRIENDLY
+      end
+    end
+
+    class NotPrivateFailure < FriendlyFailure
+      STRING   = "template %{template} is not configured for private messages"
+      FRIENDLY = "the command must not be given in private"
+    end
+
+    class NotPublicFailure < FriendlyFailure
+      STRING   = "template %{template} is not configured for public messages"
+      FRIENDLY = "the command must not be given in public"
+    end
+
+    class NoMatchFailure < Failure
+      STRING = "%{message} does not match %{template} (%{regex})"
+    end
+
+    class PartialMatchFailure < Failure
+      STRING = "%{message} only matches %{template} (%{regex}) partially"
+    end
+
+    class NoActionFailure < FriendlyFailure
+      STRING   = "%{template} calls undefined action %{action}"
+      FRIENDLY = "uh-ho, somebody forgot to tell me how to do that ..."
+    end
+
     # used to set the method name used as a fallback for unmatched messages.
     # The default fallback is a method called "usage".
     attr_writer :fallback
@@ -148,12 +199,12 @@ class Bot
     #
     # Further examples:
     #
-    #   # match 'karmastats' and call my stats() method
-    #   plugin.map 'karmastats', :action => 'stats'
-    #   # match 'karma' with an optional 'key' and call my karma() method
-    #   plugin.map 'karma :key', :defaults => {:key => false}
-    #   # match 'karma for something' and call my karma() method
-    #   plugin.map 'karma for :key'
+    #   # match 'pointstats' and call my stats() method
+    #   plugin.map 'pointstats', :action => 'stats'
+    #   # match 'points' with an optional 'key' and call my points() method
+    #   plugin.map 'points :key', :defaults => {:key => false}
+    #   # match 'points for something' and call my points() method
+    #   plugin.map 'points for :key'
     #
     #   # two matches, one for public messages in a channel, one for
     #   # private messages which therefore require a channel argument
@@ -194,30 +245,40 @@ class Bot
       return false if @templates.empty?
       failures = []
       @templates.each do |tmpl|
-        options, failure = tmpl.recognize(m)
-        if options.nil?
-          failures << [tmpl, failure]
+        params = tmpl.recognize(m)
+        if params.kind_of? Failure
+          failures << params
         else
           action = tmpl.options[:action]
           unless @parent.respond_to?(action)
-            failures << [tmpl, "class does not respond to action #{action}"]
+            failures << NoActionFailure.new(tmpl, m)
             next
           end
           auth = tmpl.options[:full_auth_path]
           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}"
-            if !m.in_thread && (tmpl.options[:thread] || tmpl.options[:threaded])
+            debug "template match found and auth'd: #{action.inspect} #{params.inspect}"
+            if !m.in_thread and (tmpl.options[:thread] or tmpl.options[:threaded]) and
+                (defined? WebServiceUser and not m.source.instance_of? WebServiceUser)
+              # Web service: requests are handled threaded anyway and we want to 
+              # wait for the responses.
+
+              # since the message action is in a separate thread, the message may be
+              # delegated to unreplied() before the thread has a chance to actually
+              # mark it as replied. since threading is used mostly for commands that
+              # are known to take some processing time (e.g. download a web page) before
+              # giving an output, we just mark these as 'replied' here
+              m.replied = true
               Thread.new do
                 begin
-                  @parent.send(action, m, options)
+                  @parent.send(action, m, params)
                 rescue Exception => e
                   error "In threaded action: #{e.message}"
                   debug e.backtrace.join("\n")
                 end
               end
             else
-              @parent.send(action, m, options)
+              @parent.send(action, m, params)
             end
 
             return true
@@ -228,13 +289,13 @@ class Bot
           return false
         end
       end
-      failures.each {|f, r|
-        debug "#{f.inspect} => #{r}"
+      failures.each {|r|
+        debug "#{r.template.inspect} => #{r}"
       }
       debug "no handler found, trying fallback"
       if @fallback && @parent.respond_to?(@fallback)
         if m.bot.auth.allow?(@fallback, m.source, m.replyto)
-          @parent.send(@fallback, m, {})
+          @parent.send(@fallback, m, {:failures => failures})
           return true
         end
       end
@@ -333,7 +394,7 @@ class Bot
       mul = multi? ? " multi" : " single"
       opt = optional? ? " optional" : " needed"
       if @regexp
-        reg = " regexp=%s index=%d" % [@regexp, @index]
+        reg = " regexp=%s index=%s" % [@regexp, @index]
       else
         reg = nil
       end
@@ -531,20 +592,19 @@ class Bot
 
       debug "Testing #{m.message.inspect} against #{self.inspect}"
 
-      # Early out
-      return nil, "template #{@template} is not configured for private messages" if @options.has_key?(:private) && !@options[:private] && m.private?
-      return nil, "template #{@template} is not configured for public messages" if @options.has_key?(:public) && !@options[:public] && !m.private?
-
-      options = {}
-
       matching = @regexp.match(m.message)
-      return nil, "#{m.message.inspect} doesn't match #{@template} (#{@regexp})" unless matching
-      return nil, "#{m.message.inspect} only matches #{@template} (#{@regexp}) partially: #{matching[0].inspect}" unless matching[0] == m.message
+      return MessageMapper::NoMatchFailure.new(self, m) unless matching
+      return MessageMapper::PartialMatchFailure.new(self, m) unless matching[0] == m.message
+
+      return MessageMapper::NotPrivateFailure.new(self, m) if @options.has_key?(:private) && !@options[:private] && m.private?
+      return MessageMapper::NotPublicFailure.new(self, m) if @options.has_key?(:public) && !@options[:public] && !m.private?
 
       debug_match = matching[1..-1].collect{ |d| d.inspect}.join(', ')
       debug "#{m.message.inspect} matched #{@regexp} with #{debug_match}"
       debug "Associating #{debug_match} with dyn items #{@dyn_items.join(', ')}"
 
+      options = @defaults.dup
+
       @dyn_items.each_with_index { |it, i|
         next if i == 0
         item = it.name
@@ -589,7 +649,7 @@ class Bot
       }
 
       options.delete_if {|k, v| v.nil?} # Remove nil values.
-      return options, nil
+      return options
     end
 
     def inspect