]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - lib/rbot/message.rb
Add Italic (\011) constant to complement Underline, Reverse and Bold
[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   Bold = "\002"
18   Underline = "\037"
19   Reverse = "\026"
20   Italic = "\011"
21   Color = "\003"
22   ColorRx = /#{Color}\d\d?(?:,\d\d?)?/
23
24   # base user message class, all user messages derive from this
25   # (a user message is defined as having a source hostmask, a target
26   # nick/channel and a message part)
27   class BasicUserMessage
28
29     # associated bot
30     attr_reader :bot
31
32     # associated server
33     attr_reader :server
34
35     # when the message was received
36     attr_reader :time
37
38     # User that originated the message
39     attr_reader :source
40
41     # User/Channel message was sent to
42     attr_reader :target
43
44     # contents of the message
45     attr_accessor :message
46
47     # has the message been replied to/handled by a plugin?
48     attr_accessor :replied
49
50     # instantiate a new Message
51     # bot::      associated bot class
52     # server::   Server where the message took place
53     # source::   User that sent the message
54     # target::   User/Channel is destined for
55     # message::  actual message
56     def initialize(bot, server, source, target, message)
57       @msg_wants_id = false unless defined? @msg_wants_id
58
59       @time = Time.now
60       @bot = bot
61       @source = source
62       @address = false
63       @target = target
64       @message = BasicUserMessage.stripcolour message
65       @replied = false
66       @server = server
67
68       @identified = false
69       if @msg_wants_id && @server.capabilities[:"identify-msg"]
70         if @message =~ /^([-+])(.*)/
71           @identified = ($1=="+")
72           @message = $2
73         else
74           warning "Message does not have identification"
75         end
76       end
77
78       if target && target == @bot.myself
79         @address = true
80       end
81
82     end
83
84     # Access the nick of the source
85     #
86     def sourcenick
87       @source.nick
88     end
89
90     # Access the user@host of the source
91     #
92     def sourceaddress
93       "#{@source.user}@#{@source.host}"
94     end
95
96     # Was the message from an identified user?
97     def identified?
98       return @identified
99     end
100
101     # returns true if the message was addressed to the bot.
102     # This includes any private message to the bot, or any public message
103     # which looks like it's addressed to the bot, e.g. "bot: foo", "bot, foo",
104     # a kick message when bot was kicked etc.
105     def address?
106       return @address
107     end
108
109     # has this message been replied to by a plugin?
110     def replied?
111       return @replied
112     end
113
114     # strip mIRC colour escapes from a string
115     def BasicUserMessage.stripcolour(string)
116       return "" unless string
117       ret = string.gsub(ColorRx, "")
118       #ret.tr!("\x00-\x1f", "")
119       ret
120     end
121
122   end
123
124   # class for handling IRC user messages. Includes some utilities for handling
125   # the message, for example in plugins.
126   # The +message+ member will have any bot addressing "^bot: " removed
127   # (address? will return true in this case)
128   class UserMessage < BasicUserMessage
129
130     # for plugin messages, the name of the plugin invoked by the message
131     attr_reader :plugin
132
133     # for plugin messages, the rest of the message, with the plugin name
134     # removed
135     attr_reader :params
136
137     # convenience member. Who to reply to (i.e. would be sourcenick for a
138     # privately addressed message, or target (the channel) for a publicly
139     # addressed message
140     attr_reader :replyto
141
142     # channel the message was in, nil for privately addressed messages
143     attr_reader :channel
144
145     # for PRIVMSGs, true if the message was a CTCP ACTION (CTCP stuff
146     # will be stripped from the message)
147     attr_reader :action
148
149     # instantiate a new UserMessage
150     # bot::      associated bot class
151     # source::   hostmask of the message source
152     # target::   nick/channel message is destined for
153     # message::  message part
154     def initialize(bot, server, source, target, message)
155       super(bot, server, source, target, message)
156       @target = target
157       @private = false
158       @plugin = nil
159       @action = false
160
161       if target == @bot.myself
162         @private = true
163         @address = true
164         @channel = nil
165         @replyto = source
166       else
167         @replyto = @target
168         @channel = @target
169       end
170
171       # check for option extra addressing prefixes, e.g "|search foo", or
172       # "!version" - first match wins
173       bot.config['core.address_prefix'].each {|mprefix|
174         if @message.gsub!(/^#{Regexp.escape(mprefix)}\s*/, "")
175           @address = true
176           break
177         end
178       }
179
180       # even if they used above prefixes, we allow for silly people who
181       # combine all possible types, e.g. "|rbot: hello", or
182       # "/msg rbot rbot: hello", etc
183       if @message.gsub!(/^\s*#{Regexp.escape(bot.nick)}\s*([:;,>]|\s)\s*/i, "")
184         @address = true
185       end
186
187       if(@message =~ /^\001ACTION\s(.+)\001/)
188         @message = $1
189         @action = true
190       end
191
192       # free splitting for plugins
193       @params = @message.dup
194       if @params.gsub!(/^\s*(\S+)[\s$]*/, "")
195         @plugin = $1.downcase
196         @params = nil unless @params.length > 0
197       end
198     end
199
200     # returns true for private messages, e.g. "/msg bot hello"
201     def private?
202       return @private
203     end
204
205     # returns true if the message was in a channel
206     def public?
207       return !@private
208     end
209
210     def action?
211       return @action
212     end
213
214     # convenience method to reply to a message, useful in plugins. It's the
215     # same as doing:
216     # <tt>@bot.say m.replyto, string</tt>
217     # So if the message is private, it will reply to the user. If it was
218     # in a channel, it will reply in the channel.
219     def plainreply(string, options={})
220       @bot.say @replyto, string, options
221       @replied = true
222     end
223
224     # Same as reply, but when replying in public it adds the nick of the user
225     # the bot is replying to
226     def nickreply(string, options={})
227       extra = self.public? ? "#{@source}#{@bot.config['core.nick_postfix']} " : ""
228       @bot.say @replyto, extra + string, options
229       @replied = true
230     end
231
232     # the default reply style is to nickreply unless the reply already contains
233     # the nick or core.reply_with_nick is set to false
234     #
235     def reply(string, options={})
236       if @bot.config['core.reply_with_nick'] and not string =~ /\b#{@source}\b/
237         return nickreply(string, options)
238       end
239       plainreply(string, options)
240     end
241
242     # convenience method to reply to a message with an action. It's the
243     # same as doing:
244     # <tt>@bot.action m.replyto, string</tt>
245     # So if the message is private, it will reply to the user. If it was
246     # in a channel, it will reply in the channel.
247     def act(string, options={})
248       @bot.action @replyto, string, options
249       @replied = true
250     end
251
252     # convenience method to reply "okay" in the current language to the
253     # message
254     def plainokay
255       self.plainreply @bot.lang.get("okay")
256     end
257
258     # Like the above, but append the username
259     def nickokay
260       str = @bot.lang.get("okay").dup
261       if self.public?
262         # remove final punctuation
263         str.gsub!(/[!,.]$/,"")
264         str += ", #{@source}"
265       end
266       self.plainreply str
267     end
268
269     # the default okay style is the same as the default reply style
270     #
271     def okay
272       if @bot.config['core.reply_with_nick']
273         return nickokay
274       end
275       plainokay
276     end
277
278   end
279
280   # class to manage IRC PRIVMSGs
281   class PrivMessage < UserMessage
282     def initialize(bot, server, source, target, message)
283       @msg_wants_id = true
284       super
285     end
286   end
287
288   # class to manage IRC NOTICEs
289   class NoticeMessage < UserMessage
290     def initialize(bot, server, source, target, message)
291       @msg_wants_id = true
292       super
293     end
294   end
295
296   # class to manage IRC KICKs
297   # +address?+ can be used as a shortcut to see if the bot was kicked,
298   # basically, +target+ was kicked from +channel+ by +source+ with +message+
299   class KickMessage < BasicUserMessage
300     # channel user was kicked from
301     attr_reader :channel
302
303     def initialize(bot, server, source, target, channel, message="")
304       super(bot, server, source, target, message)
305       @channel = channel
306     end
307   end
308
309   # class to pass IRC Nick changes in. @message contains the old nickame,
310   # @sourcenick contains the new one.
311   class NickMessage < BasicUserMessage
312     def initialize(bot, server, source, oldnick, newnick)
313       super(bot, server, source, oldnick, newnick)
314     end
315
316     def oldnick
317       return @target
318     end
319
320     def newnick
321       return @message
322     end
323   end
324
325   class QuitMessage < BasicUserMessage
326     def initialize(bot, server, source, target, message="")
327       super(bot, server, source, target, message)
328     end
329   end
330
331   class TopicMessage < BasicUserMessage
332     # channel topic
333     attr_reader :topic
334     # topic set at (unixtime)
335     attr_reader :timestamp
336     # topic set on channel
337     attr_reader :channel
338
339     def initialize(bot, server, source, channel, topic=ChannelTopic.new)
340       super(bot, server, source, channel, topic.text)
341       @topic = topic
342       @timestamp = topic.set_on
343       @channel = channel
344     end
345   end
346
347   # class to manage channel joins
348   class JoinMessage < BasicUserMessage
349     # channel joined
350     attr_reader :channel
351     def initialize(bot, server, source, channel, message="")
352       super(bot, server, source, channel, message)
353       @channel = channel
354       # in this case sourcenick is the nick that could be the bot
355       @address = (source == @bot.myself)
356     end
357   end
358
359   # class to manage channel parts
360   # same as a join, but can have a message too
361   class PartMessage < JoinMessage
362   end
363 end