cinch 1.1.3 → 2.0.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|