cinch 1.1.3 → 2.0.0.pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +1 -0
- data/README.md +3 -3
- data/docs/bot_options.md +435 -0
- data/docs/changes.md +440 -0
- data/docs/common_mistakes.md +35 -0
- data/docs/common_tasks.md +47 -0
- data/docs/encodings.md +67 -0
- data/docs/events.md +272 -0
- data/docs/logging.md +5 -0
- data/docs/migrating.md +267 -0
- data/docs/readme.md +18 -0
- data/examples/plugins/custom_prefix.rb +1 -1
- data/examples/plugins/dice_roll.rb +38 -0
- data/examples/plugins/lambdas.rb +1 -1
- data/examples/plugins/memo.rb +16 -10
- data/examples/plugins/url_shorten.rb +1 -0
- data/lib/cinch.rb +5 -60
- data/lib/cinch/ban.rb +13 -7
- data/lib/cinch/bot.rb +228 -403
- data/lib/cinch/{cache_manager.rb → cached_list.rb} +5 -1
- data/lib/cinch/callback.rb +3 -0
- data/lib/cinch/channel.rb +119 -195
- data/lib/cinch/{channel_manager.rb → channel_list.rb} +6 -3
- data/lib/cinch/configuration.rb +73 -0
- data/lib/cinch/configuration/bot.rb +47 -0
- data/lib/cinch/configuration/dcc.rb +16 -0
- data/lib/cinch/configuration/plugins.rb +41 -0
- data/lib/cinch/configuration/sasl.rb +17 -0
- data/lib/cinch/configuration/ssl.rb +19 -0
- data/lib/cinch/configuration/storage.rb +37 -0
- data/lib/cinch/configuration/timeouts.rb +14 -0
- data/lib/cinch/constants.rb +531 -369
- data/lib/cinch/dcc.rb +12 -0
- data/lib/cinch/dcc/dccable_object.rb +37 -0
- data/lib/cinch/dcc/incoming.rb +1 -0
- data/lib/cinch/dcc/incoming/send.rb +131 -0
- data/lib/cinch/dcc/outgoing.rb +1 -0
- data/lib/cinch/dcc/outgoing/send.rb +115 -0
- data/lib/cinch/exceptions.rb +8 -1
- data/lib/cinch/formatting.rb +106 -0
- data/lib/cinch/handler.rb +104 -0
- data/lib/cinch/handler_list.rb +86 -0
- data/lib/cinch/helpers.rb +167 -10
- data/lib/cinch/irc.rb +525 -110
- data/lib/cinch/isupport.rb +11 -9
- data/lib/cinch/logger.rb +168 -0
- data/lib/cinch/logger/formatted_logger.rb +72 -55
- data/lib/cinch/logger/zcbot_logger.rb +9 -24
- data/lib/cinch/logger_list.rb +62 -0
- data/lib/cinch/mask.rb +19 -10
- data/lib/cinch/message.rb +94 -28
- data/lib/cinch/message_queue.rb +70 -28
- data/lib/cinch/mode_parser.rb +6 -1
- data/lib/cinch/network.rb +104 -0
- data/lib/cinch/{rubyext/queue.rb → open_ended_queue.rb} +8 -1
- data/lib/cinch/pattern.rb +24 -4
- data/lib/cinch/plugin.rb +352 -177
- data/lib/cinch/plugin_list.rb +35 -0
- data/lib/cinch/rubyext/float.rb +3 -0
- data/lib/cinch/rubyext/module.rb +7 -0
- data/lib/cinch/rubyext/string.rb +9 -0
- data/lib/cinch/sasl.rb +34 -0
- data/lib/cinch/sasl/dh_blowfish.rb +71 -0
- data/lib/cinch/sasl/diffie_hellman.rb +47 -0
- data/lib/cinch/sasl/mechanism.rb +6 -0
- data/lib/cinch/sasl/plain.rb +26 -0
- data/lib/cinch/storage.rb +62 -0
- data/lib/cinch/storage/null.rb +12 -0
- data/lib/cinch/storage/yaml.rb +96 -0
- data/lib/cinch/syncable.rb +13 -1
- data/lib/cinch/target.rb +144 -0
- data/lib/cinch/timer.rb +145 -0
- data/lib/cinch/user.rb +169 -225
- data/lib/cinch/{user_manager.rb → user_list.rb} +7 -2
- data/lib/cinch/utilities/deprecation.rb +12 -0
- data/lib/cinch/utilities/encoding.rb +54 -0
- data/lib/cinch/utilities/kernel.rb +13 -0
- data/lib/cinch/utilities/string.rb +13 -0
- data/lib/cinch/version.rb +4 -0
- metadata +88 -47
- data/lib/cinch/logger/logger.rb +0 -44
- data/lib/cinch/logger/null_logger.rb +0 -18
- data/lib/cinch/rubyext/infinity.rb +0 -1
@@ -0,0 +1,86 @@
|
|
1
|
+
require "thread"
|
2
|
+
require "set"
|
3
|
+
require "cinch/cached_list"
|
4
|
+
|
5
|
+
module Cinch
|
6
|
+
# @since 2.0.0
|
7
|
+
class HandlerList
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@handlers = Hash.new {|h,k| h[k] = []}
|
12
|
+
@mutex = Mutex.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def register(handler)
|
16
|
+
@mutex.synchronize do
|
17
|
+
handler.bot.loggers.debug "[on handler] Registering handler with pattern `#{handler.pattern.inspect}`, reacting on `#{handler.event}`"
|
18
|
+
@handlers[handler.event] << handler
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [Handler, Array<Handler>] *handlers The handlers to unregister
|
23
|
+
# @return [void]
|
24
|
+
# @see Handler#unregister
|
25
|
+
def unregister(*handlers)
|
26
|
+
@mutex.synchronize do
|
27
|
+
handlers.each do |handler|
|
28
|
+
@handlers[handler.event].delete(handler)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @api private
|
34
|
+
# @return [Array<Handler>]
|
35
|
+
def find(type, msg = nil)
|
36
|
+
if handlers = @handlers[type]
|
37
|
+
if msg.nil?
|
38
|
+
return handlers
|
39
|
+
end
|
40
|
+
|
41
|
+
handlers = handlers.select { |handler|
|
42
|
+
msg.match(handler.pattern.to_r(msg), type)
|
43
|
+
}.group_by {|handler| handler.group}
|
44
|
+
|
45
|
+
handlers.values_at(*(handlers.keys - [nil])).map(&:first) + (handlers[nil] || [])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# @param [Symbol] event The event type
|
50
|
+
# @param [Message, nil] msg The message which is responsible for
|
51
|
+
# and attached to the event, or nil.
|
52
|
+
# @param [Array] *arguments A list of additional arguments to pass
|
53
|
+
# to event handlers
|
54
|
+
# @return [void]
|
55
|
+
def dispatch(event, msg = nil, *arguments)
|
56
|
+
if handlers = find(event, msg)
|
57
|
+
already_run = Set.new
|
58
|
+
handlers.each do |handler|
|
59
|
+
next if already_run.include?(handler.block)
|
60
|
+
already_run << handler.block
|
61
|
+
# calling Message#match multiple times is not a problem
|
62
|
+
# because we cache the result
|
63
|
+
if msg
|
64
|
+
captures = msg.match(handler.pattern.to_r(msg), event).captures
|
65
|
+
else
|
66
|
+
captures = []
|
67
|
+
end
|
68
|
+
|
69
|
+
handler.call(msg, captures, arguments)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# @yield [handler] Yields all registered handlers
|
75
|
+
# @yieldparam [Handler] handler
|
76
|
+
# @return [void]
|
77
|
+
def each(&block)
|
78
|
+
@handlers.values.flatten.each(&block)
|
79
|
+
end
|
80
|
+
|
81
|
+
# @api private
|
82
|
+
def stop_all
|
83
|
+
each { |h| h.stop }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/cinch/helpers.rb
CHANGED
@@ -1,21 +1,178 @@
|
|
1
|
+
# TODO @since for all methods
|
1
2
|
module Cinch
|
3
|
+
# The Helpers module contains a number of methods whose purpose is
|
4
|
+
# to make writing plugins easier by hiding parts of the API. The
|
5
|
+
# {#Channel} helper, for example, provides an easier way for turning
|
6
|
+
# a String object into a {Channel} object than directly using
|
7
|
+
# {ChannelList}: Compare `Channel("#some_channel")` with
|
8
|
+
# `bot.channel_list.find_ensured("#some_channel")`.
|
9
|
+
#
|
10
|
+
# The Helpers module automatically gets included in all plugins.
|
2
11
|
module Helpers
|
12
|
+
# @group Type casts
|
13
|
+
|
14
|
+
# Helper method for turning a String into a {Target} object.
|
15
|
+
#
|
16
|
+
# @param [String] target a target name
|
17
|
+
# @return [Target] a {Target} object
|
18
|
+
# @example
|
19
|
+
# on :message, /^message (.+)$/ do |m, target|
|
20
|
+
# Target(target).send "hi!"
|
21
|
+
# end
|
22
|
+
def Target(target)
|
23
|
+
return target if target.is_a?(Target)
|
24
|
+
Target.new(target, bot)
|
25
|
+
end
|
26
|
+
|
3
27
|
# Helper method for turning a String into a {Channel} object.
|
4
28
|
#
|
5
|
-
# @param
|
6
|
-
# @return
|
7
|
-
# @example
|
8
|
-
|
9
|
-
|
29
|
+
# @param [String] channel a channel name
|
30
|
+
# @return [Channel] a {Channel} object
|
31
|
+
# @example
|
32
|
+
# on :message, /^please join (#.+)$/ do |m, target|
|
33
|
+
# Channel(target).join
|
34
|
+
# end
|
35
|
+
def Channel(channel)
|
36
|
+
return channel if channel.is_a?(Channel)
|
37
|
+
bot.channel_list.find_ensured(channel)
|
10
38
|
end
|
11
39
|
|
12
40
|
# Helper method for turning a String into an {User} object.
|
13
41
|
#
|
14
|
-
# @param
|
15
|
-
# @return
|
16
|
-
# @example
|
17
|
-
|
18
|
-
|
42
|
+
# @param [String] user a user's nickname
|
43
|
+
# @return [User] an {User} object
|
44
|
+
# @example
|
45
|
+
# on :message, /^tell me everything about (.+)$/ do |m, target|
|
46
|
+
# user = User(target)
|
47
|
+
# m.reply "%s is named %s and connects from %s" % [user.nick, user.name, user.host]
|
48
|
+
# end
|
49
|
+
def User(user)
|
50
|
+
return user if user.is_a?(User)
|
51
|
+
if user == bot.nick
|
52
|
+
bot
|
53
|
+
else
|
54
|
+
bot.user_list.find_ensured(user)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# @example Used as a class method in a plugin
|
59
|
+
# timer 5, method: :some_method
|
60
|
+
# def some_method
|
61
|
+
# Channel("#cinch-bots").send(Time.now.to_s)
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# @example Used as an instance method in a plugin
|
65
|
+
# match "start timer"
|
66
|
+
# def execute(m)
|
67
|
+
# Timer(5) { puts "timer fired" }
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# @example Used as an instance method in a traditional `on` handler
|
71
|
+
# on :message, "start timer" do
|
72
|
+
# Timer(5) { puts "timer fired" }
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# @param [Number] interval Interval in seconds
|
76
|
+
# @param [Proc] block A proc to execute
|
77
|
+
# @option options [Symbol] :method (:timer) Method to call (only
|
78
|
+
# if no proc is provided)
|
79
|
+
# @option options [Boolean] :threaded (true) Call method in a
|
80
|
+
# thread?
|
81
|
+
# @option options [Number] :shots (Float::INFINITY) How often
|
82
|
+
# should the timer fire?
|
83
|
+
# @option options [Boolean] :start_automatically (true) If true,
|
84
|
+
# the timer will automatically start after the bot finished
|
85
|
+
# connecting.
|
86
|
+
# @option options [Boolean] :stop_automaticall (true) If true, the
|
87
|
+
# timer will automatically stop when the bot disconnects.
|
88
|
+
# @return [Timer]
|
89
|
+
# @since 2.0.0
|
90
|
+
def Timer(interval, options = {}, &block)
|
91
|
+
options = {:method => :timer, :threaded => true, :interval => interval}.merge(options)
|
92
|
+
block ||= self.method(options[:method])
|
93
|
+
timer = Cinch::Timer.new(bot, options, &block)
|
94
|
+
timer.start
|
95
|
+
|
96
|
+
timer
|
97
|
+
end
|
98
|
+
|
99
|
+
# @endgroup
|
100
|
+
|
101
|
+
# @group Logging
|
102
|
+
|
103
|
+
# Use this method to automatically log exceptions to the loggers.
|
104
|
+
#
|
105
|
+
# @example
|
106
|
+
# def my_method
|
107
|
+
# rescue_exception do
|
108
|
+
# something_that_might_raise()
|
109
|
+
# end
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# @return [void]
|
113
|
+
# @since 2.0.0
|
114
|
+
def rescue_exception
|
115
|
+
begin
|
116
|
+
yield
|
117
|
+
rescue => e
|
118
|
+
bot.loggers.exception(e)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# (see Logger#log)
|
123
|
+
def log(messages, event = :debug, level = event)
|
124
|
+
@bot.loggers.log(messages, event, level)
|
125
|
+
end
|
126
|
+
|
127
|
+
# (see Logger#debug)
|
128
|
+
def debug(message)
|
129
|
+
log(message, :debug)
|
130
|
+
end
|
131
|
+
|
132
|
+
# (see Logger#error)
|
133
|
+
def error(message)
|
134
|
+
log(message, :error)
|
135
|
+
end
|
136
|
+
|
137
|
+
# (see Logger#fatal)
|
138
|
+
def fatal(message)
|
139
|
+
log(message, :fatal)
|
19
140
|
end
|
141
|
+
|
142
|
+
# (see Logger#info)
|
143
|
+
def info(message)
|
144
|
+
log(message, :info)
|
145
|
+
end
|
146
|
+
|
147
|
+
# (see Logger#warn)
|
148
|
+
def warn(message)
|
149
|
+
log(message, :warn)
|
150
|
+
end
|
151
|
+
|
152
|
+
# (see Logger#incoming)
|
153
|
+
def incoming(message)
|
154
|
+
log(message, :incoming, :log)
|
155
|
+
end
|
156
|
+
|
157
|
+
# (see Logger#outgoing)
|
158
|
+
def outgoing(message)
|
159
|
+
log(message, :outgoing, :log)
|
160
|
+
end
|
161
|
+
|
162
|
+
# (see Logger#exception)
|
163
|
+
def exception(e)
|
164
|
+
log(e.message, :exception, :error)
|
165
|
+
end
|
166
|
+
# @endgroup
|
167
|
+
|
168
|
+
# @group Formatting
|
169
|
+
|
170
|
+
# (see Formatting.format)
|
171
|
+
def Format(*args)
|
172
|
+
Formatting.format(*args)
|
173
|
+
end
|
174
|
+
alias_method :Color, :Format
|
175
|
+
|
176
|
+
# @endgroup
|
20
177
|
end
|
21
178
|
end
|
data/lib/cinch/irc.rb
CHANGED
@@ -1,121 +1,227 @@
|
|
1
1
|
require "timeout"
|
2
2
|
require "net/protocol"
|
3
|
+
require "cinch/network"
|
3
4
|
|
4
5
|
module Cinch
|
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.
|
5
9
|
class IRC
|
10
|
+
include Helpers
|
11
|
+
|
6
12
|
# @return [ISupport]
|
7
13
|
attr_reader :isupport
|
14
|
+
|
15
|
+
# @return [Bot]
|
16
|
+
attr_reader :bot
|
17
|
+
|
18
|
+
# @return [Network] The detected network
|
19
|
+
attr_reader :network
|
20
|
+
|
8
21
|
def initialize(bot)
|
9
22
|
@bot = bot
|
10
23
|
@isupport = ISupport.new
|
11
24
|
end
|
12
25
|
|
13
|
-
#
|
14
|
-
#
|
15
|
-
# @
|
16
|
-
def
|
17
|
-
@
|
26
|
+
# @return [TCPSocket]
|
27
|
+
# @api private
|
28
|
+
# @since 2.0.0
|
29
|
+
def socket
|
30
|
+
@socket.io
|
31
|
+
end
|
18
32
|
|
33
|
+
# @api private
|
34
|
+
# @return [void]
|
35
|
+
# @since 2.0.0
|
36
|
+
def setup
|
37
|
+
@registration = []
|
38
|
+
@network = Network.new(:unknown, :unknown)
|
19
39
|
@whois_updates = Hash.new {|h, k| h[k] = {}}
|
20
40
|
@in_lists = Set.new
|
41
|
+
end
|
21
42
|
|
43
|
+
# @api private
|
44
|
+
# @return [Boolean] True if the connection could be established
|
45
|
+
def connect
|
22
46
|
tcp_socket = nil
|
47
|
+
|
23
48
|
begin
|
24
49
|
Timeout::timeout(@bot.config.timeouts.connect) do
|
25
50
|
tcp_socket = TCPSocket.new(@bot.config.server, @bot.config.port, @bot.config.local_host)
|
26
51
|
end
|
27
52
|
rescue Timeout::Error
|
28
|
-
@bot.
|
29
|
-
return
|
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
|
30
58
|
rescue => e
|
31
|
-
@bot.
|
32
|
-
return
|
59
|
+
@bot.loggers.exception(e)
|
60
|
+
return false
|
33
61
|
end
|
34
62
|
|
35
|
-
if @bot.config.ssl
|
36
|
-
|
63
|
+
if @bot.config.ssl.use
|
64
|
+
setup_ssl(tcp_socket)
|
65
|
+
else
|
66
|
+
@socket = tcp_socket
|
37
67
|
end
|
38
68
|
|
39
|
-
|
40
|
-
|
69
|
+
@socket = Net::BufferedIO.new(@socket)
|
70
|
+
@socket.read_timeout = @bot.config.timeouts.read
|
71
|
+
@queue = MessageQueue.new(@socket, @bot)
|
41
72
|
|
42
|
-
|
73
|
+
return true
|
74
|
+
end
|
43
75
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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))
|
53
90
|
end
|
54
|
-
@bot.logger.debug "Using SSL with #{@bot.config.server}:#{@bot.config.port}"
|
55
91
|
|
56
|
-
|
57
|
-
|
58
|
-
@socket.connect
|
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
|
59
94
|
else
|
60
|
-
|
95
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
61
96
|
end
|
97
|
+
@bot.loggers.info "Using SSL with #{@bot.config.server}:#{@bot.config.port}"
|
62
98
|
|
63
|
-
@socket
|
64
|
-
@socket.
|
99
|
+
@socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
|
100
|
+
@socket.sync = true
|
101
|
+
@socket.connect
|
102
|
+
end
|
65
103
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
+
send "CAP REQ :" + ([:"away-notify", :"multi-prefix", :sasl] & @network.capabilities).join(" ")
|
116
|
+
end
|
117
|
+
|
118
|
+
# @since 2.0.0
|
119
|
+
# @api private
|
120
|
+
# @return [void]
|
121
|
+
def send_cap_end
|
122
|
+
send "CAP END"
|
123
|
+
end
|
124
|
+
|
125
|
+
# @api private
|
126
|
+
# @return [void]
|
127
|
+
# @since 2.0.0
|
128
|
+
def send_login
|
129
|
+
send "PASS #{@bot.config.password}" if @bot.config.password
|
130
|
+
send "NICK #{@bot.generate_next_nick!}"
|
131
|
+
send "USER #{@bot.config.user} 0 * :#{@bot.config.realname}"
|
132
|
+
end
|
70
133
|
|
71
|
-
|
134
|
+
# @api private
|
135
|
+
# @return [Thread] the reading thread
|
136
|
+
# @since 2.0.0
|
137
|
+
def start_reading_thread
|
138
|
+
Thread.new do
|
72
139
|
begin
|
73
140
|
while line = @socket.readline
|
74
|
-
|
75
|
-
line = Cinch.encode_incoming(line, @bot.config.encoding)
|
141
|
+
rescue_exception do
|
142
|
+
line = Cinch::Utilities::Encoding.encode_incoming(line, @bot.config.encoding)
|
76
143
|
parse line
|
77
|
-
rescue => e
|
78
|
-
@bot.logger.log_exception(e)
|
79
144
|
end
|
80
145
|
end
|
81
146
|
rescue Timeout::Error
|
82
|
-
@bot.
|
147
|
+
@bot.loggers.warn "Connection timed out."
|
83
148
|
rescue EOFError
|
84
|
-
@bot.
|
149
|
+
@bot.loggers.warn "Lost connection."
|
85
150
|
rescue => e
|
86
|
-
@bot.
|
151
|
+
@bot.loggers.exception(e)
|
87
152
|
end
|
88
153
|
|
89
154
|
@socket.close
|
90
|
-
@bot.dispatch(:disconnect)
|
91
|
-
|
155
|
+
@bot.handlers.dispatch(:disconnect)
|
156
|
+
# FIXME won't we kill all :disconnect handlers here? prolly
|
157
|
+
# not, as they have 10 seconds to finish. that should be
|
158
|
+
# plenty of time
|
159
|
+
@bot.handlers.stop_all
|
92
160
|
end
|
161
|
+
end
|
93
162
|
|
94
|
-
|
95
|
-
|
163
|
+
# @api private
|
164
|
+
# @return [Thread] the sending thread
|
165
|
+
# @since 2.0.0
|
166
|
+
def start_sending_thread
|
167
|
+
Thread.new do
|
168
|
+
rescue_exception do
|
96
169
|
@queue.process!
|
97
|
-
rescue => e
|
98
|
-
@bot.logger.log_exception(e)
|
99
170
|
end
|
100
171
|
end
|
172
|
+
end
|
101
173
|
|
102
|
-
|
174
|
+
# @api private
|
175
|
+
# @return [Thread] The ping thread.
|
176
|
+
# @since 2.0.0
|
177
|
+
def start_ping_thread
|
178
|
+
Thread.new do
|
103
179
|
while true
|
104
180
|
sleep @bot.config.ping_interval
|
105
|
-
|
106
|
-
|
181
|
+
# PING requires a single argument. In our case the value
|
182
|
+
# doesn't matter though.
|
183
|
+
send("PING 0")
|
107
184
|
end
|
108
185
|
end
|
186
|
+
end
|
109
187
|
|
110
|
-
|
111
|
-
|
112
|
-
|
188
|
+
# @since 2.0.0
|
189
|
+
def send_sasl
|
190
|
+
if @bot.config.sasl.username && @sasl_current_method = @sasl_remaining_methods.pop
|
191
|
+
@bot.loggers.info "[SASL] Trying to authenticate with #{@sasl_current_method.mechanism_name}"
|
192
|
+
send "AUTHENTICATE #{@sasl_current_method.mechanism_name}"
|
193
|
+
else
|
194
|
+
send_cap_end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Establish a connection.
|
199
|
+
#
|
200
|
+
# @return [void]
|
201
|
+
# @since 2.0.0
|
202
|
+
def start
|
203
|
+
setup
|
204
|
+
if connect
|
205
|
+
@sasl_remaining_methods = [SASL::Plain, SASL::DH_Blowfish]
|
206
|
+
send_cap_ls
|
207
|
+
send_login
|
208
|
+
|
209
|
+
reading_thread = start_reading_thread
|
210
|
+
sending_thread = start_sending_thread
|
211
|
+
ping_thread = start_ping_thread
|
212
|
+
|
213
|
+
reading_thread.join
|
214
|
+
sending_thread.kill
|
215
|
+
ping_thread.kill
|
216
|
+
end
|
113
217
|
end
|
114
218
|
|
115
219
|
# @api private
|
116
220
|
# @return [void]
|
117
221
|
def parse(input)
|
118
|
-
|
222
|
+
return if input.chomp.empty?
|
223
|
+
@bot.loggers.incoming(input)
|
224
|
+
|
119
225
|
msg = Message.new(input, @bot)
|
120
226
|
events = [[:catchall]]
|
121
227
|
|
@@ -125,7 +231,9 @@ module Cinch
|
|
125
231
|
events << [:connect]
|
126
232
|
@bot.last_connection_was_successful = true
|
127
233
|
end
|
128
|
-
|
234
|
+
end
|
235
|
+
|
236
|
+
if ["PRIVMSG", "NOTICE"].include?(msg.command)
|
129
237
|
events << [:ctcp] if msg.ctcp?
|
130
238
|
if msg.channel?
|
131
239
|
events << [:channel]
|
@@ -134,24 +242,28 @@ module Cinch
|
|
134
242
|
end
|
135
243
|
|
136
244
|
if msg.command == "PRIVMSG"
|
137
|
-
events << [:message]
|
245
|
+
events << [:message]
|
138
246
|
else
|
139
247
|
events << [:notice]
|
140
248
|
end
|
141
|
-
else
|
142
|
-
meth = "on_#{msg.command.downcase}"
|
143
|
-
__send__(meth, msg, events) if respond_to?(meth, true)
|
144
249
|
|
145
|
-
if msg.
|
146
|
-
events << [:
|
147
|
-
else
|
148
|
-
events << [msg.command.downcase.to_sym]
|
250
|
+
if msg.action?
|
251
|
+
events << [:action]
|
149
252
|
end
|
150
253
|
end
|
151
254
|
|
152
|
-
msg.
|
255
|
+
meth = "on_#{msg.command.downcase}"
|
256
|
+
__send__(meth, msg, events) if respond_to?(meth, true)
|
257
|
+
|
258
|
+
if msg.error?
|
259
|
+
events << [:error]
|
260
|
+
else
|
261
|
+
events << [msg.command.downcase.to_sym]
|
262
|
+
end
|
263
|
+
|
264
|
+
msg.events = events.map(&:first)
|
153
265
|
events.each do |event, *args|
|
154
|
-
@bot.dispatch(event, msg, *args)
|
266
|
+
@bot.handlers.dispatch(event, msg, *args)
|
155
267
|
end
|
156
268
|
end
|
157
269
|
|
@@ -160,52 +272,173 @@ module Cinch
|
|
160
272
|
(("001".."004").to_a - @registration).empty?
|
161
273
|
end
|
162
274
|
|
163
|
-
# Send a message.
|
275
|
+
# Send a message to the server.
|
276
|
+
# @param [String] msg
|
164
277
|
# @return [void]
|
165
|
-
def
|
278
|
+
def send(msg)
|
166
279
|
@queue.queue(msg)
|
167
280
|
end
|
168
281
|
|
169
282
|
private
|
283
|
+
def set_leaving_user(message, user, events)
|
284
|
+
events << [:leaving, user]
|
285
|
+
end
|
286
|
+
|
287
|
+
# @since 2.0.0
|
288
|
+
def detect_network(msg, event)
|
289
|
+
old_network = @network
|
290
|
+
new_network = nil
|
291
|
+
new_ircd = nil
|
292
|
+
case event
|
293
|
+
when "002"
|
294
|
+
if msg.params.last =~ /^Your host is .+?, running version (.+)$/
|
295
|
+
case $1
|
296
|
+
when /\+snircd\(/
|
297
|
+
new_ircd = :snircd
|
298
|
+
when /^u[\d\.]+$/
|
299
|
+
new_ircd = :ircu
|
300
|
+
when /^(.+?)-?\d+/
|
301
|
+
new_ircd = $1.downcase.to_sym
|
302
|
+
end
|
303
|
+
elsif msg.params.last == "Your host is jtvchat"
|
304
|
+
new_network = :jtv
|
305
|
+
new_ircd = :jtv
|
306
|
+
end
|
307
|
+
when "005"
|
308
|
+
case @isupport["NETWORK"]
|
309
|
+
when "NGameTV"
|
310
|
+
new_network = :ngametv
|
311
|
+
new_ircd = :ngametv
|
312
|
+
when nil
|
313
|
+
else
|
314
|
+
new_network = @isupport["NETWORK"].downcase.to_sym
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
new_network ||= old_network.name
|
319
|
+
new_ircd ||= old_network.ircd
|
320
|
+
|
321
|
+
if old_network.unknown_ircd? && new_ircd != :unknown
|
322
|
+
@bot.loggers.info "Detected IRCd: #{new_ircd}"
|
323
|
+
end
|
324
|
+
if !old_network.unknown_ircd? && new_ircd != old_network.ircd
|
325
|
+
@bot.loggers.info "Detected different IRCd: #{old_network.ircd} -> #{new_ircd}"
|
326
|
+
end
|
327
|
+
if old_network.unknown_network? && new_network != :unknown
|
328
|
+
@bot.loggers.info "Detected network: #{new_network}"
|
329
|
+
end
|
330
|
+
if !old_network.unknown_network? && new_network != old_network.name
|
331
|
+
@bot.loggers.info "Detected different network: #{old_network.name} -> #{new_network}"
|
332
|
+
end
|
333
|
+
|
334
|
+
@network.name = new_network
|
335
|
+
@network.ircd = new_ircd
|
336
|
+
end
|
337
|
+
|
338
|
+
def process_ban_mode(msg, events, param, direction)
|
339
|
+
mask = param
|
340
|
+
ban = Ban.new(mask, msg.user, Time.now)
|
341
|
+
|
342
|
+
if direction == :add
|
343
|
+
msg.channel.bans_unsynced << ban
|
344
|
+
events << [:ban, ban]
|
345
|
+
else
|
346
|
+
msg.channel.bans_unsynced.delete_if {|b| b.mask == ban.mask}.first
|
347
|
+
events << [:unban, ban]
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def process_owner_mode(msg, events, param, direction)
|
352
|
+
owner = User(param)
|
353
|
+
if direction == :add
|
354
|
+
msg.channel.owners_unsynced << owner unless msg.channel.owners_unsynced.include?(owner)
|
355
|
+
events << [:owner, owner]
|
356
|
+
else
|
357
|
+
msg.channel.owners_unsynced.delete(owner)
|
358
|
+
events << [:deowner, owner]
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
# @since 2.0.0
|
363
|
+
def on_away(msg, events)
|
364
|
+
if msg.message.to_s.empty?
|
365
|
+
# unaway
|
366
|
+
m.user.sync(:away, nil, true)
|
367
|
+
events << [:unaway]
|
368
|
+
else
|
369
|
+
# away
|
370
|
+
m.user.sync(:away, msg.message, true)
|
371
|
+
events << [:away]
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
# @since 2.0.0
|
376
|
+
def on_cap(msg, events)
|
377
|
+
case msg.params[1]
|
378
|
+
when "LS"
|
379
|
+
@network.capabilities.concat msg.message.split(" ").map(&:to_sym)
|
380
|
+
send_cap_req
|
381
|
+
when "ACK"
|
382
|
+
if @network.capabilities.include?(:sasl)
|
383
|
+
send_sasl
|
384
|
+
else
|
385
|
+
send_cap_end
|
386
|
+
end
|
387
|
+
when "NACK"
|
388
|
+
send_cap_end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
170
392
|
def on_join(msg, events)
|
171
393
|
if msg.user == @bot
|
172
394
|
@bot.channels << msg.channel
|
173
395
|
msg.channel.sync_modes
|
174
396
|
end
|
175
397
|
msg.channel.add_user(msg.user)
|
398
|
+
msg.user.online = true
|
176
399
|
end
|
177
400
|
|
178
401
|
def on_kick(msg, events)
|
179
|
-
target =
|
402
|
+
target = User(msg.params[1])
|
180
403
|
if target == @bot
|
181
404
|
@bot.channels.delete(msg.channel)
|
182
405
|
end
|
183
406
|
msg.channel.remove_user(target)
|
407
|
+
|
408
|
+
set_leaving_user(msg, target, events)
|
184
409
|
end
|
185
410
|
|
186
411
|
def on_kill(msg, events)
|
187
|
-
user =
|
188
|
-
|
412
|
+
user = User(msg.params[1])
|
413
|
+
|
414
|
+
@bot.channel_list.each do |channel|
|
189
415
|
channel.remove_user(user)
|
190
416
|
end
|
417
|
+
|
191
418
|
user.unsync_all
|
192
|
-
|
419
|
+
user.online = false
|
420
|
+
|
421
|
+
set_leaving_user(msg, user, events)
|
193
422
|
end
|
194
423
|
|
424
|
+
# @version 1.1.0
|
195
425
|
def on_mode(msg, events)
|
196
426
|
if msg.channel?
|
197
427
|
add_and_remove = @bot.irc.isupport["CHANMODES"]["A"] + @bot.irc.isupport["CHANMODES"]["B"] + @bot.irc.isupport["PREFIX"].keys
|
198
428
|
|
199
|
-
param_modes = {
|
200
|
-
:
|
429
|
+
param_modes = {
|
430
|
+
:add => @bot.irc.isupport["CHANMODES"]["C"] + add_and_remove,
|
431
|
+
:remove => add_and_remove
|
432
|
+
}
|
201
433
|
|
202
434
|
modes = ModeParser.parse_modes(msg.params[1], msg.params[2..-1], param_modes)
|
203
435
|
modes.each do |direction, mode, param|
|
204
436
|
if @bot.irc.isupport["PREFIX"].keys.include?(mode)
|
205
|
-
target =
|
437
|
+
target = User(param)
|
438
|
+
|
206
439
|
# (un)set a user-mode
|
207
440
|
if direction == :add
|
208
|
-
msg.channel.users[target] << mode unless msg.channel.users[
|
441
|
+
msg.channel.users[target] << mode unless msg.channel.users[User(param)].include?(mode)
|
209
442
|
else
|
210
443
|
msg.channel.users[target].delete mode
|
211
444
|
end
|
@@ -222,23 +455,16 @@ module Cinch
|
|
222
455
|
elsif @bot.irc.isupport["CHANMODES"]["A"].include?(mode)
|
223
456
|
case mode
|
224
457
|
when "b"
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
if direction == :add
|
229
|
-
msg.channel.bans_unsynced << ban
|
230
|
-
events << [:ban, ban]
|
231
|
-
else
|
232
|
-
msg.channel.bans_unsynced.delete_if {|b| b.mask == ban.mask}.first
|
233
|
-
events << [:unban, ban]
|
234
|
-
end
|
458
|
+
process_ban_mode(msg, events, param, direction)
|
459
|
+
when "q"
|
460
|
+
process_owner_mode(msg, events, param, direction) if @network.owner_list_mode
|
235
461
|
else
|
236
|
-
raise
|
462
|
+
raise Exceptions::UnsupportedMode, mode
|
237
463
|
end
|
238
464
|
else
|
239
465
|
# channel options
|
240
466
|
if direction == :add
|
241
|
-
msg.channel.modes_unsynced[mode] = param
|
467
|
+
msg.channel.modes_unsynced[mode] = param.nil? ? true : param
|
242
468
|
else
|
243
469
|
msg.channel.modes_unsynced.delete(mode)
|
244
470
|
end
|
@@ -246,26 +472,40 @@ module Cinch
|
|
246
472
|
end
|
247
473
|
|
248
474
|
events << [:mode_change, modes]
|
475
|
+
elsif msg.params.first == bot.nick
|
476
|
+
modes = ModeParser.parse_modes(msg.params[1], msg.params[2..-1])
|
477
|
+
modes.each do |direction, mode, _|
|
478
|
+
if direction == :add
|
479
|
+
@bot.modes << mode
|
480
|
+
else
|
481
|
+
@bot.modes.delete(mode)
|
482
|
+
end
|
483
|
+
end
|
249
484
|
end
|
250
485
|
end
|
251
486
|
|
252
487
|
def on_nick(msg, events)
|
253
488
|
if msg.user == @bot
|
254
|
-
@bot.
|
489
|
+
@bot.set_nick msg.params.last
|
255
490
|
end
|
256
491
|
|
257
492
|
msg.user.update_nick(msg.params.last)
|
493
|
+
msg.user.online = true
|
258
494
|
end
|
259
495
|
|
260
496
|
def on_part(msg, events)
|
261
497
|
msg.channel.remove_user(msg.user)
|
498
|
+
msg.user.channels_unsynced.delete msg.channel
|
499
|
+
|
262
500
|
if msg.user == @bot
|
263
501
|
@bot.channels.delete(msg.channel)
|
264
502
|
end
|
503
|
+
|
504
|
+
set_leaving_user(msg, msg.user, events)
|
265
505
|
end
|
266
506
|
|
267
507
|
def on_ping(msg, events)
|
268
|
-
|
508
|
+
send "PONG :#{msg.params.first}"
|
269
509
|
end
|
270
510
|
|
271
511
|
def on_topic(msg, events)
|
@@ -273,27 +513,87 @@ module Cinch
|
|
273
513
|
end
|
274
514
|
|
275
515
|
def on_quit(msg, events)
|
276
|
-
@bot.
|
516
|
+
@bot.channel_list.each do |channel|
|
277
517
|
channel.remove_user(msg.user)
|
278
518
|
end
|
279
519
|
msg.user.unsync_all
|
280
|
-
|
520
|
+
msg.user.online = true
|
521
|
+
|
522
|
+
set_leaving_user(msg, msg.user, events)
|
523
|
+
|
524
|
+
if msg.message.downcase == "excess flood" && msg.user == @bot
|
525
|
+
@bot.warn ["Looks like your bot has been kicked because of excess flood.",
|
526
|
+
"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:",
|
527
|
+
"- Server: #{@bot.config.server}",
|
528
|
+
"- Messages per second: #{@bot.config.messages_per_second}",
|
529
|
+
"- Server queue size: #{@bot.config.server_queue_size}"]
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
# @since 2.0.0
|
534
|
+
def on_privmsg(msg, events)
|
535
|
+
if msg.user
|
536
|
+
msg.user.online = true
|
537
|
+
end
|
538
|
+
|
539
|
+
|
540
|
+
if msg.message =~ /^\001DCC SEND (\S+) (\d+) (\d+)(?: (\d+))?\001$/
|
541
|
+
process_dcc_send($1, $2, $3, $4, msg, events)
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
# @since 2.0.0
|
546
|
+
def process_dcc_send(filename, ip, port, size, m, events)
|
547
|
+
ip = ip.to_i
|
548
|
+
ip = [24, 16, 8, 0].collect {|b| (ip >> b) & 255}.join('.')
|
549
|
+
port = port.to_i
|
550
|
+
size = size.to_i
|
551
|
+
|
552
|
+
@bot.loggers.info "DCC: Incoming DCC SEND: File name: %s - Size: %dB - IP: %s - Port: %d" % [filename, size, ip, port]
|
553
|
+
|
554
|
+
dcc = DCC::Incoming::Send.new(user: m.user, filename: filename, size: size, ip: ip, port: port)
|
555
|
+
events << [:dcc_send, dcc]
|
556
|
+
end
|
557
|
+
|
558
|
+
# @since 2.0.0
|
559
|
+
def on_001(msg, events)
|
560
|
+
# Ensure that we know our real, possibly truncated or otherwise
|
561
|
+
# modified nick.
|
562
|
+
@bot.set_nick msg.params.first
|
563
|
+
end
|
564
|
+
|
565
|
+
# @since 2.0.0
|
566
|
+
def on_002(msg, events)
|
567
|
+
detect_network(msg, "002")
|
281
568
|
end
|
282
569
|
|
283
570
|
def on_005(msg, events)
|
284
571
|
# ISUPPORT
|
285
572
|
@isupport.parse(*msg.params[1..-2].map {|v| v.split(" ")}.flatten)
|
573
|
+
detect_network(msg, "005")
|
574
|
+
end
|
575
|
+
|
576
|
+
# @since 2.0.0
|
577
|
+
def on_301(msg, events)
|
578
|
+
# RPL_AWAY
|
579
|
+
user = User(msg.params.first)
|
580
|
+
away = msg.message
|
581
|
+
|
582
|
+
if @whois_updates[user]
|
583
|
+
@whois_updates[user].merge!({:away => away})
|
584
|
+
end
|
286
585
|
end
|
287
586
|
|
587
|
+
# @since 1.1.0
|
288
588
|
def on_307(msg, events)
|
289
589
|
# RPL_WHOISREGNICK
|
290
|
-
user =
|
590
|
+
user = User(msg.params[1])
|
291
591
|
@whois_updates[user].merge!({:authname => user.nick})
|
292
592
|
end
|
293
593
|
|
294
594
|
def on_311(msg, events)
|
295
595
|
# RPL_WHOISUSER
|
296
|
-
user =
|
596
|
+
user = User(msg.params[1])
|
297
597
|
@whois_updates[user].merge!({
|
298
598
|
:user => msg.params[2],
|
299
599
|
:host => msg.params[3],
|
@@ -303,7 +603,7 @@ module Cinch
|
|
303
603
|
|
304
604
|
def on_317(msg, events)
|
305
605
|
# RPL_WHOISIDLE
|
306
|
-
user =
|
606
|
+
user = User(msg.params[1])
|
307
607
|
@whois_updates[user].merge!({
|
308
608
|
:idle => msg.params[2].to_i,
|
309
609
|
:signed_on_at => Time.at(msg.params[3].to_i),
|
@@ -312,7 +612,7 @@ module Cinch
|
|
312
612
|
|
313
613
|
def on_318(msg, events)
|
314
614
|
# RPL_ENDOFWHOIS
|
315
|
-
user =
|
615
|
+
user = User(msg.params[1])
|
316
616
|
|
317
617
|
if @whois_updates[user].empty? && !user.attr(:unknown?, true, true)
|
318
618
|
user.end_of_whois(nil)
|
@@ -324,16 +624,16 @@ module Cinch
|
|
324
624
|
|
325
625
|
def on_319(msg, events)
|
326
626
|
# RPL_WHOISCHANNELS
|
327
|
-
user
|
328
|
-
channels = msg.params[2].scan(/#{@isupport["CHANTYPES"].join}[^ ]+/o).map {|c|
|
627
|
+
user = User(msg.params[1])
|
628
|
+
channels = msg.params[2].scan(/#{@isupport["CHANTYPES"].join}[^ ]+/o).map {|c| Channel(c) }
|
329
629
|
user.sync(:channels, channels, true)
|
330
630
|
end
|
331
631
|
|
332
632
|
def on_324(msg, events)
|
333
633
|
# RPL_CHANNELMODEIS
|
334
|
-
|
335
|
-
modes = {}
|
634
|
+
modes = {}
|
336
635
|
arguments = msg.params[3..-1]
|
636
|
+
|
337
637
|
msg.params[2][1..-1].split("").each do |mode|
|
338
638
|
if (@isupport["CHANMODES"]["B"] + @isupport["CHANMODES"]["C"]).include?(mode)
|
339
639
|
modes[mode] = arguments.shift
|
@@ -347,8 +647,9 @@ module Cinch
|
|
347
647
|
|
348
648
|
def on_330(msg, events)
|
349
649
|
# RPL_WHOISACCOUNT
|
350
|
-
user
|
650
|
+
user = User(msg.params[1])
|
351
651
|
authname = msg.params[2]
|
652
|
+
|
352
653
|
@whois_updates[user].merge!({:authname => authname})
|
353
654
|
end
|
354
655
|
|
@@ -362,6 +663,35 @@ module Cinch
|
|
362
663
|
msg.channel.sync(:topic, msg.params[2])
|
363
664
|
end
|
364
665
|
|
666
|
+
def on_352(msg, events)
|
667
|
+
# RPL_WHOREPLY
|
668
|
+
# "<channel> <user> <host> <server> <nick> <H|G>[*][@|+] :<hopcount> <real name>"
|
669
|
+
_, channel, user, host, server, nick, flags, hopsrealname = msg.params
|
670
|
+
hops, realname = hopsrealname.split(" ", 2)
|
671
|
+
channel = Channel(channel)
|
672
|
+
user_object = User(nick)
|
673
|
+
user_object.sync(:user, user, true)
|
674
|
+
user_object.sync(:host, host, true)
|
675
|
+
end
|
676
|
+
|
677
|
+
def on_354(msg, events)
|
678
|
+
# RPL_WHOSPCRPL
|
679
|
+
# We are using the following format: %acfhnru
|
680
|
+
|
681
|
+
# _ user host nick f account realame
|
682
|
+
# :leguin.freenode.net 354 dominikh_ ~a ip-88-152-125-117.unitymediagroup.de dominikh_ H 0 :d
|
683
|
+
# :leguin.freenode.net 354 dominikh_ ~FiXato fixato.net FiXato H FiXato :FiXato, using WeeChat -- More? See: http://twitter
|
684
|
+
# :leguin.freenode.net 354 dominikh_ ~dominikh cinch/developer/dominikh dominikh H DominikH :dominikh
|
685
|
+
# :leguin.freenode.net 354 dominikh_ ~oddmunds s21-04214.dsl.no.powertech.net oddmunds H 0 :oddmunds
|
686
|
+
|
687
|
+
_, channel, user, host, nick, _, account, realname = msg.params
|
688
|
+
channel = Channel(channel)
|
689
|
+
user_object = User(nick)
|
690
|
+
user_object.sync(:user, user, true)
|
691
|
+
user_object.sync(:host, host, true)
|
692
|
+
user_object.sync(:authname, account == "0" ? nil : account, true)
|
693
|
+
end
|
694
|
+
|
365
695
|
def on_353(msg, events)
|
366
696
|
# RPL_NAMEREPLY
|
367
697
|
unless @in_lists.include?(:names)
|
@@ -372,14 +702,16 @@ module Cinch
|
|
372
702
|
msg.params[3].split(" ").each do |user|
|
373
703
|
m = user.match(/^([#{@isupport["PREFIX"].values.join}]+)/)
|
374
704
|
if m
|
375
|
-
prefixes = m[1].split.map {|s| @isupport["PREFIX"].key(s)}
|
705
|
+
prefixes = m[1].split("").map {|s| @isupport["PREFIX"].key(s)}
|
376
706
|
nick = user[prefixes.size..-1]
|
377
707
|
else
|
378
708
|
nick = user
|
379
709
|
prefixes = []
|
380
710
|
end
|
381
|
-
user
|
711
|
+
user = User(nick)
|
712
|
+
user.online = true
|
382
713
|
msg.channel.add_user(user, prefixes)
|
714
|
+
user.channels_unsynced << msg.channel unless user.channels_unsynced.include?(msg.channel)
|
383
715
|
end
|
384
716
|
end
|
385
717
|
|
@@ -389,6 +721,7 @@ module Cinch
|
|
389
721
|
msg.channel.mark_as_synced(:users)
|
390
722
|
end
|
391
723
|
|
724
|
+
# @version 2.0.0
|
392
725
|
def on_367(msg, events)
|
393
726
|
# RPL_BANLIST
|
394
727
|
unless @in_lists.include?(:bans)
|
@@ -397,9 +730,19 @@ module Cinch
|
|
397
730
|
@in_lists << :bans
|
398
731
|
|
399
732
|
mask = msg.params[2]
|
400
|
-
|
401
|
-
|
733
|
+
if @network.jtv?
|
734
|
+
# on the justin tv network, ban "masks" only consist of the
|
735
|
+
# nick/username
|
736
|
+
mask = "%s!%s@%s" % [mask, mask, mask + ".irc.justin.tv"]
|
737
|
+
end
|
738
|
+
|
739
|
+
if msg.params[3]
|
740
|
+
by = User(msg.params[3].split("!").first)
|
741
|
+
else
|
742
|
+
by = nil
|
743
|
+
end
|
402
744
|
|
745
|
+
at = Time.at(msg.params[4].to_i)
|
403
746
|
ban = Ban.new(mask, by, at)
|
404
747
|
msg.channel.bans_unsynced << ban
|
405
748
|
end
|
@@ -416,16 +759,40 @@ module Cinch
|
|
416
759
|
msg.channel.mark_as_synced(:bans)
|
417
760
|
end
|
418
761
|
|
762
|
+
def on_386(msg, events)
|
763
|
+
# RPL_QLIST
|
764
|
+
unless @in_lists.include?(:owners)
|
765
|
+
msg.channel.owners_unsynced.clear
|
766
|
+
end
|
767
|
+
@in_lists << :owners
|
768
|
+
|
769
|
+
owner = User(msg.params[2])
|
770
|
+
msg.channel.owners_unsynced << owner
|
771
|
+
end
|
772
|
+
|
773
|
+
def on_387(msg, events)
|
774
|
+
# RPL_ENDOFQLIST
|
775
|
+
if @in_lists.include?(:owners)
|
776
|
+
@in_lists.delete :owners
|
777
|
+
else
|
778
|
+
#we never received an owner, yet an end of list -> no owners
|
779
|
+
msg.channel.owners_unsynced.clear
|
780
|
+
end
|
781
|
+
|
782
|
+
msg.channel.mark_as_synced(:owners)
|
783
|
+
end
|
784
|
+
|
419
785
|
def on_396(msg, events)
|
420
786
|
# RPL_HOSTHIDDEN
|
421
787
|
# note: designed for freenode
|
422
|
-
|
788
|
+
User(msg.params[0]).sync(:host, msg.params[1], true)
|
423
789
|
end
|
424
790
|
|
425
791
|
def on_401(msg, events)
|
426
792
|
# ERR_NOSUCHNICK
|
427
|
-
user =
|
793
|
+
user = User(msg.params[1])
|
428
794
|
user.sync(:unknown?, true, true)
|
795
|
+
msg.user.online = false
|
429
796
|
if @whois_updates.key?(user)
|
430
797
|
user.end_of_whois(nil, true)
|
431
798
|
@whois_updates.delete user
|
@@ -435,7 +802,7 @@ module Cinch
|
|
435
802
|
def on_402(msg, events)
|
436
803
|
# ERR_NOSUCHSERVER
|
437
804
|
|
438
|
-
if user = @bot.
|
805
|
+
if user = @bot.user_list.find(msg.params[1]) # not _ensured, we only want a user that already exists
|
439
806
|
user.end_of_whois(nil, true)
|
440
807
|
@whois_updates.delete user
|
441
808
|
# TODO freenode specific, test on other IRCd
|
@@ -444,12 +811,60 @@ module Cinch
|
|
444
811
|
|
445
812
|
def on_433(msg, events)
|
446
813
|
# ERR_NICKNAMEINUSE
|
447
|
-
@bot.nick = @bot.generate_next_nick(msg.params[1])
|
814
|
+
@bot.nick = @bot.generate_next_nick!(msg.params[1])
|
448
815
|
end
|
449
816
|
|
450
817
|
def on_671(msg, events)
|
451
|
-
user =
|
818
|
+
user = User(msg.params[1])
|
452
819
|
@whois_updates[user].merge!({:secure? => true})
|
453
820
|
end
|
821
|
+
|
822
|
+
# @since 2.0.0
|
823
|
+
def on_730(msg, events)
|
824
|
+
# RPL_MONONLINE
|
825
|
+
msg.params.last.split(",").each do |mask|
|
826
|
+
user = User(Mask.new(mask).nick)
|
827
|
+
# User is responsible for emitting an event
|
828
|
+
user.online = true
|
829
|
+
end
|
830
|
+
end
|
831
|
+
|
832
|
+
# @since 2.0.0
|
833
|
+
def on_731(msg, events)
|
834
|
+
# RPL_MONOFFLINE
|
835
|
+
msg.params.last.split(",").each do |nick|
|
836
|
+
user = User(nick)
|
837
|
+
# User is responsible for emitting an event
|
838
|
+
user.online = false
|
839
|
+
end
|
840
|
+
end
|
841
|
+
|
842
|
+
# @since 2.0.0
|
843
|
+
def on_734(msg, events)
|
844
|
+
# ERR_MONLISTFULL
|
845
|
+
user = User(msg.params[2])
|
846
|
+
user.monitored = false
|
847
|
+
end
|
848
|
+
|
849
|
+
# @since 2.0.0
|
850
|
+
def on_903(msg, events)
|
851
|
+
# SASL authentication successful
|
852
|
+
@bot.loggers.info "[SASL] SASL authentication with #{@sasl_current_method.mechanism_name} successful"
|
853
|
+
send_cap_end
|
854
|
+
end
|
855
|
+
|
856
|
+
# @since 2.0.0
|
857
|
+
def on_904(msg, events)
|
858
|
+
# SASL authentication failed
|
859
|
+
@bot.loggers.info "[SASL] SASL authentication with #{@sasl_current_method.mechanism_name} failed"
|
860
|
+
send_sasl
|
861
|
+
end
|
862
|
+
|
863
|
+
# @since 2.0.0
|
864
|
+
def on_authenticate(msg, events)
|
865
|
+
send "AUTHENTICATE " + @sasl_current_method.generate(@bot.config.sasl.username,
|
866
|
+
@bot.config.sasl.password,
|
867
|
+
msg.params.last)
|
868
|
+
end
|
454
869
|
end
|
455
870
|
end
|