From: Giuseppe Bilotta Date: Mon, 5 Feb 2007 01:09:01 +0000 (+0000) Subject: sendmsg improvements: plugins can now choose what to do with overlong messages withou... X-Git-Url: https://git.netwichtig.de/gitweb/?a=commitdiff_plain;h=7f4e98a691ba6ee6f220fec982f17c900c929f1d;p=user%2Fhenk%2Fcode%2Fruby%2Frbot.git sendmsg improvements: plugins can now choose what to do with overlong messages without having to resort to custom solutions --- diff --git a/ChangeLog b/ChangeLog index ababc59d..b6df965e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,26 @@ +2007-02-05 Giuseppe Bilotta + + * 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 * Plugin message mapper: Enhancements to the :requirements option. diff --git a/lib/rbot/ircbot.rb b/lib/rbot/ircbot.rb index 5cb9c5d8..956415f9 100644 --- a/lib/rbot/ircbot.rb +++ b/lib/rbot/ircbot.rb @@ -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 diff --git a/lib/rbot/message.rb b/lib/rbot/message.rb index df5ac8d3..62ba7469 100644 --- a/lib/rbot/message.rb +++ b/lib/rbot/message.rb @@ -214,27 +214,27 @@ module Irc # @bot.say m.replyto, string # 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 # @bot.action m.replyto, string # 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