]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - lib/rbot/message.rb
!!! (message.rb) dear tango_! i have no idea what r1073 is! -jsn.
[user/henk/code/ruby/rbot.git] / lib / rbot / message.rb
1 module Irc
2   BotConfig.register BotConfigArrayValue.new('core.address_prefix',
3     :default => [], :wizard => true,
4     :desc => "what non nick-matching prefixes should the bot respond to as if addressed (e.g !, so that '!foo' is treated like 'rbot: foo')"
5   )
6
7   BotConfig.register BotConfigBooleanValue.new('core.reply_with_nick',
8     :default => false, :wizard => true,
9     :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!')"
10   )
11
12   BotConfig.register BotConfigStringValue.new('core.nick_postfix',
13     :default => ':', :wizard => true,
14     :desc => "when replying with nick put this character after the nick of the user the bot is replying to"
15   )
16
17   # Define standard IRC attriubtes (not so standard actually,
18   # but the closest thing we have ...)
19   Bold = "\002"
20   Underline = "\037"
21   Reverse = "\026"
22   Italic = "\011"
23   NormalText = "\017"
24
25   # Color is prefixed by \003 and followed by optional
26   # foreground and background specifications, two-digits-max
27   # numbers separated by a comma. One of the two parts
28   # must be present.
29   Color = "\003"
30   ColorRx = /#{Color}\d?\d?(?:,\d\d?)?/
31
32   # Standard color codes
33   ColorCode = {
34     :black      => 1,
35     :blue       => 2,
36     :navyblue   => 2,
37     :navy_blue  => 2,
38     :green      => 3,
39     :red        => 4,
40     :brown      => 5,
41     :purple     => 6,
42     :olive      => 7,
43     :yellow     => 8,
44     :limegreen  => 9,
45     :lime_green => 9,
46     :teal       => 10,
47     :aqualight  => 11,
48     :aqua_light => 11,
49     :royal_blue => 12,
50     :hotpink    => 13,
51     :hot_pink   => 13,
52     :darkgray   => 14,
53     :dark_gray  => 14,
54     :lightgray  => 15,
55     :light_gray => 15,
56     :white      => 16
57   }
58
59   # Convert a String or Symbol into a color number
60   def Irc.find_color(data)
61     if Integer === data
62       data
63     else
64       f = if String === data
65             data.intern
66           else
67             data
68           end
69       if ColorCode.key?(f)
70         ColorCode[f] 
71       else
72         0
73       end
74     end
75   end
76
77   # Insert the full color code for a given
78   # foreground/background combination.
79   def Irc.color(fg=nil,bg=nil)
80     str = Color.dup
81     if fg
82      str << Irc.find_color(fg).to_s
83     end
84     if bg
85       str << "," << Irc.find_color(bg).to_s
86     end
87     return str
88   end
89
90   # base user message class, all user messages derive from this
91   # (a user message is defined as having a source hostmask, a target
92   # nick/channel and a message part)
93   class BasicUserMessage
94
95     # associated bot
96     attr_reader :bot
97
98     # associated server
99     attr_reader :server
100
101     # when the message was received
102     attr_reader :time
103
104     # User that originated the message
105     attr_reader :source
106
107     # User/Channel message was sent to
108     attr_reader :target
109
110     # contents of the message
111     attr_accessor :message
112
113     # has the message been replied to/handled by a plugin?
114     attr_accessor :replied
115
116     # instantiate a new Message
117     # bot::      associated bot class
118     # server::   Server where the message took place
119     # source::   User that sent the message
120     # target::   User/Channel is destined for
121     # message::  actual message
122     def initialize(bot, server, source, target, message)
123       @msg_wants_id = false unless defined? @msg_wants_id
124
125       @time = Time.now
126       @bot = bot
127       @source = source
128       @address = false
129       @target = target
130       @message = BasicUserMessage.stripcolour message
131       @replied = false
132       @server = server
133
134       @identified = false
135       if @msg_wants_id && @server.capabilities[:"identify-msg"]
136         if @message =~ /^([-+])(.*)/
137           @identified = ($1=="+")
138           @message = $2
139         else
140           warning "Message does not have identification"
141         end
142       end
143
144       if target && target == @bot.myself
145         @address = true
146       end
147
148     end
149
150     # Access the nick of the source
151     #
152     def sourcenick
153       @source.nick
154     end
155
156     # Access the user@host of the source
157     #
158     def sourceaddress
159       "#{@source.user}@#{@source.host}"
160     end
161
162     # Was the message from an identified user?
163     def identified?
164       return @identified
165     end
166
167     # returns true if the message was addressed to the bot.
168     # This includes any private message to the bot, or any public message
169     # which looks like it's addressed to the bot, e.g. "bot: foo", "bot, foo",
170     # a kick message when bot was kicked etc.
171     def address?
172       return @address
173     end
174
175     # has this message been replied to by a plugin?
176     def replied?
177       return @replied
178     end
179
180     # strip mIRC colour escapes from a string
181     def BasicUserMessage.stripcolour(string)
182       return "" unless string
183       ret = string.gsub(ColorRx, "")
184       #ret.tr!("\x00-\x1f", "")
185       ret
186     end
187
188   end
189
190   # class for handling IRC user messages. Includes some utilities for handling
191   # the message, for example in plugins.
192   # The +message+ member will have any bot addressing "^bot: " removed
193   # (address? will return true in this case)
194   class UserMessage < BasicUserMessage
195
196     # for plugin messages, the name of the plugin invoked by the message
197     attr_reader :plugin
198
199     # for plugin messages, the rest of the message, with the plugin name
200     # removed
201     attr_reader :params
202
203     # convenience member. Who to reply to (i.e. would be sourcenick for a
204     # privately addressed message, or target (the channel) for a publicly
205     # addressed message
206     attr_reader :replyto
207
208     # channel the message was in, nil for privately addressed messages
209     attr_reader :channel
210
211     # for PRIVMSGs, true if the message was a CTCP ACTION (CTCP stuff
212     # will be stripped from the message)
213     attr_reader :action
214
215     # instantiate a new UserMessage
216     # bot::      associated bot class
217     # source::   hostmask of the message source
218     # target::   nick/channel message is destined for
219     # message::  message part
220     def initialize(bot, server, source, target, message)
221       super(bot, server, source, target, message)
222       @target = target
223       @private = false
224       @plugin = nil
225       @action = false
226
227       if target == @bot.myself
228         @private = true
229         @address = true
230         @channel = nil
231         @replyto = source
232       else
233         @replyto = @target
234         @channel = @target
235       end
236
237       # check for option extra addressing prefixes, e.g "|search foo", or
238       # "!version" - first match wins
239       bot.config['core.address_prefix'].each {|mprefix|
240         if @message.gsub!(/^#{Regexp.escape(mprefix)}\s*/, "")
241           @address = true
242           break
243         end
244       }
245
246       # even if they used above prefixes, we allow for silly people who
247       # combine all possible types, e.g. "|rbot: hello", or
248       # "/msg rbot rbot: hello", etc
249       if @message.gsub!(/^\s*#{Regexp.escape(bot.nick)}\s*([:;,>]|\s)\s*/i, "")
250         @address = true
251       end
252
253       if(@message =~ /^\001ACTION\s(.+)\001/)
254         @message = $1
255         @action = true
256       end
257
258       # free splitting for plugins
259       @params = @message.dup
260       if @params.gsub!(/^\s*(\S+)[\s$]*/, "")
261         @plugin = $1.downcase
262         @params = nil unless @params.length > 0
263       end
264     end
265
266     # returns true for private messages, e.g. "/msg bot hello"
267     def private?
268       return @private
269     end
270
271     # returns true if the message was in a channel
272     def public?
273       return !@private
274     end
275
276     def action?
277       return @action
278     end
279
280     # convenience method to reply to a message, useful in plugins. It's the
281     # same as doing:
282     # <tt>@bot.say m.replyto, string</tt>
283     # So if the message is private, it will reply to the user. If it was
284     # in a channel, it will reply in the channel.
285     def plainreply(string, options={})
286       @bot.say @replyto, string, options
287       @replied = true
288     end
289
290     # Same as reply, but when replying in public it adds the nick of the user
291     # the bot is replying to
292     def nickreply(string, options={})
293       extra = self.public? ? "#{@source}#{@bot.config['core.nick_postfix']} " : ""
294       @bot.say @replyto, extra + string, options
295       @replied = true
296     end
297
298     # the default reply style is to nickreply unless the reply already contains
299     # the nick or core.reply_with_nick is set to false
300     #
301     def reply(string, options={})
302       if @bot.config['core.reply_with_nick'] and not string =~ /\b#{@source}\b/
303         return nickreply(string, options)
304       end
305       plainreply(string, options)
306     end
307
308     # convenience method to reply to a message with an action. It's the
309     # same as doing:
310     # <tt>@bot.action m.replyto, string</tt>
311     # So if the message is private, it will reply to the user. If it was
312     # in a channel, it will reply in the channel.
313     def act(string, options={})
314       @bot.action @replyto, string, options
315       @replied = true
316     end
317
318     # convenience method to reply "okay" in the current language to the
319     # message
320     def plainokay
321       self.plainreply @bot.lang.get("okay")
322     end
323
324     # Like the above, but append the username
325     def nickokay
326       str = @bot.lang.get("okay").dup
327       if self.public?
328         # remove final punctuation
329         str.gsub!(/[!,.]$/,"")
330         str += ", #{@source}"
331       end
332       self.plainreply str
333     end
334
335     # the default okay style is the same as the default reply style
336     #
337     def okay
338       if @bot.config['core.reply_with_nick']
339         return nickokay
340       end
341       plainokay
342     end
343
344   end
345
346   # class to manage IRC PRIVMSGs
347   class PrivMessage < UserMessage
348     def initialize(bot, server, source, target, message)
349       @msg_wants_id = true
350       super
351     end
352   end
353
354   # class to manage IRC NOTICEs
355   class NoticeMessage < UserMessage
356     def initialize(bot, server, source, target, message)
357       @msg_wants_id = true
358       super
359     end
360   end
361
362   # class to manage IRC KICKs
363   # +address?+ can be used as a shortcut to see if the bot was kicked,
364   # basically, +target+ was kicked from +channel+ by +source+ with +message+
365   class KickMessage < BasicUserMessage
366     # channel user was kicked from
367     attr_reader :channel
368
369     def initialize(bot, server, source, target, channel, message="")
370       super(bot, server, source, target, message)
371       @channel = channel
372     end
373   end
374
375   # class to pass IRC Nick changes in. @message contains the old nickame,
376   # @sourcenick contains the new one.
377   class NickMessage < BasicUserMessage
378     def initialize(bot, server, source, oldnick, newnick)
379       super(bot, server, source, oldnick, newnick)
380     end
381
382     def oldnick
383       return @target
384     end
385
386     def newnick
387       return @message
388     end
389   end
390
391   class QuitMessage < BasicUserMessage
392     def initialize(bot, server, source, target, message="")
393       super(bot, server, source, target, message)
394     end
395   end
396
397   class TopicMessage < BasicUserMessage
398     # channel topic
399     attr_reader :topic
400     # topic set at (unixtime)
401     attr_reader :timestamp
402     # topic set on channel
403     attr_reader :channel
404
405     def initialize(bot, server, source, channel, topic=ChannelTopic.new)
406       super(bot, server, source, channel, topic.text)
407       @topic = topic
408       @timestamp = topic.set_on
409       @channel = channel
410     end
411   end
412
413   # class to manage channel joins
414   class JoinMessage < BasicUserMessage
415     # channel joined
416     attr_reader :channel
417     def initialize(bot, server, source, channel, message="")
418       super(bot, server, source, channel, message)
419       @channel = channel
420       # in this case sourcenick is the nick that could be the bot
421       @address = (source == @bot.myself)
422     end
423   end
424
425   # class to manage channel parts
426   # same as a join, but can have a message too
427   class PartMessage < JoinMessage
428   end
429 end