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