cakewalk 3.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 +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