cakewalk 3.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 +169 -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/cakewalk/ban.rb +50 -0
  41. data/lib/cakewalk/bot.rb +479 -0
  42. data/lib/cakewalk/cached_list.rb +19 -0
  43. data/lib/cakewalk/callback.rb +20 -0
  44. data/lib/cakewalk/channel.rb +463 -0
  45. data/lib/cakewalk/channel_list.rb +29 -0
  46. data/lib/cakewalk/configuration/bot.rb +48 -0
  47. data/lib/cakewalk/configuration/dcc.rb +16 -0
  48. data/lib/cakewalk/configuration/plugins.rb +41 -0
  49. data/lib/cakewalk/configuration/sasl.rb +19 -0
  50. data/lib/cakewalk/configuration/ssl.rb +19 -0
  51. data/lib/cakewalk/configuration/timeouts.rb +14 -0
  52. data/lib/cakewalk/configuration.rb +73 -0
  53. data/lib/cakewalk/constants.rb +533 -0
  54. data/lib/cakewalk/dcc/dccable_object.rb +37 -0
  55. data/lib/cakewalk/dcc/incoming/send.rb +147 -0
  56. data/lib/cakewalk/dcc/incoming.rb +1 -0
  57. data/lib/cakewalk/dcc/outgoing/send.rb +122 -0
  58. data/lib/cakewalk/dcc/outgoing.rb +1 -0
  59. data/lib/cakewalk/dcc.rb +12 -0
  60. data/lib/cakewalk/exceptions.rb +46 -0
  61. data/lib/cakewalk/formatting.rb +125 -0
  62. data/lib/cakewalk/handler.rb +118 -0
  63. data/lib/cakewalk/handler_list.rb +90 -0
  64. data/lib/cakewalk/helpers.rb +231 -0
  65. data/lib/cakewalk/irc.rb +913 -0
  66. data/lib/cakewalk/isupport.rb +98 -0
  67. data/lib/cakewalk/log_filter.rb +21 -0
  68. data/lib/cakewalk/logger/formatted_logger.rb +97 -0
  69. data/lib/cakewalk/logger/zcbot_logger.rb +22 -0
  70. data/lib/cakewalk/logger.rb +168 -0
  71. data/lib/cakewalk/logger_list.rb +85 -0
  72. data/lib/cakewalk/mask.rb +69 -0
  73. data/lib/cakewalk/message.rb +391 -0
  74. data/lib/cakewalk/message_queue.rb +107 -0
  75. data/lib/cakewalk/mode_parser.rb +76 -0
  76. data/lib/cakewalk/network.rb +89 -0
  77. data/lib/cakewalk/open_ended_queue.rb +26 -0
  78. data/lib/cakewalk/pattern.rb +65 -0
  79. data/lib/cakewalk/plugin.rb +515 -0
  80. data/lib/cakewalk/plugin_list.rb +38 -0
  81. data/lib/cakewalk/rubyext/float.rb +3 -0
  82. data/lib/cakewalk/rubyext/module.rb +26 -0
  83. data/lib/cakewalk/rubyext/string.rb +33 -0
  84. data/lib/cakewalk/sasl/dh_blowfish.rb +71 -0
  85. data/lib/cakewalk/sasl/diffie_hellman.rb +47 -0
  86. data/lib/cakewalk/sasl/mechanism.rb +6 -0
  87. data/lib/cakewalk/sasl/plain.rb +26 -0
  88. data/lib/cakewalk/sasl.rb +34 -0
  89. data/lib/cakewalk/syncable.rb +83 -0
  90. data/lib/cakewalk/target.rb +199 -0
  91. data/lib/cakewalk/timer.rb +145 -0
  92. data/lib/cakewalk/user.rb +488 -0
  93. data/lib/cakewalk/user_list.rb +87 -0
  94. data/lib/cakewalk/utilities/deprecation.rb +16 -0
  95. data/lib/cakewalk/utilities/encoding.rb +37 -0
  96. data/lib/cakewalk/utilities/kernel.rb +13 -0
  97. data/lib/cakewalk/version.rb +4 -0
  98. data/lib/cakewalk.rb +5 -0
  99. metadata +140 -0
@@ -0,0 +1,479 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'socket'
3
+ require "thread"
4
+ require "ostruct"
5
+ require "cakewalk/rubyext/module"
6
+ require "cakewalk/rubyext/string"
7
+ require "cakewalk/rubyext/float"
8
+
9
+ require "cakewalk/exceptions"
10
+
11
+ require "cakewalk/handler"
12
+ require "cakewalk/helpers"
13
+
14
+ require "cakewalk/logger_list"
15
+ require "cakewalk/logger"
16
+
17
+ require "cakewalk/logger/formatted_logger"
18
+ require "cakewalk/syncable"
19
+ require "cakewalk/message"
20
+ require "cakewalk/message_queue"
21
+ require "cakewalk/irc"
22
+ require "cakewalk/target"
23
+ require "cakewalk/channel"
24
+ require "cakewalk/user"
25
+ require "cakewalk/constants"
26
+ require "cakewalk/callback"
27
+ require "cakewalk/ban"
28
+ require "cakewalk/mask"
29
+ require "cakewalk/isupport"
30
+ require "cakewalk/plugin"
31
+ require "cakewalk/pattern"
32
+ require "cakewalk/mode_parser"
33
+ require "cakewalk/dcc"
34
+ require "cakewalk/sasl"
35
+
36
+ require "cakewalk/handler_list"
37
+ require "cakewalk/cached_list"
38
+ require "cakewalk/channel_list"
39
+ require "cakewalk/user_list"
40
+ require "cakewalk/plugin_list"
41
+
42
+ require "cakewalk/timer"
43
+ require "cakewalk/formatting"
44
+
45
+ require "cakewalk/configuration"
46
+ require "cakewalk/configuration/bot"
47
+ require "cakewalk/configuration/plugins"
48
+ require "cakewalk/configuration/ssl"
49
+ require "cakewalk/configuration/timeouts"
50
+ require "cakewalk/configuration/dcc"
51
+ require "cakewalk/configuration/sasl"
52
+
53
+ module Cakewalk
54
+ # @attr nick
55
+ # @version 2.0.0
56
+ class Bot < User
57
+ include Helpers
58
+
59
+
60
+ # @return [Configuration::Bot]
61
+ # @version 2.0.0
62
+ attr_reader :config
63
+
64
+ # The underlying IRC connection
65
+ #
66
+ # @return [IRC]
67
+ attr_reader :irc
68
+
69
+ # The logger list containing all loggers
70
+ #
71
+ # @return [LoggerList]
72
+ # @since 2.0.0
73
+ attr_accessor :loggers
74
+
75
+ # @return [Array<Channel>] All channels the bot currently is in
76
+ attr_reader :channels
77
+
78
+ # @return [PluginList] The {PluginList} giving access to
79
+ # (un)loading plugins
80
+ # @version 2.0.0
81
+ attr_reader :plugins
82
+
83
+ # @return [Boolean] whether the bot is in the process of disconnecting
84
+ attr_reader :quitting
85
+
86
+ # @return [UserList] All {User users} the bot knows about.
87
+ # @see UserList
88
+ # @since 1.1.0
89
+ attr_reader :user_list
90
+
91
+ # @return [ChannelList] All {Channel channels} the bot knows about.
92
+ # @see ChannelList
93
+ # @since 1.1.0
94
+ attr_reader :channel_list
95
+
96
+ # @return [Boolean]
97
+ # @api private
98
+ attr_accessor :last_connection_was_successful
99
+
100
+ # @return [Callback]
101
+ # @api private
102
+ attr_reader :callback
103
+
104
+ # The {HandlerList}, providing access to all registered plugins
105
+ # and plugin manipulation as well as {HandlerList#dispatch calling handlers}.
106
+ #
107
+ # @return [HandlerList]
108
+ # @see HandlerList
109
+ # @since 2.0.0
110
+ attr_reader :handlers
111
+
112
+ # The bot's modes.
113
+ #
114
+ # @return [Array<String>]
115
+ # @since 2.0.0
116
+ attr_reader :modes
117
+
118
+ # @group Helper methods
119
+
120
+ # Define helper methods in the context of the bot.
121
+ #
122
+ # @yield Expects a block containing method definitions
123
+ # @return [void]
124
+ def helpers(&b)
125
+ @callback.instance_eval(&b)
126
+ end
127
+
128
+ # Since Cakewalk uses threads, all handlers can be run
129
+ # simultaneously, even the same handler multiple times. This also
130
+ # means, that your code has to be thread-safe. Most of the time,
131
+ # this is not a problem, but if you are accessing stored data, you
132
+ # will most likely have to synchronize access to it. Instead of
133
+ # managing all mutexes yourself, Cakewalk provides a synchronize
134
+ # method, which takes a name and block.
135
+ #
136
+ # Synchronize blocks with the same name share the same mutex,
137
+ # which means that only one of them will be executed at a time.
138
+ #
139
+ # @param [String, Symbol] name a name for the synchronize block.
140
+ # @return [void]
141
+ # @yield
142
+ #
143
+ # @example
144
+ # configure do |c|
145
+ # …
146
+ # @i = 0
147
+ # end
148
+ #
149
+ # on :channel, /^start counting!/ do
150
+ # synchronize(:my_counter) do
151
+ # 10.times do
152
+ # val = @i
153
+ # # at this point, another thread might've incremented :i already.
154
+ # # this thread wouldn't know about it, though.
155
+ # @i = val + 1
156
+ # end
157
+ # end
158
+ # end
159
+ def synchronize(name, &block)
160
+ # Must run the default block +/ fetch in a thread safe way in order to
161
+ # ensure we always get the same mutex for a given name.
162
+ semaphore = @semaphores_mutex.synchronize { @semaphores[name] }
163
+ semaphore.synchronize(&block)
164
+ end
165
+
166
+ # @endgroup
167
+
168
+ # @group Events &amp; Plugins
169
+
170
+ # Registers a handler.
171
+ #
172
+ # @param [String, Symbol, Integer] event the event to match. For a
173
+ # list of available events, check the {file:docs/events.md Events
174
+ # documentation}.
175
+ #
176
+ # @param [Regexp, Pattern, String] regexp every message of the
177
+ # right event will be checked against this argument and the event
178
+ # will only be called if it matches
179
+ #
180
+ # @param [Array<Object>] args Arguments that should be passed to
181
+ # the block, additionally to capture groups of the regexp.
182
+ #
183
+ # @yieldparam [Array<String>] args each capture group of the regex will
184
+ # be one argument to the block.
185
+ #
186
+ # @return [Handler] The handlers that have been registered
187
+ def on(event, regexp = //, *args, &block)
188
+ event = event.to_s.to_sym
189
+
190
+ pattern = case regexp
191
+ when Pattern
192
+ regexp
193
+ when Regexp
194
+ Pattern.new(nil, regexp, nil)
195
+ else
196
+ if event == :ctcp
197
+ Pattern.generate(:ctcp, regexp)
198
+ else
199
+ Pattern.new(/^/, /#{Regexp.escape(regexp.to_s)}/, /$/)
200
+ end
201
+ end
202
+
203
+ handler = Handler.new(self, event, pattern, {args: args, execute_in_callback: true}, &block)
204
+ @handlers.register(handler)
205
+
206
+ return handler
207
+ end
208
+
209
+ # @endgroup
210
+ # @group Bot Control
211
+
212
+ # This method is used to set a bot's options. It indeed does
213
+ # nothing else but yielding {Bot#config}, but it makes for a nice DSL.
214
+ #
215
+ # @yieldparam [Struct] config the bot's config
216
+ # @return [void]
217
+ def configure
218
+ yield @config
219
+ end
220
+
221
+ # Disconnects from the server.
222
+ #
223
+ # @param [String] message The quit message to send while quitting
224
+ # @return [void]
225
+ def quit(message = nil)
226
+ @quitting = true
227
+ command = message ? "QUIT :#{message}" : "QUIT"
228
+
229
+ @irc.send command
230
+ end
231
+
232
+ # Connects the bot to a server.
233
+ #
234
+ # @param [Boolean] plugins Automatically register plugins from
235
+ # `@config.plugins.plugins`?
236
+ # @return [void]
237
+ def start(plugins = true)
238
+ @reconnects = 0
239
+ @plugins.register_plugins(@config.plugins.plugins) if plugins
240
+
241
+ begin
242
+ @user_list.each do |user|
243
+ user.in_whois = false
244
+ user.unsync_all
245
+ end # reset state of all users
246
+
247
+ @channel_list.each do |channel|
248
+ channel.unsync_all
249
+ end # reset state of all channels
250
+
251
+ @channels = [] # reset list of channels the bot is in
252
+
253
+ @join_handler.unregister if @join_handler
254
+ @join_timer.stop if @join_timer
255
+
256
+ join_lambda = lambda { @config.channels.each { |channel| Channel(channel).join }}
257
+
258
+ if @config.delay_joins.is_a?(Symbol)
259
+ @join_handler = join_handler = on(@config.delay_joins) {
260
+ join_handler.unregister
261
+ join_lambda.call
262
+ }
263
+ else
264
+ @join_timer = Timer.new(self, interval: @config.delay_joins, shots: 1) {
265
+ join_lambda.call
266
+ }
267
+ end
268
+
269
+ @modes = []
270
+
271
+ @loggers.info "Connecting to #{@config.server}:#{@config.port}"
272
+ @irc = IRC.new(self)
273
+ @irc.start
274
+
275
+ if @config.reconnect && !@quitting
276
+ # double the delay for each unsuccesful reconnection attempt
277
+ if @last_connection_was_successful
278
+ @reconnects = 0
279
+ @last_connection_was_successful = false
280
+ else
281
+ @reconnects += 1
282
+ end
283
+
284
+ # Throttle reconnect attempts
285
+ wait = 2**@reconnects
286
+ wait = @config.max_reconnect_delay if wait > @config.max_reconnect_delay
287
+ @loggers.info "Waiting #{wait} seconds before reconnecting"
288
+ start_time = Time.now
289
+ while !@quitting && (Time.now - start_time) < wait
290
+ sleep 1
291
+ end
292
+ end
293
+ end while @config.reconnect and not @quitting
294
+ end
295
+
296
+ # @endgroup
297
+ # @group Channel Control
298
+
299
+ # Join a channel.
300
+ #
301
+ # @param [String, Channel] channel either the name of a channel or a {Channel} object
302
+ # @param [String] key optionally the key of the channel
303
+ # @return [Channel] The joined channel
304
+ # @see Channel#join
305
+ def join(channel, key = nil)
306
+ channel = Channel(channel)
307
+ channel.join(key)
308
+
309
+ channel
310
+ end
311
+
312
+ # Part a channel.
313
+ #
314
+ # @param [String, Channel] channel either the name of a channel or a {Channel} object
315
+ # @param [String] reason an optional reason/part message
316
+ # @return [Channel] The channel that was left
317
+ # @see Channel#part
318
+ def part(channel, reason = nil)
319
+ channel = Channel(channel)
320
+ channel.part(reason)
321
+
322
+ channel
323
+ end
324
+
325
+ # @endgroup
326
+
327
+ # @return [Boolean] True if the bot reports ISUPPORT violations as
328
+ # exceptions.
329
+ def strict?
330
+ @config.strictness == :strict
331
+ end
332
+
333
+ # @yield
334
+ def initialize(&b)
335
+ @config = Configuration::Bot.new
336
+
337
+ @loggers = LoggerList.new
338
+ @loggers << Logger::FormattedLogger.new($stderr, level: @config.default_logger_level)
339
+ @handlers = HandlerList.new
340
+ @semaphores_mutex = Mutex.new
341
+ @semaphores = Hash.new { |h, k| h[k] = Mutex.new }
342
+ @callback = Callback.new(self)
343
+ @channels = []
344
+ @quitting = false
345
+ @modes = []
346
+
347
+ @user_list = UserList.new(self)
348
+ @channel_list = ChannelList.new(self)
349
+ @plugins = PluginList.new(self)
350
+
351
+ @join_handler = nil
352
+ @join_timer = nil
353
+
354
+ super(nil, self)
355
+ instance_eval(&b) if block_given?
356
+ end
357
+
358
+ # @since 2.0.0
359
+ # @return [self]
360
+ # @api private
361
+ def bot
362
+ # This method is needed for the Helpers interface
363
+ self
364
+ end
365
+
366
+ # Sets a mode on the bot.
367
+ #
368
+ # @param [String] mode
369
+ # @return [void]
370
+ # @since 2.0.0
371
+ # @see Bot#modes
372
+ # @see Bot#unset_mode
373
+ def set_mode(mode)
374
+ @modes << mode unless @modes.include?(mode)
375
+ @irc.send "MODE #{nick} +#{mode}"
376
+ end
377
+
378
+ # Unsets a mode on the bot.
379
+ #
380
+ # @param [String] mode
381
+ # @return [void]
382
+ # @since 2.0.0
383
+ def unset_mode(mode)
384
+ @modes.delete(mode)
385
+ @irc.send "MODE #{nick} -#{mode}"
386
+ end
387
+
388
+ # @since 2.0.0
389
+ def modes=(modes)
390
+ (@modes - modes).each do |mode|
391
+ unset_mode(mode)
392
+ end
393
+
394
+ (modes - @modes).each do |mode|
395
+ set_mode(mode)
396
+ end
397
+ end
398
+
399
+ # Used for updating the bot's nick from within the IRC parser.
400
+ #
401
+ # @param [String] nick
402
+ # @api private
403
+ # @return [String]
404
+ def set_nick(nick)
405
+ @name = nick
406
+ end
407
+
408
+ # The bot's nickname.
409
+ # @overload nick=(new_nick)
410
+ # @raise [Exceptions::NickTooLong] Raised if the bot is
411
+ # operating in {#strict? strict mode} and the new nickname is
412
+ # too long
413
+ # @return [String]
414
+ # @overload nick
415
+ # @return [String]
416
+ # @return [String]
417
+ def nick
418
+ @name
419
+ end
420
+
421
+ def nick=(new_nick)
422
+ if new_nick.size > @irc.isupport["NICKLEN"] && strict?
423
+ raise Exceptions::NickTooLong, new_nick
424
+ end
425
+ @config.nick = new_nick
426
+ @irc.send "NICK #{new_nick}"
427
+ end
428
+
429
+ # Gain oper privileges.
430
+ #
431
+ # @param [String] password
432
+ # @param [String] user The username to use. Defaults to the bot's
433
+ # nickname
434
+ # @since 2.1.0
435
+ # @return [void]
436
+ def oper(password, user = nil)
437
+ user ||= self.nick
438
+ @irc.send "OPER #{user} #{password}"
439
+ end
440
+
441
+ # Try to create a free nick, first by cycling through all
442
+ # available alternatives and then by appending underscores.
443
+ #
444
+ # @param [String] base The base nick to start trying from
445
+ # @api private
446
+ # @return [String]
447
+ # @since 2.0.0
448
+ def generate_next_nick!(base = nil)
449
+ nicks = @config.nicks || []
450
+
451
+ if base
452
+ # if `base` is not in our list of nicks to try, assume that it's
453
+ # custom and just append an underscore
454
+ if !nicks.include?(base)
455
+ new_nick = base + "_"
456
+ else
457
+ # if we have a base, try the next nick or append an
458
+ # underscore if no more nicks are left
459
+ new_index = nicks.index(base) + 1
460
+ if nicks[new_index]
461
+ new_nick = nicks[new_index]
462
+ else
463
+ new_nick = base + "_"
464
+ end
465
+ end
466
+ else
467
+ # if we have no base, try the first possible nick
468
+ new_nick = @config.nicks ? @config.nicks.first : @config.nick
469
+ end
470
+
471
+ @config.nick = new_nick
472
+ end
473
+
474
+ # @return [String]
475
+ def inspect
476
+ "#<Bot nick=#{@name.inspect}>"
477
+ end
478
+ end
479
+ end
@@ -0,0 +1,19 @@
1
+ module Cakewalk
2
+ # @api private
3
+ # @since 2.0.0
4
+ # @version 1.1.0
5
+ # @note In prior versions, this class was called CacheManager
6
+ class CachedList
7
+ include Enumerable
8
+
9
+ def initialize(bot)
10
+ @bot = bot
11
+ @cache = {}
12
+ @mutex = Mutex.new
13
+ end
14
+
15
+ def each(&block)
16
+ @cache.each_value(&block)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module Cakewalk
2
+ # Class used for encapsulating handlers to prevent them from
3
+ # overwriting instance variables in {Bot}
4
+ #
5
+ # @api private
6
+ class Callback
7
+ include Helpers
8
+
9
+ # @return [Bot]
10
+ attr_reader :bot
11
+ def initialize(bot)
12
+ @bot = bot
13
+ end
14
+
15
+ # (see Bot#synchronize)
16
+ def synchronize(name, &block)
17
+ @bot.synchronize(name, &block)
18
+ end
19
+ end
20
+ end