mcinch 2.4.1

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