]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - lib/rbot/message.rb
core/config: remove leftover heavy-load debug line
[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       if @source.reply_to?(:nick)
154         @source.nick
155       else
156         @source.to_s
157       end
158     end
159
160     # Access the user@host of the source
161     #
162     def sourceaddress
163       str = String.new
164       if @source.reply_to?(:user)
165         str << @source.user.to_s
166       end
167       if @source.reply_to?(:host)
168         str << @source.host.to_s
169       end
170       str
171     end
172
173     # Was the message from an identified user?
174     def identified?
175       return @identified
176     end
177
178     # returns true if the message was addressed to the bot.
179     # This includes any private message to the bot, or any public message
180     # which looks like it's addressed to the bot, e.g. "bot: foo", "bot, foo",
181     # a kick message when bot was kicked etc.
182     def address?
183       return @address
184     end
185
186     # has this message been replied to by a plugin?
187     def replied?
188       return @replied
189     end
190
191     # strip mIRC colour escapes from a string
192     def BasicUserMessage.stripcolour(string)
193       return "" unless string
194       ret = string.gsub(ColorRx, "")
195       #ret.tr!("\x00-\x1f", "")
196       ret
197     end
198
199   end
200
201   # class for handling IRC user messages. Includes some utilities for handling
202   # the message, for example in plugins.
203   # The +message+ member will have any bot addressing "^bot: " removed
204   # (address? will return true in this case)
205   class UserMessage < BasicUserMessage
206
207     # for plugin messages, the name of the plugin invoked by the message
208     attr_reader :plugin
209
210     # for plugin messages, the rest of the message, with the plugin name
211     # removed
212     attr_reader :params
213
214     # convenience member. Who to reply to (i.e. would be sourcenick for a
215     # privately addressed message, or target (the channel) for a publicly
216     # addressed message
217     attr_reader :replyto
218
219     # channel the message was in, nil for privately addressed messages
220     attr_reader :channel
221
222     # for PRIVMSGs, true if the message was a CTCP ACTION (CTCP stuff
223     # will be stripped from the message)
224     attr_reader :action
225
226     # instantiate a new UserMessage
227     # bot::      associated bot class
228     # source::   hostmask of the message source
229     # target::   nick/channel message is destined for
230     # message::  message part
231     def initialize(bot, server, source, target, message)
232       super(bot, server, source, target, message)
233       @target = target
234       @private = false
235       @plugin = nil
236       @action = false
237
238       if target == @bot.myself
239         @private = true
240         @address = true
241         @channel = nil
242         @replyto = source
243       else
244         @replyto = @target
245         @channel = @target
246       end
247
248       # check for option extra addressing prefixes, e.g "|search foo", or
249       # "!version" - first match wins
250       bot.config['core.address_prefix'].each {|mprefix|
251         if @message.gsub!(/^#{Regexp.escape(mprefix)}\s*/, "")
252           @address = true
253           break
254         end
255       }
256
257       # even if they used above prefixes, we allow for silly people who
258       # combine all possible types, e.g. "|rbot: hello", or
259       # "/msg rbot rbot: hello", etc
260       if @message.gsub!(/^\s*#{Regexp.escape(bot.nick)}\s*([:;,>]|\s)\s*/i, "")
261         @address = true
262       end
263
264       if(@message =~ /^\001ACTION\s(.+)\001/)
265         @message = $1
266         @action = true
267       end
268
269       # free splitting for plugins
270       @params = @message.dup
271       if @params.gsub!(/^\s*(\S+)[\s$]*/, "")
272         @plugin = $1.downcase
273         @params = nil unless @params.length > 0
274       end
275     end
276
277     # returns true for private messages, e.g. "/msg bot hello"
278     def private?
279       return @private
280     end
281
282     # returns true if the message was in a channel
283     def public?
284       return !@private
285     end
286
287     def action?
288       return @action
289     end
290
291     # convenience method to reply to a message, useful in plugins. It's the
292     # same as doing:
293     # <tt>@bot.say m.replyto, string</tt>
294     # So if the message is private, it will reply to the user. If it was
295     # in a channel, it will reply in the channel.
296     def plainreply(string, options={})
297       @bot.say @replyto, string, options
298       @replied = true
299     end
300
301     # Same as reply, but when replying in public it adds the nick of the user
302     # the bot is replying to
303     def nickreply(string, options={})
304       extra = self.public? ? "#{@source}#{@bot.config['core.nick_postfix']} " : ""
305       @bot.say @replyto, extra + string, options
306       @replied = true
307     end
308
309     # the default reply style is to nickreply unless the reply already contains
310     # the nick or core.reply_with_nick is set to false
311     #
312     def reply(string, options={})
313       if @bot.config['core.reply_with_nick'] and not string =~ /\b#{@source}\b/
314         return nickreply(string, options)
315       end
316       plainreply(string, options)
317     end
318
319     # convenience method to reply to a message with an action. It's the
320     # same as doing:
321     # <tt>@bot.action m.replyto, string</tt>
322     # So if the message is private, it will reply to the user. If it was
323     # in a channel, it will reply in the channel.
324     def act(string, options={})
325       @bot.action @replyto, string, options
326       @replied = true
327     end
328
329     # convenience method to reply "okay" in the current language to the
330     # message
331     def plainokay
332       self.plainreply @bot.lang.get("okay")
333     end
334
335     # Like the above, but append the username
336     def nickokay
337       str = @bot.lang.get("okay").dup
338       if self.public?
339         # remove final punctuation
340         str.gsub!(/[!,.]$/,"")
341         str += ", #{@source}"
342       end
343       self.plainreply str
344     end
345
346     # the default okay style is the same as the default reply style
347     #
348     def okay
349       if @bot.config['core.reply_with_nick']
350         return nickokay
351       end
352       plainokay
353     end
354
355   end
356
357   # class to manage IRC PRIVMSGs
358   class PrivMessage < UserMessage
359     def initialize(bot, server, source, target, message)
360       @msg_wants_id = true
361       super
362     end
363   end
364
365   # class to manage IRC NOTICEs
366   class NoticeMessage < UserMessage
367     def initialize(bot, server, source, target, message)
368       @msg_wants_id = true
369       super
370     end
371   end
372
373   # class to manage IRC KICKs
374   # +address?+ can be used as a shortcut to see if the bot was kicked,
375   # basically, +target+ was kicked from +channel+ by +source+ with +message+
376   class KickMessage < BasicUserMessage
377     # channel user was kicked from
378     attr_reader :channel
379
380     def initialize(bot, server, source, target, channel, message="")
381       super(bot, server, source, target, message)
382       @channel = channel
383     end
384   end
385
386   # class to pass IRC Nick changes in. @message contains the old nickame,
387   # @sourcenick contains the new one.
388   class NickMessage < BasicUserMessage
389     def initialize(bot, server, source, oldnick, newnick)
390       super(bot, server, source, oldnick, newnick)
391     end
392
393     def oldnick
394       return @target
395     end
396
397     def newnick
398       return @message
399     end
400   end
401
402   class QuitMessage < BasicUserMessage
403     def initialize(bot, server, source, target, message="")
404       super(bot, server, source, target, message)
405     end
406   end
407
408   class TopicMessage < BasicUserMessage
409     # channel topic
410     attr_reader :topic
411     # topic set at (unixtime)
412     attr_reader :timestamp
413     # topic set on channel
414     attr_reader :channel
415
416     def initialize(bot, server, source, channel, topic=ChannelTopic.new)
417       super(bot, server, source, channel, topic.text)
418       @topic = topic
419       @timestamp = topic.set_on
420       @channel = channel
421     end
422   end
423
424   # class to manage channel joins
425   class JoinMessage < BasicUserMessage
426     # channel joined
427     attr_reader :channel
428     def initialize(bot, server, source, channel, message="")
429       super(bot, server, source, channel, message)
430       @channel = channel
431       # in this case sourcenick is the nick that could be the bot
432       @address = (source == @bot.myself)
433     end
434   end
435
436   # class to manage channel parts
437   # same as a join, but can have a message too
438   class PartMessage < JoinMessage
439   end
440 end