grinch 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +1 -0
  3. data/LICENSE +22 -0
  4. data/README.md +180 -0
  5. data/docs/bot_options.md +454 -0
  6. data/docs/changes.md +541 -0
  7. data/docs/common_mistakes.md +60 -0
  8. data/docs/common_tasks.md +57 -0
  9. data/docs/encodings.md +69 -0
  10. data/docs/events.md +273 -0
  11. data/docs/getting_started.md +184 -0
  12. data/docs/logging.md +90 -0
  13. data/docs/migrating.md +267 -0
  14. data/docs/plugins.md +4 -0
  15. data/docs/readme.md +20 -0
  16. data/examples/basic/autovoice.rb +32 -0
  17. data/examples/basic/google.rb +35 -0
  18. data/examples/basic/hello.rb +15 -0
  19. data/examples/basic/join_part.rb +34 -0
  20. data/examples/basic/memo.rb +39 -0
  21. data/examples/basic/msg.rb +16 -0
  22. data/examples/basic/seen.rb +36 -0
  23. data/examples/basic/urban_dict.rb +35 -0
  24. data/examples/basic/url_shorten.rb +35 -0
  25. data/examples/plugins/autovoice.rb +37 -0
  26. data/examples/plugins/custom_prefix.rb +23 -0
  27. data/examples/plugins/dice_roll.rb +38 -0
  28. data/examples/plugins/google.rb +36 -0
  29. data/examples/plugins/hello.rb +22 -0
  30. data/examples/plugins/hooks.rb +36 -0
  31. data/examples/plugins/join_part.rb +42 -0
  32. data/examples/plugins/lambdas.rb +35 -0
  33. data/examples/plugins/last_nick.rb +24 -0
  34. data/examples/plugins/msg.rb +22 -0
  35. data/examples/plugins/multiple_matches.rb +32 -0
  36. data/examples/plugins/own_events.rb +37 -0
  37. data/examples/plugins/seen.rb +45 -0
  38. data/examples/plugins/timer.rb +22 -0
  39. data/examples/plugins/url_shorten.rb +33 -0
  40. data/lib/cinch.rb +5 -0
  41. data/lib/cinch/ban.rb +50 -0
  42. data/lib/cinch/bot.rb +479 -0
  43. data/lib/cinch/cached_list.rb +19 -0
  44. data/lib/cinch/callback.rb +20 -0
  45. data/lib/cinch/channel.rb +463 -0
  46. data/lib/cinch/channel_list.rb +29 -0
  47. data/lib/cinch/configuration.rb +73 -0
  48. data/lib/cinch/configuration/bot.rb +48 -0
  49. data/lib/cinch/configuration/dcc.rb +16 -0
  50. data/lib/cinch/configuration/plugins.rb +41 -0
  51. data/lib/cinch/configuration/sasl.rb +19 -0
  52. data/lib/cinch/configuration/ssl.rb +19 -0
  53. data/lib/cinch/configuration/timeouts.rb +14 -0
  54. data/lib/cinch/constants.rb +533 -0
  55. data/lib/cinch/dcc.rb +12 -0
  56. data/lib/cinch/dcc/dccable_object.rb +37 -0
  57. data/lib/cinch/dcc/incoming.rb +1 -0
  58. data/lib/cinch/dcc/incoming/send.rb +147 -0
  59. data/lib/cinch/dcc/outgoing.rb +1 -0
  60. data/lib/cinch/dcc/outgoing/send.rb +122 -0
  61. data/lib/cinch/exceptions.rb +46 -0
  62. data/lib/cinch/formatting.rb +125 -0
  63. data/lib/cinch/handler.rb +118 -0
  64. data/lib/cinch/handler_list.rb +90 -0
  65. data/lib/cinch/helpers.rb +231 -0
  66. data/lib/cinch/irc.rb +924 -0
  67. data/lib/cinch/isupport.rb +98 -0
  68. data/lib/cinch/log_filter.rb +21 -0
  69. data/lib/cinch/logger.rb +168 -0
  70. data/lib/cinch/logger/formatted_logger.rb +97 -0
  71. data/lib/cinch/logger/zcbot_logger.rb +22 -0
  72. data/lib/cinch/logger_list.rb +85 -0
  73. data/lib/cinch/mask.rb +69 -0
  74. data/lib/cinch/message.rb +392 -0
  75. data/lib/cinch/message_queue.rb +107 -0
  76. data/lib/cinch/mode_parser.rb +76 -0
  77. data/lib/cinch/network.rb +104 -0
  78. data/lib/cinch/open_ended_queue.rb +26 -0
  79. data/lib/cinch/pattern.rb +65 -0
  80. data/lib/cinch/plugin.rb +515 -0
  81. data/lib/cinch/plugin_list.rb +38 -0
  82. data/lib/cinch/rubyext/float.rb +3 -0
  83. data/lib/cinch/rubyext/module.rb +26 -0
  84. data/lib/cinch/rubyext/string.rb +33 -0
  85. data/lib/cinch/sasl.rb +34 -0
  86. data/lib/cinch/sasl/dh_blowfish.rb +71 -0
  87. data/lib/cinch/sasl/diffie_hellman.rb +47 -0
  88. data/lib/cinch/sasl/mechanism.rb +6 -0
  89. data/lib/cinch/sasl/plain.rb +26 -0
  90. data/lib/cinch/syncable.rb +83 -0
  91. data/lib/cinch/target.rb +199 -0
  92. data/lib/cinch/timer.rb +145 -0
  93. data/lib/cinch/user.rb +488 -0
  94. data/lib/cinch/user_list.rb +87 -0
  95. data/lib/cinch/utilities/deprecation.rb +16 -0
  96. data/lib/cinch/utilities/encoding.rb +37 -0
  97. data/lib/cinch/utilities/kernel.rb +13 -0
  98. data/lib/cinch/version.rb +4 -0
  99. metadata +140 -0
data/lib/cinch/mask.rb ADDED
@@ -0,0 +1,69 @@
1
+ module Cinch
2
+ # This class represents masks, which are primarily used for bans.
3
+ class Mask
4
+ # @return [String]
5
+ attr_reader :nick
6
+ # @return [String]
7
+ attr_reader :user
8
+ # @return [String]
9
+ attr_reader :host
10
+ # @return [String]
11
+ attr_reader :mask
12
+
13
+ # @version 1.1.2
14
+ # @param [String] mask
15
+ def initialize(mask)
16
+ @mask = mask
17
+ @nick, @user, @host = mask.match(/(.+)!(.+)@(.+)/)[1..-1]
18
+ @regexp = Regexp.new("^" + Regexp.escape(mask).gsub("\\*", ".*").gsub("\\?", ".?") + "$")
19
+ end
20
+
21
+ # @return [Boolean]
22
+ # @since 1.1.0
23
+ def ==(other)
24
+ other.respond_to?(:mask) && other.mask == @mask
25
+ end
26
+
27
+ # @return [Boolean]
28
+ # @since 1.1.0
29
+ def eql?(other)
30
+ other.is_a?(self.class) && self == other
31
+ end
32
+
33
+ # @return [Fixnum]
34
+ def hash
35
+ @mask.hash
36
+ end
37
+
38
+ # @param [Mask, String, #mask] target
39
+ # @return [Boolean]
40
+ # @version 1.1.2
41
+ def match(target)
42
+ return self.class.from(target).mask =~ @regexp
43
+
44
+ # TODO support CIDR (freenode)
45
+ end
46
+ alias_method :=~, :match
47
+
48
+ # @return [String]
49
+ def to_s
50
+ @mask.dup
51
+ end
52
+
53
+ # @param [String, #mask] target
54
+ # @return [target] if already a Mask
55
+ # @return [Mask]
56
+ # @version 2.0.0
57
+ def self.from(target)
58
+ return target if target.is_a?(self)
59
+
60
+ if target.respond_to?(:mask)
61
+ mask = target.mask
62
+ else
63
+ mask = Mask.new(target.to_s)
64
+ end
65
+
66
+ return mask
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,392 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "time"
3
+ require "cinch/formatting"
4
+
5
+ module Cinch
6
+ # This class serves two purposes. For one, it simply
7
+ # represents incoming messages and allows for querying various
8
+ # details (who sent the message, what kind of message it is, etc).
9
+ #
10
+ # At the same time, it allows **responding** to messages, which
11
+ # means sending messages to either users or channels.
12
+ class Message
13
+ # @return [String]
14
+ attr_reader :raw
15
+
16
+ # @return [String]
17
+ attr_reader :prefix
18
+
19
+ # @return [String]
20
+ attr_reader :command
21
+
22
+ # @return [Array<String>]
23
+ attr_reader :params
24
+
25
+ # @return [Hash]
26
+ attr_reader :tags
27
+
28
+ # @return [Array<Symbol>]
29
+ attr_reader :events
30
+ # @api private
31
+ attr_writer :events
32
+
33
+ # @return [Time]
34
+ # @since 2.0.0
35
+ attr_reader :time
36
+
37
+ # @return [Bot]
38
+ # @since 1.1.0
39
+ attr_reader :bot
40
+
41
+ # @return [User] The user who sent this message
42
+ attr_reader :user
43
+
44
+ # @return [String, nil]
45
+ attr_reader :server
46
+
47
+ # @return [Integer, nil] the numeric error code, if any
48
+ attr_reader :error
49
+
50
+ # @return [String, nil] the command part of an CTCP message
51
+ attr_reader :ctcp_command
52
+
53
+ # @return [Channel] The channel in which this message was sent
54
+ attr_reader :channel
55
+
56
+ # @return [String, nil] the CTCP message, without \001 control characters
57
+ attr_reader :ctcp_message
58
+
59
+ # @return [Array<String>, nil]
60
+ attr_reader :ctcp_args
61
+
62
+ # @return [String, nil]
63
+ attr_reader :message
64
+
65
+ # @return [String, nil] The action message
66
+ # @since 2.0.0
67
+ attr_reader :action_message
68
+
69
+ # @return [Target]
70
+ attr_reader :target
71
+
72
+ # The STATUSMSG mode a channel message was sent to.
73
+ #
74
+ # Some IRC servers allow sending messages limited to people in a
75
+ # channel who have a certain mode. For example, by sending a
76
+ # message to `+#channel`, only people who are voiced, or have a
77
+ # higher mode (op) will receive the message.
78
+ #
79
+ # This attribute contains the mode character the message was sent
80
+ # to, or nil if it was a normal message. For the previous example,
81
+ # this attribute would be set to `"v"`, for voiced.
82
+ #
83
+ # @return [String, nil]
84
+ # @since 2.3.0
85
+ attr_reader :statusmsg_mode
86
+
87
+ def initialize(msg, bot)
88
+ @raw = msg
89
+ @bot = bot
90
+ @matches = {:ctcp => {}, :action => {}, :other => {}}
91
+ @events = []
92
+ @time = Time.now
93
+ @statusmsg_mode = nil
94
+ parse if msg
95
+ end
96
+
97
+ # @api private
98
+ # @return [void]
99
+ def parse
100
+ match = @raw.match(/(?:^@([^:]+))?(?::?(\S+) )?(\S+)(.*)/)
101
+ tags, @prefix, @command, raw_params = match.captures
102
+
103
+ if @bot.irc.network.ngametv?
104
+ if @prefix != "ngame"
105
+ @prefix = "%s!%s@%s" % [@prefix, @prefix, @prefix]
106
+ end
107
+ end
108
+
109
+ @params = parse_params(raw_params)
110
+ @tags = parse_tags(tags)
111
+
112
+ @user = parse_user
113
+ @channel, @statusmsg_mode = parse_channel
114
+ @target = @channel || @user
115
+ @server = parse_server
116
+ @error = parse_error
117
+ @message = parse_message
118
+
119
+ @ctcp_message = parse_ctcp_message
120
+ @ctcp_command = parse_ctcp_command
121
+ @ctcp_args = parse_ctcp_args
122
+
123
+ @action_message = parse_action_message
124
+ end
125
+
126
+ # @group Type checking
127
+
128
+ # @return [Boolean] true if the message is an numeric reply (as
129
+ # opposed to a command)
130
+ def numeric_reply?
131
+ !!@command.match(/^\d{3}$/)
132
+ end
133
+
134
+ # @return [Boolean] true if the message describes an error
135
+ def error?
136
+ !@error.nil?
137
+ end
138
+
139
+ # @return [Boolean] true if this message was sent in a channel
140
+ def channel?
141
+ !@channel.nil?
142
+ end
143
+
144
+ # @return [Boolean] true if the message is an CTCP message
145
+ def ctcp?
146
+ !!(@params.last =~ /\001.+\001/)
147
+ end
148
+
149
+ # @return [Boolean] true if the message is an action (/me)
150
+ # @since 2.0.0
151
+ def action?
152
+ @ctcp_command == "ACTION"
153
+ end
154
+
155
+ # @endgroup
156
+
157
+ # @api private
158
+ # @return [MatchData]
159
+ def match(regexp, type, strip_colors)
160
+ text = ""
161
+ case type
162
+ when :ctcp
163
+ text = ctcp_message
164
+ when :action
165
+ text = action_message
166
+ else
167
+ text = message.to_s
168
+ type = :other
169
+ end
170
+
171
+ if strip_colors
172
+ text = Cinch::Formatting.unformat(text)
173
+ end
174
+
175
+ @matches[type][regexp] ||= text.match(regexp)
176
+ end
177
+
178
+ # @group Replying
179
+
180
+ # Replies to a message, automatically determining if it was a
181
+ # channel or a private message.
182
+ #
183
+ # If the message is a STATUSMSG, i.e. it was send to `+#channel`
184
+ # or `@#channel` instead of `#channel`, the reply will be sent as
185
+ # the same kind of STATUSMSG. See {#statusmsg_mode} for more
186
+ # information on STATUSMSG.
187
+ #
188
+ # @param [String] text the message
189
+ # @param [Boolean] prefix if prefix is true and the message was in
190
+ # a channel, the reply will be prefixed by the nickname of whoever
191
+ # send the mesage
192
+ # @return [void]
193
+ def reply(text, prefix = false)
194
+ text = text.to_s
195
+ if @channel && prefix
196
+ text = text.split("\n").map {|l| "#{user.nick}: #{l}"}.join("\n")
197
+ end
198
+
199
+ reply_target.send(text)
200
+ end
201
+
202
+ # Like {#reply}, but using {Target#safe_send} instead
203
+ #
204
+ # @param (see #reply)
205
+ # @return (see #reply)
206
+ def safe_reply(text, prefix = false)
207
+ text = text.to_s
208
+ if channel && prefix
209
+ text = "#{@user.nick}: #{text}"
210
+ end
211
+ reply_target.safe_send(text)
212
+ end
213
+
214
+ # Reply to a message with an action.
215
+ #
216
+ # For its behaviour with regard to STATUSMSG, see {#reply}.
217
+ #
218
+ # @param [String] text the action message
219
+ # @return [void]
220
+ def action_reply(text)
221
+ text = text.to_s
222
+ reply_target.action(text)
223
+ end
224
+
225
+ # Like {#action_reply}, but using {Target#safe_action} instead
226
+ #
227
+ # @param (see #action_reply)
228
+ # @return (see #action_reply)
229
+ def safe_action_reply(text)
230
+ text = text.to_s
231
+ reply_target.safe_action(text)
232
+ end
233
+
234
+ # Reply to a CTCP message
235
+ #
236
+ # @return [void]
237
+ def ctcp_reply(answer)
238
+ return unless ctcp?
239
+ @user.notice "\001#{@ctcp_command} #{answer}\001"
240
+ end
241
+
242
+ # @endgroup
243
+
244
+ # @return [String]
245
+ # @since 1.1.0
246
+ def to_s
247
+ "#<Cinch::Message @raw=#{@raw.chomp.inspect} @params=#{@params.inspect} channel=#{@channel.inspect} user=#{@user.inspect}>"
248
+ end
249
+
250
+ private
251
+ def reply_target
252
+ if @channel.nil? || @statusmsg_mode.nil?
253
+ return @target
254
+ end
255
+ prefix = @bot.irc.isupport["PREFIX"][@statusmsg_mode]
256
+ return Target.new(prefix + @channel.name, @bot)
257
+ end
258
+ def regular_command?
259
+ !numeric_reply? # a command can only be numeric or "regular"…
260
+ end
261
+
262
+ def parse_params(raw_params)
263
+ params = []
264
+ if match = raw_params.match(/(?:^:| :)(.*)$/)
265
+ params = match.pre_match.split(" ")
266
+ params << match[1]
267
+ else
268
+ params = raw_params.split(" ")
269
+ end
270
+
271
+ return params
272
+ end
273
+
274
+ def parse_tags(raw_tags)
275
+ return {} if raw_tags.nil?
276
+
277
+ def to_symbol(string)
278
+ return string.gsub(/-/, "_").downcase.to_sym
279
+ end
280
+
281
+ tags = {}
282
+ raw_tags.split(";").each do |tag|
283
+ tag_name, tag_value = tag.split("=")
284
+ if tag_value =~ /,/
285
+ tag_value = tag_value.split(',')
286
+ elsif tag_value.nil?
287
+ tag_value = tag_name
288
+ end
289
+ if tag_name =~ /\//
290
+ vendor, tag_name = tag_name.split('/')
291
+ tags[to_symbol(vendor)] = {
292
+ to_symbol(tag_name) => tag_value
293
+ }
294
+ else
295
+ tags[to_symbol(tag_name)] = tag_value
296
+ end
297
+ end
298
+ return tags
299
+ end
300
+
301
+ def parse_user
302
+ return unless @prefix
303
+ nick = @prefix[/^(\S+)!/, 1]
304
+ user = @prefix[/^\S+!(\S+)@/, 1]
305
+ host = @prefix[/@(\S+)$/, 1]
306
+
307
+ return nil if nick.nil?
308
+ return @bot.user_list.find_ensured(user, nick, host)
309
+ end
310
+
311
+ def parse_channel
312
+ # has to be called after parse_params
313
+ return nil if @params.empty?
314
+
315
+ case @command
316
+ when "INVITE", Constants::RPL_CHANNELMODEIS.to_s, Constants::RPL_BANLIST.to_s
317
+ @bot.channel_list.find_ensured(@params[1])
318
+ when Constants::RPL_NAMEREPLY.to_s
319
+ @bot.channel_list.find_ensured(@params[2])
320
+ else
321
+ # Note that this will also find channels for messages that
322
+ # don't actually include a channel parameter. For example
323
+ # `QUIT :#sometext` will be interpreted as a channel. The
324
+ # alternative to the currently used heuristic would be to
325
+ # hardcode a list of commands that provide a channel argument.
326
+ ch, status = privmsg_channel_name(@params.first)
327
+ if ch.nil? && numeric_reply? && @params.size > 1
328
+ ch, status = privmsg_channel_name(@params[1])
329
+ end
330
+ if ch
331
+ return @bot.channel_list.find_ensured(ch), status
332
+ end
333
+ end
334
+ end
335
+
336
+ def privmsg_channel_name(s)
337
+ chantypes = @bot.irc.isupport["CHANTYPES"]
338
+ statusmsg = @bot.irc.isupport["STATUSMSG"]
339
+ if statusmsg.include?(s[0]) && chantypes.include?(s[1])
340
+ status = @bot.irc.isupport["PREFIX"].invert[s[0]]
341
+ return s[1..-1], status
342
+ elsif chantypes.include?(s[0])
343
+ return s, nil
344
+ end
345
+ end
346
+
347
+ def parse_server
348
+ return unless @prefix
349
+ return if @prefix.match(/[@!]/)
350
+ return @prefix[/^(\S+)/, 1]
351
+ end
352
+
353
+ def parse_error
354
+ return @command.to_i if numeric_reply? && @command[/[45]\d\d/]
355
+ end
356
+
357
+ def parse_message
358
+ # has to be called after parse_params
359
+ if error?
360
+ @error.to_s
361
+ elsif regular_command?
362
+ @params.last
363
+ end
364
+ end
365
+
366
+ def parse_ctcp_message
367
+ # has to be called after parse_params
368
+ return unless ctcp?
369
+ @params.last =~ /\001(.+)\001/
370
+ $1
371
+ end
372
+
373
+ def parse_ctcp_command
374
+ # has to be called after parse_ctcp_message
375
+ return unless ctcp?
376
+ @ctcp_message.split(" ").first
377
+ end
378
+
379
+ def parse_ctcp_args
380
+ # has to be called after parse_ctcp_message
381
+ return unless ctcp?
382
+ @ctcp_message.split(" ")[1..-1]
383
+ end
384
+
385
+ def parse_action_message
386
+ # has to be called after parse_ctcp_message
387
+ return nil unless action?
388
+ @ctcp_message.split(" ", 2).last
389
+ end
390
+
391
+ end
392
+ end