grinch 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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