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,913 @@
1
+ require "timeout"
2
+ require "net/protocol"
3
+ require "cakewalk/network"
4
+
5
+ module Cakewalk
6
+ # This class manages the connection to the IRC server. That includes
7
+ # processing incoming and outgoing messages, creating Ruby objects
8
+ # and invoking plugins.
9
+ class IRC
10
+ include Helpers
11
+
12
+ # @return [ISupport]
13
+ attr_reader :isupport
14
+
15
+ # @return [Bot]
16
+ attr_reader :bot
17
+
18
+ # @return [Network] The detected network
19
+ attr_reader :network
20
+
21
+ def initialize(bot)
22
+ @bot = bot
23
+ @isupport = ISupport.new
24
+ end
25
+
26
+ # @return [TCPSocket]
27
+ # @api private
28
+ # @since 2.0.0
29
+ def socket
30
+ @socket.io
31
+ end
32
+
33
+ # @api private
34
+ # @return [void]
35
+ # @since 2.0.0
36
+ def setup
37
+ @registration = []
38
+ @network = Network.new(:unknown, :unknown)
39
+ @whois_updates = {}
40
+ @in_lists = Set.new
41
+ end
42
+
43
+ # @api private
44
+ # @return [Boolean] True if the connection could be established
45
+ def connect
46
+ tcp_socket = nil
47
+
48
+ begin
49
+ Timeout::timeout(@bot.config.timeouts.connect) do
50
+ tcp_socket = TCPSocket.new(@bot.config.server, @bot.config.port, @bot.config.local_host)
51
+ end
52
+ rescue Timeout::Error
53
+ @bot.loggers.warn("Timed out while connecting")
54
+ return false
55
+ rescue SocketError => e
56
+ @bot.loggers.warn("Could not connect to the IRC server. Please check your network: #{e.message}")
57
+ return false
58
+ rescue => e
59
+ @bot.loggers.exception(e)
60
+ return false
61
+ end
62
+
63
+ if @bot.config.ssl.use
64
+ setup_ssl(tcp_socket)
65
+ else
66
+ @socket = tcp_socket
67
+ end
68
+
69
+ @socket = Net::BufferedIO.new(@socket)
70
+ @socket.read_timeout = @bot.config.timeouts.read
71
+ @queue = MessageQueue.new(@socket, @bot)
72
+
73
+ return true
74
+ end
75
+
76
+ # @api private
77
+ # @return [void]
78
+ # @since 2.0.0
79
+ def setup_ssl(socket)
80
+ # require openssl in this method so the bot doesn't break for
81
+ # people who don't have SSL but don't want to use SSL anyway.
82
+ require 'openssl'
83
+
84
+ ssl_context = OpenSSL::SSL::SSLContext.new
85
+
86
+ if @bot.config.ssl.is_a?(Configuration::SSL)
87
+ if @bot.config.ssl.client_cert
88
+ ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(@bot.config.ssl.client_cert))
89
+ ssl_context.key = OpenSSL::PKey::RSA.new(File.read(@bot.config.ssl.client_cert))
90
+ end
91
+
92
+ ssl_context.ca_path = @bot.config.ssl.ca_path
93
+ ssl_context.verify_mode = @bot.config.ssl.verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
94
+ else
95
+ ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
96
+ end
97
+ @bot.loggers.info "Using SSL with #{@bot.config.server}:#{@bot.config.port}"
98
+
99
+ @socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
100
+ @socket.sync = true
101
+ @socket.connect
102
+ end
103
+
104
+ # @api private
105
+ # @return [void]
106
+ # @since 2.0.0
107
+ def send_cap_ls
108
+ send "CAP LS"
109
+ end
110
+
111
+ # @api private
112
+ # @return [void]
113
+ # @since 2.0.0
114
+ def send_cap_req
115
+ caps = [:"away-notify", :"multi-prefix", :sasl, :"twitch.tv/tags"] & @network.capabilities
116
+
117
+ # InspIRCd doesn't respond to empty REQs, so send an END in that
118
+ # case.
119
+ if caps.size > 0
120
+ send "CAP REQ :" + caps.join(" ")
121
+ else
122
+ send_cap_end
123
+ end
124
+ end
125
+
126
+ # @since 2.0.0
127
+ # @api private
128
+ # @return [void]
129
+ def send_cap_end
130
+ send "CAP END"
131
+ end
132
+
133
+ # @api private
134
+ # @return [void]
135
+ # @since 2.0.0
136
+ def send_login
137
+ send "PASS #{@bot.config.password}" if @bot.config.password
138
+ send "NICK #{@bot.generate_next_nick!}"
139
+ send "USER #{@bot.config.user} 0 * :#{@bot.config.realname}"
140
+ end
141
+
142
+ # @api private
143
+ # @return [Thread] the reading thread
144
+ # @since 2.0.0
145
+ def start_reading_thread
146
+ Thread.new do
147
+ begin
148
+ while line = @socket.readline
149
+ rescue_exception do
150
+ line = Cakewalk::Utilities::Encoding.encode_incoming(line, @bot.config.encoding)
151
+ parse line
152
+ end
153
+ end
154
+ rescue Timeout::Error
155
+ @bot.loggers.warn "Connection timed out."
156
+ rescue EOFError
157
+ @bot.loggers.warn "Lost connection."
158
+ rescue => e
159
+ @bot.loggers.exception(e)
160
+ end
161
+
162
+ @socket.close
163
+ @bot.handlers.dispatch(:disconnect)
164
+ # FIXME won't we kill all :disconnect handlers here? prolly
165
+ # not, as they have 10 seconds to finish. that should be
166
+ # plenty of time
167
+ @bot.handlers.stop_all
168
+ end
169
+ end
170
+
171
+ # @api private
172
+ # @return [Thread] the sending thread
173
+ # @since 2.0.0
174
+ def start_sending_thread
175
+ Thread.new do
176
+ rescue_exception do
177
+ @queue.process!
178
+ end
179
+ end
180
+ end
181
+
182
+ # @api private
183
+ # @return [Thread] The ping thread.
184
+ # @since 2.0.0
185
+ def start_ping_thread
186
+ Thread.new do
187
+ while true
188
+ sleep @bot.config.ping_interval
189
+ # PING requires a single argument. In our case the value
190
+ # doesn't matter though.
191
+ send("PING 0")
192
+ end
193
+ end
194
+ end
195
+
196
+ # @since 2.0.0
197
+ def send_sasl
198
+ if @bot.config.sasl.username && @sasl_current_method = @sasl_remaining_methods.pop
199
+ @bot.loggers.info "[SASL] Trying to authenticate with #{@sasl_current_method.mechanism_name}"
200
+ send "AUTHENTICATE #{@sasl_current_method.mechanism_name}"
201
+ else
202
+ send_cap_end
203
+ end
204
+ end
205
+
206
+ # Establish a connection.
207
+ #
208
+ # @return [void]
209
+ # @since 2.0.0
210
+ def start
211
+ setup
212
+ if connect
213
+ @sasl_remaining_methods = @bot.config.sasl.mechanisms.reverse
214
+ send_cap_ls
215
+ send_login
216
+
217
+ reading_thread = start_reading_thread
218
+ sending_thread = start_sending_thread
219
+ ping_thread = start_ping_thread
220
+
221
+ reading_thread.join
222
+ sending_thread.kill
223
+ ping_thread.kill
224
+ end
225
+ end
226
+
227
+ # @api private
228
+ # @return [void]
229
+ def parse(input)
230
+ return if input.chomp.empty?
231
+ @bot.loggers.incoming(input)
232
+
233
+ msg = Message.new(input, @bot)
234
+ events = [[:catchall]]
235
+
236
+ if ["001", "002", "003", "004", "422"].include?(msg.command)
237
+ @registration << msg.command
238
+ if registered?
239
+ events << [:connect]
240
+ @bot.last_connection_was_successful = true
241
+ on_connect(msg, events)
242
+ end
243
+ end
244
+
245
+ if ["PRIVMSG", "NOTICE"].include?(msg.command)
246
+ events << [:ctcp] if msg.ctcp?
247
+ if msg.channel?
248
+ events << [:channel]
249
+ else
250
+ events << [:private]
251
+ end
252
+
253
+ if msg.command == "PRIVMSG"
254
+ events << [:message]
255
+ end
256
+
257
+ if msg.action?
258
+ events << [:action]
259
+ end
260
+ end
261
+
262
+ meth = "on_#{msg.command.downcase}"
263
+ __send__(meth, msg, events) if respond_to?(meth, true)
264
+
265
+ if msg.error?
266
+ events << [:error]
267
+ end
268
+
269
+ events << [msg.command.downcase.to_sym]
270
+
271
+ msg.events = events.map(&:first)
272
+ events.each do |event, *args|
273
+ @bot.handlers.dispatch(event, msg, *args)
274
+ end
275
+ end
276
+
277
+ # @return [Boolean] true if we successfully registered yet
278
+ def registered?
279
+ (("001".."004").to_a - @registration).empty? || @registration.include?("422")
280
+ end
281
+
282
+ # Send a message to the server.
283
+ # @param [String] msg
284
+ # @return [void]
285
+ def send(msg)
286
+ @queue.queue(msg)
287
+ end
288
+
289
+ private
290
+ def set_leaving_user(message, user, events)
291
+ events << [:leaving, user]
292
+ end
293
+
294
+ # @since 2.0.0
295
+ def detect_network(msg, event)
296
+ old_network = @network
297
+ new_network = nil
298
+ new_ircd = nil
299
+ case event
300
+ when "002"
301
+ if msg.params.last =~ /^Your host is .+?, running version (.+)$/
302
+ case $1
303
+ when /\+snircd\(/
304
+ new_ircd = :snircd
305
+ when /^u[\d\.]+$/
306
+ new_ircd = :ircu
307
+ when /^(.+?)-?\d+/
308
+ new_ircd = $1.downcase.to_sym
309
+ end
310
+ end
311
+ when "004"
312
+ if msg.params == %w{irc.tinyspeck.com IRC-SLACK gateway}
313
+ new_network = :slack
314
+ new_ircd = :slack
315
+ end
316
+ when "005"
317
+ case @isupport["NETWORK"]
318
+ when nil
319
+ else
320
+ new_network = @isupport["NETWORK"].downcase.to_sym
321
+ end
322
+ end
323
+
324
+ new_network ||= old_network.name
325
+ new_ircd ||= old_network.ircd
326
+
327
+ if old_network.unknown_ircd? && new_ircd != :unknown
328
+ @bot.loggers.info "Detected IRCd: #{new_ircd}"
329
+ end
330
+ if !old_network.unknown_ircd? && new_ircd != old_network.ircd
331
+ @bot.loggers.info "Detected different IRCd: #{old_network.ircd} -> #{new_ircd}"
332
+ end
333
+ if old_network.unknown_network? && new_network != :unknown
334
+ @bot.loggers.info "Detected network: #{new_network}"
335
+ end
336
+ if !old_network.unknown_network? && new_network != old_network.name
337
+ @bot.loggers.info "Detected different network: #{old_network.name} -> #{new_network}"
338
+ end
339
+
340
+ @network.name = new_network
341
+ @network.ircd = new_ircd
342
+ end
343
+
344
+ def process_ban_mode(msg, events, param, direction)
345
+ mask = param
346
+ ban = Ban.new(mask, msg.user, Time.now)
347
+
348
+ if direction == :add
349
+ msg.channel.bans_unsynced << ban
350
+ events << [:ban, ban]
351
+ else
352
+ msg.channel.bans_unsynced.delete_if {|b| b.mask == ban.mask}
353
+ events << [:unban, ban]
354
+ end
355
+ end
356
+
357
+ def process_owner_mode(msg, events, param, direction)
358
+ owner = User(param)
359
+ if direction == :add
360
+ msg.channel.owners_unsynced << owner unless msg.channel.owners_unsynced.include?(owner)
361
+ events << [:owner, owner]
362
+ else
363
+ msg.channel.owners_unsynced.delete(owner)
364
+ events << [:deowner, owner]
365
+ end
366
+ end
367
+
368
+ def update_whois(user, data)
369
+ @whois_updates[user] ||= {}
370
+ @whois_updates[user].merge!(data)
371
+ end
372
+
373
+ # @since 2.0.0
374
+ def on_away(msg, events)
375
+ if msg.message.to_s.empty?
376
+ # unaway
377
+ msg.user.sync(:away, nil, true)
378
+ events << [:unaway]
379
+ else
380
+ # away
381
+ msg.user.sync(:away, msg.message, true)
382
+ events << [:away]
383
+ end
384
+ end
385
+
386
+ # @since 2.0.0
387
+ def on_cap(msg, events)
388
+ case msg.params[1]
389
+ when "LS"
390
+ @network.capabilities.concat msg.message.split(" ").map(&:to_sym)
391
+ send_cap_req
392
+ when "ACK"
393
+ if @network.capabilities.include?(:sasl)
394
+ send_sasl
395
+ else
396
+ send_cap_end
397
+ end
398
+ when "NAK"
399
+ send_cap_end
400
+ end
401
+ end
402
+
403
+ # @since 2.0.0
404
+ def on_connect(msg, events)
405
+ @bot.modes = @bot.config.modes
406
+ end
407
+
408
+ def on_join(msg, events)
409
+ if msg.user == @bot
410
+ @bot.channels << msg.channel
411
+ msg.channel.sync_modes
412
+ end
413
+ msg.channel.add_user(msg.user)
414
+ msg.user.online = true
415
+ end
416
+
417
+ def on_kick(msg, events)
418
+ target = User(msg.params[1])
419
+ if target == @bot
420
+ @bot.channels.delete(msg.channel)
421
+ end
422
+ msg.channel.remove_user(target)
423
+
424
+ set_leaving_user(msg, target, events)
425
+ end
426
+
427
+ def on_kill(msg, events)
428
+ user = User(msg.params[1])
429
+
430
+ @bot.channel_list.each do |channel|
431
+ channel.remove_user(user)
432
+ end
433
+
434
+ user.unsync_all
435
+ user.online = false
436
+
437
+ set_leaving_user(msg, user, events)
438
+ end
439
+
440
+ # @version 1.1.0
441
+ def on_mode(msg, events)
442
+ if msg.channel?
443
+ parse_channel_modes(msg, events)
444
+ return
445
+ end
446
+ if msg.params.first == bot.nick
447
+ parse_bot_modes(msg)
448
+ end
449
+ end
450
+
451
+ def parse_channel_modes(msg, events)
452
+ add_and_remove = @bot.irc.isupport["CHANMODES"]["A"] + @bot.irc.isupport["CHANMODES"]["B"] + @bot.irc.isupport["PREFIX"].keys
453
+
454
+ param_modes = {
455
+ :add => @bot.irc.isupport["CHANMODES"]["C"] + add_and_remove,
456
+ :remove => add_and_remove
457
+ }
458
+
459
+
460
+ modes, err = ModeParser.parse_modes(msg.params[1], msg.params[2..-1], param_modes)
461
+ if err != nil
462
+ if @network.ircd != :slack || !err.is_a?(ModeParser::TooManyParametersError)
463
+ raise Exceptions::InvalidModeString, err
464
+ end
465
+ end
466
+ modes.each do |direction, mode, param|
467
+ if @bot.irc.isupport["PREFIX"].keys.include?(mode)
468
+ target = User(param)
469
+
470
+ # (un)set a user-mode
471
+ if direction == :add
472
+ msg.channel.users[target] << mode unless msg.channel.users[target].include?(mode)
473
+ else
474
+ msg.channel.users[target].delete mode
475
+ end
476
+
477
+ user_events = {
478
+ "o" => "op",
479
+ "v" => "voice",
480
+ "h" => "halfop"
481
+ }
482
+ if user_events.has_key?(mode)
483
+ event = (direction == :add ? "" : "de") + user_events[mode]
484
+ events << [event.to_sym, target]
485
+ end
486
+ elsif @bot.irc.isupport["CHANMODES"]["A"].include?(mode)
487
+ case mode
488
+ when "b"
489
+ process_ban_mode(msg, events, param, direction)
490
+ when "q"
491
+ process_owner_mode(msg, events, param, direction) if @network.owner_list_mode
492
+ else
493
+ raise Exceptions::UnsupportedMode, mode
494
+ end
495
+ else
496
+ # channel options
497
+ if direction == :add
498
+ msg.channel.modes_unsynced[mode] = param.nil? ? true : param
499
+ else
500
+ msg.channel.modes_unsynced.delete(mode)
501
+ end
502
+ end
503
+ end
504
+
505
+ events << [:mode_change, modes]
506
+ end
507
+
508
+ def parse_bot_modes(msg)
509
+ modes, err = ModeParser.parse_modes(msg.params[1], msg.params[2..-1])
510
+ if err != nil
511
+ raise Exceptions::InvalidModeString, err
512
+ end
513
+ modes.each do |direction, mode, _|
514
+ if direction == :add
515
+ @bot.modes << mode unless @bot.modes.include?(mode)
516
+ else
517
+ @bot.modes.delete(mode)
518
+ end
519
+ end
520
+ end
521
+
522
+ def on_nick(msg, events)
523
+ if msg.user == @bot
524
+ # @bot.set_nick msg.params.last
525
+ target = @bot
526
+ else
527
+ target = msg.user
528
+ end
529
+
530
+ target.update_nick(msg.params.last)
531
+ target.online = true
532
+ end
533
+
534
+ def on_part(msg, events)
535
+ msg.channel.remove_user(msg.user)
536
+ msg.user.channels_unsynced.delete msg.channel
537
+
538
+ if msg.user == @bot
539
+ @bot.channels.delete(msg.channel)
540
+ end
541
+
542
+ set_leaving_user(msg, msg.user, events)
543
+ end
544
+
545
+ def on_ping(msg, events)
546
+ send "PONG :#{msg.params.first}"
547
+ end
548
+
549
+ def on_topic(msg, events)
550
+ msg.channel.sync(:topic, msg.params[1])
551
+ end
552
+
553
+ def on_quit(msg, events)
554
+ @bot.channel_list.each do |channel|
555
+ channel.remove_user(msg.user)
556
+ end
557
+ msg.user.unsync_all
558
+ msg.user.online = false
559
+
560
+ set_leaving_user(msg, msg.user, events)
561
+
562
+ if msg.message.downcase == "excess flood" && msg.user == @bot
563
+ @bot.warn ["Looks like your bot has been kicked because of excess flood.",
564
+ "If you haven't modified the throttling options manually, please file a bug report at https://github.com/petru/cakewalk/issues and include the following information:",
565
+ "- Server: #{@bot.config.server}",
566
+ "- Messages per second: #{@bot.config.messages_per_second}",
567
+ "- Server queue size: #{@bot.config.server_queue_size}"]
568
+ end
569
+ end
570
+
571
+ # @since 2.0.0
572
+ def on_privmsg(msg, events)
573
+ if msg.user
574
+ msg.user.online = true
575
+ end
576
+
577
+ if msg.message =~ /^\001DCC SEND (?:"([^"]+)"|(\S+)) (\S+) (\d+)(?: (\d+))?\001$/
578
+ process_dcc_send($1 || $2, $3, $4, $5, msg, events)
579
+ end
580
+ end
581
+
582
+ # @since 2.0.0
583
+ def process_dcc_send(filename, ip, port, size, m, events)
584
+ if ip =~ /^\d+$/
585
+ # If ip is a single integer, assume it's a specification
586
+ # compliant IPv4 address in network byte order. If it's any
587
+ # other string, assume that it's a valid IPv4 or IPv6 address.
588
+ # If it's not valid, let someone higher up the chain notice
589
+ # that.
590
+ ip = ip.to_i
591
+ ip = [24, 16, 8, 0].collect {|b| (ip >> b) & 255}.join('.')
592
+ end
593
+
594
+ port = port.to_i
595
+ size = size.to_i
596
+
597
+ @bot.loggers.info "DCC: Incoming DCC SEND: File name: %s - Size: %dB - IP: %s - Port: %d" % [filename, size, ip, port]
598
+
599
+ dcc = DCC::Incoming::Send.new(user: m.user, filename: filename, size: size, ip: ip, port: port)
600
+ events << [:dcc_send, dcc]
601
+ end
602
+
603
+ # @since 2.0.0
604
+ def on_001(msg, events)
605
+ # Ensure that we know our real, possibly truncated or otherwise
606
+ # modified nick.
607
+ @bot.set_nick msg.params.first
608
+ end
609
+
610
+ # @since 2.0.0
611
+ def on_002(msg, events)
612
+ detect_network(msg, "002")
613
+ end
614
+
615
+ # @since 2.2.6
616
+ def on_004(msg, events)
617
+ detect_network(msg, "004")
618
+ end
619
+
620
+ def on_005(msg, events)
621
+ # ISUPPORT
622
+ @isupport.parse(*msg.params[1..-2].map {|v| v.split(" ")}.flatten)
623
+ detect_network(msg, "005")
624
+ end
625
+
626
+ # @since 2.0.0
627
+ def on_301(msg, events)
628
+ # RPL_AWAY
629
+ user = User(msg.params[1])
630
+ away = msg.params.last
631
+
632
+ if @whois_updates[user]
633
+ update_whois(user, {:away => away})
634
+ end
635
+ end
636
+
637
+ # @since 1.1.0
638
+ def on_307(msg, events)
639
+ # RPL_WHOISREGNICK
640
+ user = User(msg.params[1])
641
+ update_whois(user, {:registered => true})
642
+ end
643
+
644
+ def on_311(msg, events)
645
+ # RPL_WHOISUSER
646
+ user = User(msg.params[1])
647
+ update_whois(user, {
648
+ :user => msg.params[2],
649
+ :host => msg.params[3],
650
+ :realname => msg.params[5],
651
+ })
652
+ end
653
+
654
+ def on_313(msg, events)
655
+ # RPL_WHOISOPERATOR
656
+ user = User(msg.params[1])
657
+ update_whois(user, {:oper? => true})
658
+ end
659
+
660
+ def on_317(msg, events)
661
+ # RPL_WHOISIDLE
662
+ user = User(msg.params[1])
663
+ update_whois(user, {
664
+ :idle => msg.params[2].to_i,
665
+ :signed_on_at => Time.at(msg.params[3].to_i),
666
+ })
667
+ end
668
+
669
+ def on_318(msg, events)
670
+ # RPL_ENDOFWHOIS
671
+ user = User(msg.params[1])
672
+ user.end_of_whois(@whois_updates[user])
673
+ @whois_updates.delete user
674
+ end
675
+
676
+ def on_319(msg, events)
677
+ # RPL_WHOISCHANNELS
678
+ user = User(msg.params[1])
679
+ channels = msg.params[2].scan(/[#{@isupport["CHANTYPES"].join}][^ ]+/o).map {|c| Channel(c) }
680
+ update_whois(user, {:channels => channels})
681
+ end
682
+
683
+ def on_324(msg, events)
684
+ # RPL_CHANNELMODEIS
685
+ modes = {}
686
+ arguments = msg.params[3..-1]
687
+
688
+ msg.params[2][1..-1].split("").each do |mode|
689
+ if (@isupport["CHANMODES"]["B"] + @isupport["CHANMODES"]["C"]).include?(mode)
690
+ modes[mode] = arguments.shift
691
+ else
692
+ modes[mode] = true
693
+ end
694
+ end
695
+
696
+ msg.channel.sync(:modes, modes, false)
697
+ end
698
+
699
+ def on_330(msg, events)
700
+ # RPL_WHOISACCOUNT
701
+ user = User(msg.params[1])
702
+ authname = msg.params[2]
703
+ update_whois(user, {:authname => authname})
704
+ end
705
+
706
+ def on_331(msg, events)
707
+ # RPL_NOTOPIC
708
+ msg.channel.sync(:topic, "")
709
+ end
710
+
711
+ def on_332(msg, events)
712
+ # RPL_TOPIC
713
+ msg.channel.sync(:topic, msg.params[2])
714
+ end
715
+
716
+ def on_352(msg, events)
717
+ # RPL_WHOREPLY
718
+ # "<channel> <user> <host> <server> <nick> <H|G>[*][@|+] :<hopcount> <real name>"
719
+ _, channel, user, host, _, nick, _, hopsrealname = msg.params
720
+ _, realname = hopsrealname.split(" ", 2)
721
+ channel = Channel(channel)
722
+ user_object = User(nick)
723
+ user_object.sync(:user, user, true)
724
+ user_object.sync(:host, host, true)
725
+ user_object.sync(:realname, realname, true)
726
+ end
727
+
728
+ def on_354(msg, events)
729
+ # RPL_WHOSPCRPL
730
+ # We are using the following format: %acfhnru
731
+
732
+ # _ user host nick f account realame
733
+ # :calcium.libera.chat 354 dominikh_ ~a ip-88-152-125-117.unitymediagroup.de dominikh_ H 0 :d
734
+ # :calcium.libera.chat 354 dominikh_ ~FiXato fixato.net FiXato H FiXato :FiXato, using WeeChat -- More? See: http://twitter
735
+ # :calcium.libera.chat 354 dominikh_ ~dominikh cakewalk/developer/dominikh dominikh H DominikH :dominikh
736
+ # :calcium.libera.chat 354 dominikh_ ~oddmunds s21-04214.dsl.no.powertech.net oddmunds H 0 :oddmunds
737
+
738
+ _, channel, user, host, nick, _, account, realname = msg.params
739
+ channel = Channel(channel)
740
+ user_object = User(nick)
741
+ user_object.sync(:user, user, true)
742
+ user_object.sync(:host, host, true)
743
+ user_object.sync(:realname, realname, true)
744
+ user_object.sync(:authname, account == "0" ? nil : account, true)
745
+ end
746
+
747
+ def on_353(msg, events)
748
+ # RPL_NAMEREPLY
749
+ unless @in_lists.include?(:names)
750
+ msg.channel.clear_users
751
+ end
752
+ @in_lists << :names
753
+
754
+ msg.params[3].split(" ").each do |user|
755
+ m = user.match(/^([#{@isupport["PREFIX"].values.join}]+)/)
756
+ if m
757
+ prefixes = m[1].split("").map {|s| @isupport["PREFIX"].key(s)}
758
+ nick = user[prefixes.size..-1]
759
+ else
760
+ nick = user
761
+ prefixes = []
762
+ end
763
+ user = User(nick)
764
+ user.online = true
765
+ msg.channel.add_user(user, prefixes)
766
+ user.channels_unsynced << msg.channel unless user.channels_unsynced.include?(msg.channel)
767
+ end
768
+ end
769
+
770
+ def on_366(msg, events)
771
+ # RPL_ENDOFNAMES
772
+ @in_lists.delete :names
773
+ msg.channel.mark_as_synced(:users)
774
+ end
775
+
776
+ # @version 2.0.0
777
+ def on_367(msg, events)
778
+ # RPL_BANLIST
779
+ unless @in_lists.include?(:bans)
780
+ msg.channel.bans_unsynced.clear
781
+ end
782
+ @in_lists << :bans
783
+
784
+ mask = msg.params[2]
785
+
786
+ if msg.params[3]
787
+ by = User(msg.params[3].split("!").first)
788
+ else
789
+ by = nil
790
+ end
791
+
792
+ at = Time.at(msg.params[4].to_i)
793
+ ban = Ban.new(mask, by, at)
794
+ msg.channel.bans_unsynced << ban
795
+ end
796
+
797
+ def on_368(msg, events)
798
+ # RPL_ENDOFBANLIST
799
+ if @in_lists.include?(:bans)
800
+ @in_lists.delete :bans
801
+ else
802
+ # we never received a ban, yet an end of list => no bans
803
+ msg.channel.bans_unsynced.clear
804
+ end
805
+
806
+ msg.channel.mark_as_synced(:bans)
807
+ end
808
+
809
+ def on_386(msg, events)
810
+ # RPL_QLIST
811
+ unless @in_lists.include?(:owners)
812
+ msg.channel.owners_unsynced.clear
813
+ end
814
+ @in_lists << :owners
815
+
816
+ owner = User(msg.params[2])
817
+ msg.channel.owners_unsynced << owner
818
+ end
819
+
820
+ def on_387(msg, events)
821
+ # RPL_ENDOFQLIST
822
+ if @in_lists.include?(:owners)
823
+ @in_lists.delete :owners
824
+ else
825
+ #we never received an owner, yet an end of list -> no owners
826
+ msg.channel.owners_unsynced.clear
827
+ end
828
+
829
+ msg.channel.mark_as_synced(:owners)
830
+ end
831
+
832
+ def on_396(msg, events)
833
+ # RPL_HOSTHIDDEN
834
+ # note: designed for freenode
835
+ User(msg.params[0]).sync(:host, msg.params[1], true)
836
+ end
837
+
838
+ def on_401(msg, events)
839
+ # ERR_NOSUCHNICK
840
+ if user = @bot.user_list.find(msg.params[1])
841
+ update_whois(user, {:unknown? => true})
842
+ end
843
+ end
844
+
845
+ def on_402(msg, events)
846
+ # ERR_NOSUCHSERVER
847
+
848
+ if user = @bot.user_list.find(msg.params[1]) # not _ensured, we only want a user that already exists
849
+ user.end_of_whois({:unknown? => true})
850
+ @whois_updates.delete user
851
+ # TODO freenode specific, test on other IRCd
852
+ end
853
+ end
854
+
855
+ def on_433(msg, events)
856
+ # ERR_NICKNAMEINUSE
857
+ @bot.nick = @bot.generate_next_nick!(msg.params[1])
858
+ end
859
+
860
+ def on_671(msg, events)
861
+ user = User(msg.params[1])
862
+ update_whois(user, {:secure? => true})
863
+ end
864
+
865
+ # @since 2.0.0
866
+ def on_730(msg, events)
867
+ # RPL_MONONLINE
868
+ msg.params.last.split(",").each do |mask|
869
+ user = User(Mask.new(mask).nick)
870
+ # User is responsible for emitting an event
871
+ user.online = true
872
+ end
873
+ end
874
+
875
+ # @since 2.0.0
876
+ def on_731(msg, events)
877
+ # RPL_MONOFFLINE
878
+ msg.params.last.split(",").each do |nick|
879
+ user = User(nick)
880
+ # User is responsible for emitting an event
881
+ user.online = false
882
+ end
883
+ end
884
+
885
+ # @since 2.0.0
886
+ def on_734(msg, events)
887
+ # ERR_MONLISTFULL
888
+ user = User(msg.params[2])
889
+ user.monitored = false
890
+ end
891
+
892
+ # @since 2.0.0
893
+ def on_903(msg, events)
894
+ # SASL authentication successful
895
+ @bot.loggers.info "[SASL] SASL authentication with #{@sasl_current_method.mechanism_name} successful"
896
+ send_cap_end
897
+ end
898
+
899
+ # @since 2.0.0
900
+ def on_904(msg, events)
901
+ # SASL authentication failed
902
+ @bot.loggers.info "[SASL] SASL authentication with #{@sasl_current_method.mechanism_name} failed"
903
+ send_sasl
904
+ end
905
+
906
+ # @since 2.0.0
907
+ def on_authenticate(msg, events)
908
+ send "AUTHENTICATE " + @sasl_current_method.generate(@bot.config.sasl.username,
909
+ @bot.config.sasl.password,
910
+ msg.params.last)
911
+ end
912
+ end
913
+ end