]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/commitdiff
sendmsg improvements: plugins can now choose what to do with overlong messages withou...
authorGiuseppe Bilotta <giuseppe.bilotta@gmail.com>
Mon, 5 Feb 2007 01:09:01 +0000 (01:09 +0000)
committerGiuseppe Bilotta <giuseppe.bilotta@gmail.com>
Mon, 5 Feb 2007 01:09:01 +0000 (01:09 +0000)
ChangeLog
lib/rbot/ircbot.rb
lib/rbot/message.rb

index ababc59d6ac1fe9a1c0c35eae418dcfebd4a0e06..b6df965e827d29e04893f31df639dcd7e644666a 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,26 @@
+2007-02-05  Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
+
+       * sendmsg improvements: the bot sendmsg() method and all the methods
+       that rely on it (say(), notice(), and message methods such as reply())
+       now accept an option hash to customize its behaviour.
+               * :newlines, which can be set to either :split or :join,
+               depending on whether newlines in messages should be used as
+               split-points, or should be be replaced by some string (defined
+               in the :join_with option)
+               * :max_lines, which determines the maximum number of lines to be
+               sent by each messages. Last line is truncated (see next
+               options). Set to nil to have no limits.
+               * :overlong, which determines the behaviour when overlong lines
+               are to be sent; possible values are :split or :truncate.
+               * If :overlong is set to :split, :split_at determines the
+               string/regexp to split at; default is /\s+/, other usual
+               choice could be /\s+\|\s+/.
+               * If :overlong is set to :split, the :purge_split option
+               determines whether the :split_at expression should be removed
+               from the next lines (defaults to true).
+               * If :overlong is set to :truncate, the value of the option
+               :truncate_text is replaced at the end of the truncated line
+
 2007-02-03  Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
 
        * Plugin message mapper: Enhancements to the :requirements option.
index 5cb9c5d84e0af15135041d38e44f479cb4694cac..956415f95476cd27d478c5ce62421191f666e53e 100644 (file)
@@ -555,6 +555,27 @@ class IrcBot
       #debug "UNKNOWN: #{data[:serverstring]}"
       irclog data[:serverstring], ".unknown"
     }
+
+    set_default_send_options
+  end
+
+  def set_default_send_options
+    # Default send options for NOTICE and PRIVMSG
+    # TODO document, for plugin writers
+    # TODO some of these options, like :truncate_text and :max_lines,
+    # should be made into config variables that trigger this routine on change
+    @default_send_options = {
+      :queue_channel => nil,      # use default queue channel
+      :queue_ring => nil,         # use default queue ring
+      :newlines => :split,        # or :join
+      :join_with => ' ',          # by default, use a single space
+      :max_lines => nil,          # maximum number of lines to send with a single command
+      :overlong => :split,        # or :truncate
+      # TODO an array of splitpoints would be preferrable for this option:
+      :split_at => /\s+/,         # by default, split overlong lines at whitespace
+      :purge_split => true,       # should the split string be removed?
+      :truncate_text => "#{Reverse}...#{Reverse}"  # text to be appened when truncating
+    }
   end
 
   # checks if we should be quiet on a channel
@@ -690,7 +711,43 @@ class IrcBot
   # 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, chan=nil, ring=0)
+  def sendmsg(type, where, original_message, options={})
+    opts = @default_send_options.merge(options)
+
+    # For starters, set up appropriate queue channels and rings
+    mchan = opts[:queue_channel]
+    mring = opts[:queue_ring]
+    if mchan
+      chan = mchan
+    else
+      chan = where
+    end
+    if mring
+      ring = mring
+    else
+      case where
+      when User
+        ring = 1
+      else
+        ring = 2
+      end
+    end
+
+    message = original_message.to_s.gsub(/[\r\n]+/, "\n")
+    case opts[:newlines]
+    when :join
+      lines = [message.gsub("\n", opts[:join_with])]
+    when :split
+      lines = Array.new
+      message.each_line { |line|
+        line.chomp!
+        next unless(line.length > 0)
+        lines << line
+      }
+    else
+      raise "Unknown :newlines option #{opts[:newlines]} while sending #{original_message.inspect}"
+    end
+
     # The IRC protocol requires that each raw message must be not longer
     # than 512 characters. From this length with have to subtract the EOL
     # terminators (CR+LF) and the length of ":botnick!botuser@bothost "
@@ -713,21 +770,55 @@ class IrcBot
 
     # And this is what's left
     left = max_len - fixed.length
-    begin
-      if(left >= message.length)
-        sendq "#{fixed}#{message}", chan, ring
-        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 "#{fixed}#{line}", chan, ring
-      log_sent(type, where, line)
-    end while(message.length > 0)
+
+    case opts[:overlong]
+    when :split
+      truncate = false
+      split_at = opts[:split_at]
+    when :truncate
+      truncate = opts[:truncate_text]
+      truncate = @default_send_options[:truncate_text] if truncate.length > left
+      truncate = "" if truncate.length > left
+    else
+      raise "Unknown :overlong option #{opts[:overlong]} while sending #{original_message.inspect}"
+    end
+
+    # Counter to check the number of lines sent by this command
+    cmd_lines = 0
+    max_lines = opts[:max_lines]
+    line = String.new
+    lines.each { |msg|
+      begin
+        if(left >= msg.length)
+          sendq "#{fixed}#{msg}", chan, ring
+          log_sent(type, where, msg)
+          return
+        end
+        if opts[:max_lines] and cmd_lines == max_lines - 1
+          debug "Max lines count reached for message #{original_message.inspect} while sending #{msg.inspect}, truncating"
+          truncate = opts[:truncate_text]
+          truncate = @default_send_options[:truncate_text] if truncate.length > left
+          truncate = "" if truncate.length > left
+        end
+        if truncate
+          line.replace msg.slice(0, left-truncate.length)
+          line.sub!(/\s+\S*$/, truncate)
+          raise "PROGRAMMER ERROR! #{line.inspect} of length #{line.length} > #{left}" if line.length > left
+          sendq "#{fixed}#{line}", chan, ring
+          log_sent(type, where, line)
+          return
+        end
+        line.replace msg.slice!(0, left)
+        lastspace = line.rindex(opts[:split_at])
+        if(lastspace)
+          msg.replace line.slice!(lastspace, line.length) + msg
+          msg.gsub!(/^#{opts[:split_at]}/, "") if opts[:purge_split]
+        end
+        sendq "#{fixed}#{line}", chan, ring
+        log_sent(type, where, line)
+       cmd_lines += 1
+      end while(msg.length > 0)
+    }
   end
 
   # queue an arbitraty message for the server
@@ -737,72 +828,39 @@ class IrcBot
   end
 
   # send a notice message to channel/nick +where+
-  def notice(where, message, mchan="", mring=-1)
-    if mchan == ""
-      chan = where
-    else
-      chan = mchan
-    end
-    if mring < 0
-      case where
-      when User
-        ring = 1
-      else
-        ring = 2
-      end
-    else
-      ring = mring
+  def notice(where, message, options={})
+    unless quiet_on?(where)
+      sendmsg "NOTICE", where, message, options
     end
-    message.each_line { |line|
-      line.chomp!
-      next unless(line.length > 0)
-      sendmsg "NOTICE", where, line, chan, ring
-    }
   end
 
   # say something (PRIVMSG) to channel/nick +where+
-  def say(where, message, mchan="", mring=-1)
-    if mchan == ""
-      chan = where
-    else
-      chan = mchan
-    end
-    if mring < 0
-      case where
-      when User
-        ring = 1
-      else
-        ring = 2
-      end
-    else
-      ring = mring
+  def say(where, message, options={})
+    unless quiet_on?(where)
+      sendmsg "PRIVMSG", where, message, options
     end
-    message.to_s.gsub(/[\r\n]+/, "\n").each_line { |line|
-      line.chomp!
-      next unless(line.length > 0)
-      unless quiet_on?(where)
-        sendmsg "PRIVMSG", where, line, chan, ring 
-      end
-    }
   end
 
   # perform a CTCP action with message +message+ to channel/nick +where+
-  def action(where, message, mchan="", mring=-1)
-    if mchan == ""
-      chan = where
-    else
+  def action(where, message, options={})
+    mchan = options.fetch(:queue_channel, nil)
+    mring = options.fetch(:queue_ring, nil)
+    if mchan
       chan = mchan
+    else
+      chan = where
     end
-    if mring < 0
+    if mring
+      ring = mring
+    else
       case where
-      when Channel
-        ring = 2
-      else
+      when User
         ring = 1
+      else
+        ring = 2
       end
-    else
-      ring = mring
     end
+    # FIXME doesn't check message length. Can we make this exploit sendmsg?
     sendq "PRIVMSG #{where} :\001ACTION #{message}\001", chan, ring
     case where
     when Channel
index df5ac8d3d211462f06134814e4f9a99332157ee8..62ba746921c5d3ad9c3be29fbb2deaa32011a8b8 100644 (file)
@@ -214,27 +214,27 @@ module Irc
     # <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 plainreply(string)
-      @bot.say @replyto, string
+    def plainreply(string, options={})
+      @bot.say @replyto, string, options
       @replied = true
     end
 
     # Same as reply, but when replying in public it adds the nick of the user
     # the bot is replying to
-    def nickreply(string)
+    def nickreply(string, options={})
       extra = self.public? ? "#{@source}#{@bot.config['core.nick_postfix']} " : ""
-      @bot.say @replyto, extra + string
+      @bot.say @replyto, extra + string, options
       @replied = true
     end
 
     # the default reply style is to nickreply unless the reply already contains
     # the nick or core.reply_with_nick is set to false
     #
-    def reply(string)
+    def reply(string, options={})
       if @bot.config['core.reply_with_nick'] and not string =~ /\b#{@source}\b/
-        return nickreply(string)
+        return nickreply(string, options)
       end
-      plainreply(string)
+      plainreply(string, options)
     end
 
     # convenience method to reply to a message with an action. It's the
@@ -242,8 +242,8 @@ module Irc
     # <tt>@bot.action 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 act(string)
-      @bot.action @replyto, string
+    def act(string, options={})
+      @bot.action @replyto, string, options
       @replied = true
     end