4 # :title: IRC message datastructures
11 Config.register ArrayValue.new('core.address_prefix',
12 :default => [], :wizard => true,
13 :desc => "what non nick-matching prefixes should the bot respond to as if addressed (e.g !, so that '!foo' is treated like 'rbot: foo')"
16 Config.register BooleanValue.new('core.reply_with_nick',
17 :default => false, :wizard => true,
18 :desc => "if true, the bot will prepend the nick to what he has to say when replying (e.g. 'markey: you can't do that!')"
21 Config.register StringValue.new('core.nick_postfix',
22 :default => ':', :wizard => true,
23 :desc => "when replying with nick put this character after the nick of the user the bot is replying to"
29 # Define standard IRC attriubtes (not so standard actually,
30 # but the closest thing we have ...)
36 AttributeRx = /#{Bold}|#{Underline}|#{Reverse}|#{Italic}|#{NormalText}/
38 # Color is prefixed by \003 and followed by optional
39 # foreground and background specifications, two-digits-max
40 # numbers separated by a comma. One of the two parts
43 ColorRx = /#{Color}\d?\d?(?:,\d\d?)?/
45 # Standard color codes
72 # Convert a String or Symbol into a color number
73 def Irc.find_color(data)
74 "%02d" % if Integer === data
77 f = if String === data
90 # Insert the full color code for a given
91 # foreground/background combination.
92 def Irc.color(fg=nil,bg=nil)
95 str << Irc.find_color(fg)
98 str << "," << Irc.find_color(bg)
103 # base user message class, all user messages derive from this
104 # (a user message is defined as having a source hostmask, a target
105 # nick/channel and a message part)
106 class BasicUserMessage
114 # when the message was received
117 # User that originated the message
120 # User/Channel message was sent to
123 # contents of the message
124 attr_accessor :message
126 # contents of the message (for logging purposes)
127 attr_accessor :logmessage
129 # has the message been replied to/handled by a plugin?
130 attr_accessor :replied
132 # should the message be ignored?
133 attr_accessor :ignored
134 alias :ignored? :ignored
136 # set this to true if the method that delegates the message is run in a thread
137 attr_accessor :in_thread
138 alias :in_thread? :in_thread
140 # instantiate a new Message
141 # bot:: associated bot class
142 # server:: Server where the message took place
143 # source:: User that sent the message
144 # target:: User/Channel is destined for
145 # message:: actual message
146 def initialize(bot, server, source, target, message)
147 @msg_wants_id = false unless defined? @msg_wants_id
154 @message = BasicUserMessage.stripcolour message
161 if @msg_wants_id && @server.capabilities[:"identify-msg"]
162 if @message =~ /^([-+])(.*)/
163 @identified = ($1=="+")
166 warning "Message does not have identification"
169 @logmessage = @message.dup
171 if target && target == @bot.myself
177 # Access the nick of the source
180 @source.nick rescue @source.to_s
183 # Access the user@host of the source
186 "#{@source.user}@#{@source.host}" rescue @source.to_s
189 # Access the botuser corresponding to the source, if any
192 source.botuser rescue @bot.auth.everyone
196 # Was the message from an identified user?
201 # returns true if the message was addressed to the bot.
202 # This includes any private message to the bot, or any public message
203 # which looks like it's addressed to the bot, e.g. "bot: foo", "bot, foo",
204 # a kick message when bot was kicked etc.
209 # has this message been replied to by a plugin?
214 # strip mIRC colour escapes from a string
215 def BasicUserMessage.stripcolour(string)
216 return "" unless string
217 ret = string.gsub(ColorRx, "")
218 #ret.tr!("\x00-\x1f", "")
224 # class for handling welcome messages from the server
225 class WelcomeMessage < BasicUserMessage
228 # class for handling MOTD from the server. Yes, MotdMessage
229 # is somewhat redundant, but it fits with the naming scheme
230 class MotdMessage < BasicUserMessage
233 # class for handling IRC user messages. Includes some utilities for handling
234 # the message, for example in plugins.
235 # The +message+ member will have any bot addressing "^bot: " removed
236 # (address? will return true in this case)
237 class UserMessage < BasicUserMessage
239 # for plugin messages, the name of the plugin invoked by the message
242 # for plugin messages, the rest of the message, with the plugin name
246 # convenience member. Who to reply to (i.e. would be sourcenick for a
247 # privately addressed message, or target (the channel) for a publicly
251 # channel the message was in, nil for privately addressed messages
254 # for PRIVMSGs, false unless the message was a CTCP command,
255 # in which case it evaluates to the CTCP command itself
256 # (TIME, PING, VERSION, etc). The CTCP command parameters
257 # are then stored in the message.
260 # for PRIVMSGs, true if the message was a CTCP ACTION (CTCP stuff
261 # will be stripped from the message)
264 # instantiate a new UserMessage
265 # bot:: associated bot class
266 # source:: hostmask of the message source
267 # target:: nick/channel message is destined for
268 # message:: message part
269 def initialize(bot, server, source, target, message)
270 super(bot, server, source, target, message)
277 if target == @bot.myself
287 # check for option extra addressing prefixes, e.g "|search foo", or
288 # "!version" - first match wins
289 bot.config['core.address_prefix'].each {|mprefix|
290 if @message.gsub!(/^#{Regexp.escape(mprefix)}\s*/, "")
296 # even if they used above prefixes, we allow for silly people who
297 # combine all possible types, e.g. "|rbot: hello", or
298 # "/msg rbot rbot: hello", etc
299 if @message.gsub!(/^\s*#{Regexp.escape(bot.nick)}\s*([:;,>]|\s)\s*/i, "")
303 if(@message =~ /^\001(\S+)(\s(.+))?\001/)
305 # FIXME need to support quoting of NULL and CR/LF, see
306 # http://www.irchelp.org/irchelp/rfc/ctcpspec.html
307 @message = $3 || String.new
308 @action = @ctcp == 'ACTION'
309 debug "Received CTCP command #{@ctcp} with options #{@message} (action? #{@action})"
310 @logmessage = @message.dup
313 # free splitting for plugins
314 @params = @message.dup
315 if @params.gsub!(/^\s*(\S+)[\s$]*/, "")
316 @plugin = $1.downcase
317 @params = nil unless @params.length > 0
321 # returns true for private messages, e.g. "/msg bot hello"
326 # returns true if the message was in a channel
335 # convenience method to reply to a message, useful in plugins. It's the
337 # <tt>@bot.say m.replyto, string</tt>
338 # So if the message is private, it will reply to the user. If it was
339 # in a channel, it will reply in the channel.
340 def plainreply(string, options={})
341 @bot.say @replyto, string, options
345 # Same as reply, but when replying in public it adds the nick of the user
346 # the bot is replying to
347 def nickreply(string, options={})
348 extra = self.public? ? "#{@source}#{@bot.config['core.nick_postfix']} " : ""
349 @bot.say @replyto, extra + string, options
353 # the default reply style is to nickreply unless the reply already contains
354 # the nick or core.reply_with_nick is set to false
356 def reply(string, options={})
357 if @bot.config['core.reply_with_nick'] and not string =~ /(?:^|\W)#{Regexp.escape(@source.to_s)}(?:$|\W)/
358 return nickreply(string, options)
360 plainreply(string, options)
363 # convenience method to reply to a message with an action. It's the
365 # <tt>@bot.action m.replyto, string</tt>
366 # So if the message is private, it will reply to the user. If it was
367 # in a channel, it will reply in the channel.
368 def act(string, options={})
369 @bot.action @replyto, string, options
373 # send a CTCP response, i.e. a private NOTICE to the sender
374 # with the same CTCP command and the reply as a parameter
375 def ctcp_reply(string, options={})
376 @bot.ctcp_notice @source, @ctcp, string, options
379 # convenience method to reply "okay" in the current language to the
382 self.plainreply @bot.lang.get("okay")
385 # Like the above, but append the username
387 str = @bot.lang.get("okay").dup
389 # remove final punctuation
390 str.gsub!(/[!,.]$/,"")
391 str += ", #{@source}"
396 # the default okay style is the same as the default reply style
399 if @bot.config['core.reply_with_nick']
405 # send a NOTICE to the message source
407 def notify(msg,opts={})
408 @bot.notice(sourcenick, msg, opts)
413 # class to manage IRC PRIVMSGs
414 class PrivMessage < UserMessage
415 def initialize(bot, server, source, target, message)
421 # class to manage IRC NOTICEs
422 class NoticeMessage < UserMessage
423 def initialize(bot, server, source, target, message)
429 # class to manage IRC KICKs
430 # +address?+ can be used as a shortcut to see if the bot was kicked,
431 # basically, +target+ was kicked from +channel+ by +source+ with +message+
432 class KickMessage < BasicUserMessage
433 # channel user was kicked from
436 def initialize(bot, server, source, target, channel, message="")
437 super(bot, server, source, target, message)
442 # class to manage IRC INVITEs
443 # +address?+ can be used as a shortcut to see if the bot was invited,
444 # which should be true except for server bugs
445 class InviteMessage < BasicUserMessage
446 # channel user was invited to
449 def initialize(bot, server, source, target, channel, message="")
450 super(bot, server, source, target, message)
455 # class to pass IRC Nick changes in. @message contains the old nickame,
456 # @sourcenick contains the new one.
457 class NickMessage < BasicUserMessage
459 def initialize(bot, server, source, oldnick, newnick)
460 super(bot, server, source, oldnick, newnick)
473 # class to manage mode changes
474 class ModeChangeMessage < BasicUserMessage
476 def initialize(bot, server, source, target, message="")
477 super(bot, server, source, target, message)
478 @address = (source == @bot.myself)
483 # class to manage NAME replies
484 class NamesMessage < BasicUserMessage
486 def initialize(bot, server, source, target, message="")
487 super(bot, server, source, target, message)
492 class QuitMessage < BasicUserMessage
493 attr_accessor :was_on
494 def initialize(bot, server, source, target, message="")
495 super(bot, server, source, target, message)
500 class TopicMessage < BasicUserMessage
503 # topic set at (unixtime)
504 attr_reader :timestamp
505 # topic set on channel
508 # :info if topic info, :set if topic set
509 attr_accessor :info_or_set
510 def initialize(bot, server, source, channel, topic=ChannelTopic.new)
511 super(bot, server, source, channel, topic.text)
513 @timestamp = topic.set_on
519 # class to manage channel joins
520 class JoinMessage < BasicUserMessage
523 def initialize(bot, server, source, channel, message="")
524 super(bot, server, source, channel, message)
526 # in this case sourcenick is the nick that could be the bot
527 @address = (source == @bot.myself)
531 # class to manage channel parts
532 # same as a join, but can have a message too
533 class PartMessage < JoinMessage
536 class UnknownMessage < BasicUserMessage