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