blur 1.8.6 → 2.1.6
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.
- checksums.yaml +5 -5
- data/README.md +10 -24
- data/executables/blur +63 -0
- data/library/blur/callbacks.rb +53 -0
- data/library/blur/channel.rb +78 -0
- data/library/blur/client.rb +165 -107
- data/library/blur/handling.rb +259 -179
- data/library/blur/network/connection.rb +26 -30
- data/library/blur/network/isupport.rb +38 -32
- data/library/blur/network.rb +193 -55
- data/library/blur/script.rb +132 -116
- data/library/blur/script_cache.rb +45 -0
- data/library/blur/user.rb +122 -0
- data/library/blur/version.rb +3 -3
- data/library/blur.rb +46 -19
- metadata +35 -28
- data/library/blur/encryption/base64.rb +0 -71
- data/library/blur/encryption/fish.rb +0 -80
- data/library/blur/encryption.rb +0 -17
- data/library/blur/enhancements.rb +0 -41
- data/library/blur/evaluable.rb +0 -13
- data/library/blur/extension.rb +0 -42
- data/library/blur/network/channel.rb +0 -91
- data/library/blur/network/command.rb +0 -83
- data/library/blur/network/user.rb +0 -106
- data/library/blur/script/cache.rb +0 -77
- data/library/blur/script/commands.rb +0 -77
- data/library/blur/script/dsl.rb +0 -52
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Blur
|
4
4
|
class Network
|
@@ -13,14 +13,23 @@ module Blur
|
|
13
13
|
class Connection < EM::Protocols::LineAndTextProtocol
|
14
14
|
SSLValidationError = Class.new StandardError
|
15
15
|
|
16
|
+
# @return [Float] the default connection timeout interval in seconds.
|
17
|
+
DEFAULT_CONNECT_TIMEOUT_INTERVAL = 30
|
18
|
+
|
16
19
|
# Check whether or not connection is established.
|
17
|
-
def established
|
20
|
+
def established?
|
21
|
+
@connected == true
|
22
|
+
end
|
18
23
|
|
19
24
|
# EventMachine instantiates this class, and then sends event messages to
|
20
25
|
# that instance.
|
21
26
|
def initialize network
|
22
27
|
@network = network
|
23
28
|
@connected = false
|
29
|
+
connect_timeout = network.options.fetch 'connect_timeout',
|
30
|
+
DEFAULT_CONNECT_TIMEOUT_INTERVAL
|
31
|
+
|
32
|
+
self.pending_connect_timeout = connect_timeout
|
24
33
|
|
25
34
|
super
|
26
35
|
end
|
@@ -28,19 +37,18 @@ module Blur
|
|
28
37
|
# Called when a new connection is being set up, all we're going to use
|
29
38
|
# it for is to enable SSL/TLS on our connection.
|
30
39
|
def post_init
|
31
|
-
|
32
|
-
verify_peer = (@network.options[:ssl_no_verify] ? false : true)
|
40
|
+
return unless @network.secure?
|
33
41
|
|
34
|
-
|
35
|
-
|
42
|
+
verify_peer = (@network.options[:ssl_no_verify] ? false : true)
|
43
|
+
start_tls verify_peer: verify_peer
|
36
44
|
end
|
37
45
|
|
38
46
|
# Called when a line was received, the connection sends it to the network
|
39
47
|
# delegate which then sends it to the client.
|
40
48
|
def receive_line line
|
41
|
-
|
49
|
+
message = IRCParser::Message.parse line
|
42
50
|
|
43
|
-
@network.
|
51
|
+
@network.got_message message
|
44
52
|
end
|
45
53
|
|
46
54
|
# Called when the SSL handshake was completed with the remote server,
|
@@ -61,12 +69,8 @@ module Blur
|
|
61
69
|
ssl_cert_file = @network.options[:ssl_cert_file]
|
62
70
|
peer_certificate = OpenSSL::X509::Certificate.new peer_cert
|
63
71
|
|
64
|
-
if ssl_cert_file
|
65
|
-
|
66
|
-
raise SSLValidationError, "Could not read the CA certificate file."
|
67
|
-
|
68
|
-
return false
|
69
|
-
end
|
72
|
+
if ssl_cert_file && !File.readable?(ssl_cert_file)
|
73
|
+
raise SSLValidationError, 'Could not read the CA certificate file.'
|
70
74
|
end
|
71
75
|
|
72
76
|
if fingerprint_verification?
|
@@ -75,9 +79,7 @@ module Blur
|
|
75
79
|
|
76
80
|
if fingerprint != peer_fingerprint
|
77
81
|
raise SSLValidationError,
|
78
|
-
|
79
|
-
|
80
|
-
return false
|
82
|
+
"Expected fingerprint '#{fingerprint}', but got '#{peer_fingerprint}'"
|
81
83
|
end
|
82
84
|
end
|
83
85
|
|
@@ -85,11 +87,7 @@ module Blur
|
|
85
87
|
ca_certificate = OpenSSL::X509::Certificate.new File.read ssl_cert_file
|
86
88
|
valid_signature = peer_certificate.verify ca_certificate.public_key
|
87
89
|
|
88
|
-
|
89
|
-
raise SSLValidationError, "Certificate verify failed"
|
90
|
-
|
91
|
-
return false
|
92
|
-
end
|
90
|
+
raise SSLValidationError, 'Certificate verify failed' unless valid_signature
|
93
91
|
end
|
94
92
|
|
95
93
|
true
|
@@ -98,9 +96,7 @@ module Blur
|
|
98
96
|
# Called once the connection is finally established.
|
99
97
|
def connection_completed
|
100
98
|
# We aren't completely connected yet if the connection is encrypted.
|
101
|
-
unless @network.secure?
|
102
|
-
connected!
|
103
|
-
end
|
99
|
+
connected! unless @network.secure?
|
104
100
|
end
|
105
101
|
|
106
102
|
# Called just as the connection is being terminated, either by remote or
|
@@ -112,7 +108,8 @@ module Blur
|
|
112
108
|
super
|
113
109
|
end
|
114
110
|
|
115
|
-
|
111
|
+
private
|
112
|
+
|
116
113
|
# Called when connection has been established.
|
117
114
|
def connected!
|
118
115
|
@connected = true
|
@@ -122,12 +119,12 @@ module Blur
|
|
122
119
|
|
123
120
|
# Returns true if we're expected to verify the certificate fingerprint.
|
124
121
|
def fingerprint_verification?
|
125
|
-
|
122
|
+
!@network.options[:ssl_fingerprint].nil?
|
126
123
|
end
|
127
124
|
|
128
125
|
# Returns true if we should verify the peer certificate.
|
129
126
|
def certificate_verification?
|
130
|
-
|
127
|
+
!@network.options[:ssl_cert_file].nil?
|
131
128
|
end
|
132
129
|
|
133
130
|
# Get the hexadecimal representation of the certificates public key.
|
@@ -143,9 +140,8 @@ module Blur
|
|
143
140
|
fingerprint = @network.options[:ssl_fingerprint]
|
144
141
|
|
145
142
|
raise SSLValidationError,
|
146
|
-
|
143
|
+
"Expected fingerprint '#{fingerprint}' but got '#{peer_fingerprint}'"
|
147
144
|
end
|
148
|
-
|
149
145
|
end
|
150
146
|
end
|
151
147
|
end
|
@@ -1,20 +1,22 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'English'
|
2
4
|
|
3
5
|
module Blur
|
4
6
|
class Network
|
5
7
|
# ISupport class that enables servers to announce what they support.
|
6
|
-
#
|
8
|
+
#
|
7
9
|
# @see https://tools.ietf.org/html/draft-brocklesby-irc-isupport-03
|
8
10
|
class ISupport < Hash
|
9
11
|
# Return the network reference.
|
10
12
|
attr_accessor :network
|
11
13
|
|
12
14
|
# ISUPPORT parameters which should always be casted to numeric values.
|
13
|
-
|
14
|
-
|
15
|
+
NUMERIC_PARAMS = %w[CHANNELLEN MODES NICKLEN KICKLEN TOPICLEN AWAYLEN
|
16
|
+
MAXCHANNELS MAXBANS MAXPARA MAXTARGETS].freeze
|
15
17
|
|
16
18
|
# Our parsers for parameters that require special treatment.
|
17
|
-
|
19
|
+
PARSERS = {
|
18
20
|
# CHANLIMIT=pfx:num[,pfx:num,...]
|
19
21
|
#
|
20
22
|
# This parameter specifies the maximum number of channels that a client
|
@@ -24,10 +26,10 @@ module Blur
|
|
24
26
|
# the client may join in total. If there is no limit to the number of
|
25
27
|
# certain channel type(s) a client may join, the limit should be
|
26
28
|
# specified as the empty string, for example "#:".
|
27
|
-
%w[CHANLIMIT] =>
|
28
|
-
|
29
|
-
params = value.split
|
30
|
-
mappings = params.map{|param| param.split
|
29
|
+
%w[CHANLIMIT] => lambda do |value|
|
30
|
+
{}.tap do |result|
|
31
|
+
params = value.split ','
|
32
|
+
mappings = params.map { |param| param.split ':' }
|
31
33
|
|
32
34
|
mappings.each do |prefixes, limit|
|
33
35
|
prefixes.each_char do |prefix|
|
@@ -48,10 +50,12 @@ module Blur
|
|
48
50
|
#
|
49
51
|
# The order of the modes is from that which gives most privileges on
|
50
52
|
# the channel, to that which gives the least.
|
51
|
-
%w[PREFIX] =>
|
52
|
-
|
53
|
-
|
54
|
-
|
53
|
+
%w[PREFIX] => lambda do |value|
|
54
|
+
{}.tap do |result|
|
55
|
+
match = value.match(/^\((.+)\)(.*)/)
|
56
|
+
|
57
|
+
if match
|
58
|
+
modes, prefix = match[1..2]
|
55
59
|
|
56
60
|
modes.chars.each_with_index do |char, index|
|
57
61
|
result[char] = prefix[index]
|
@@ -77,39 +81,41 @@ module Blur
|
|
77
81
|
# mode is removed both in the client's and server's MODE command.
|
78
82
|
# o Type D: Modes that change a setting on the channel. These modes
|
79
83
|
# never take a parameter.
|
80
|
-
%w[CHANMODES] =>
|
81
|
-
|
82
|
-
r[
|
84
|
+
%w[CHANMODES] => lambda do |value|
|
85
|
+
{}.tap do |r|
|
86
|
+
r['A'], r['B'], r['C'], r['D'] = value.split(',').map &:chars
|
83
87
|
end
|
84
88
|
end,
|
85
89
|
|
86
90
|
# Cast known params that are numeric, to a numeric value.
|
87
|
-
|
91
|
+
NUMERIC_PARAMS => lambda do |value|
|
88
92
|
value.to_i
|
89
93
|
end
|
90
|
-
}
|
94
|
+
}.freeze
|
91
95
|
|
92
96
|
# Initialize a new ISupport with a network reference.
|
93
|
-
#
|
97
|
+
#
|
94
98
|
# @param network [Network] The parent network.
|
95
99
|
def initialize network
|
100
|
+
super
|
101
|
+
|
96
102
|
@network = network
|
97
103
|
|
98
104
|
# Set default ISUPPORT values.
|
99
105
|
#
|
100
106
|
# @see
|
101
107
|
# https://tools.ietf.org/html/draft-brocklesby-irc-isupport-03#appendix-A
|
102
|
-
self[
|
103
|
-
self[
|
104
|
-
self[
|
105
|
-
self[
|
106
|
-
self[
|
108
|
+
self['MODES'] = 3
|
109
|
+
self['PREFIX'] = { 'o' => '@', 'v' => '+' }
|
110
|
+
self['KICKLEN'] = 200
|
111
|
+
self['NICKLEN'] = 9
|
112
|
+
self['MAXLIST'] = { '#' => Float::INFINITY, '&' => Float::INFINITY }
|
107
113
|
self['TOPICLEN'] = 200
|
108
|
-
self[
|
109
|
-
self[
|
110
|
-
self[
|
111
|
-
self[
|
112
|
-
self[
|
114
|
+
self['CHANMODES'] = {}
|
115
|
+
self['CHANTYPES'] = %w[# &]
|
116
|
+
self['CHANLIMIT'] = { '#' => Float::INFINITY, '&' => Float::INFINITY }
|
117
|
+
self['CHANNELLEN'] = 200
|
118
|
+
self['CASEMAPPING'] = 'rfc1459'
|
113
119
|
end
|
114
120
|
|
115
121
|
# Parse a list of parameters to see what the server supports.
|
@@ -117,12 +123,12 @@ module Blur
|
|
117
123
|
# @param parameters [Array] The list of parameters.
|
118
124
|
def parse *params
|
119
125
|
params.each do |parameter|
|
120
|
-
name, value = parameter.split
|
126
|
+
name, value = parameter.split '='
|
121
127
|
|
122
128
|
if value
|
123
|
-
_, parser =
|
129
|
+
_, parser = PARSERS.find { |key, _value| key.include? name }
|
124
130
|
|
125
|
-
self[name] = parser.nil? ? value : parser.(value)
|
131
|
+
self[name] = parser.nil? ? value : parser.call(value)
|
126
132
|
else
|
127
133
|
self[name] = true
|
128
134
|
end
|
data/library/blur/network.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Blur
|
4
4
|
# The +Network+ module is to be percieved as an IRC network.
|
@@ -6,44 +6,81 @@ module Blur
|
|
6
6
|
# Although the connection is a part of the network module, it is mainly used
|
7
7
|
# for network-related structures, such as {User}, {Channel} and {Command}.
|
8
8
|
class Network
|
9
|
-
include Logging
|
10
|
-
|
11
9
|
# +ConnectionError+ should only be triggered from within {Connection}.
|
12
10
|
class ConnectionError < StandardError; end
|
13
11
|
|
12
|
+
DEFAULT_PING_INTERVAL = 30
|
13
|
+
DEFAULT_RECONNECT = true
|
14
|
+
|
15
|
+
# Returns a unique identifier for this network.
|
16
|
+
#
|
17
|
+
# You can override the id in your network configuration by setting an 'id'
|
18
|
+
# key with the id you want.. If no id is specified, the the id will be
|
19
|
+
# constructed from the hostname and port number
|
20
|
+
# in the format "<host>:<port>"
|
21
|
+
#
|
22
|
+
# @return [String] the unique identifier for this network.
|
23
|
+
attr_reader :id
|
24
|
+
# @return [String] the current nickname.
|
25
|
+
attr_accessor :nickname
|
14
26
|
# @return [Hash] the network options.
|
15
27
|
attr_accessor :options
|
16
|
-
# @return [
|
28
|
+
# @return [Hash] the map of users that is known.
|
29
|
+
attr_accessor :users
|
30
|
+
# @return [Hash] the map of channels the client is in.
|
17
31
|
attr_accessor :channels
|
18
|
-
# @return [
|
19
|
-
attr_accessor :
|
20
|
-
# @return [Client] the client delegate.
|
21
|
-
attr_accessor :delegate
|
32
|
+
# @return [Client] the client reference.
|
33
|
+
attr_accessor :client
|
22
34
|
# @return [Network::Connection] the connection instance.
|
23
35
|
attr_accessor :connection
|
24
36
|
# @return [Network::ISupport] the network isupport specs.
|
25
37
|
attr_accessor :isupport
|
38
|
+
# @return [Array<String>] list of capabilities supported by the network.
|
39
|
+
attr_accessor :capabilities
|
40
|
+
# @return [Boolean] true if we're waiting for a capability negotiation.
|
41
|
+
attr_reader :waiting_for_cap
|
42
|
+
# @return [Time] the last time a pong was sent or received.
|
43
|
+
attr_accessor :last_pong_time
|
44
|
+
# The max PING interval for the server. This is used to determine when the
|
45
|
+
# client will attempt to send its own PING command.
|
46
|
+
#
|
47
|
+
# @note the actual time until a client PING is sent can vary by an
|
48
|
+
# additional 0-30 seconds.
|
49
|
+
# @return [Number] the max interval between pings from a server.
|
50
|
+
attr_accessor :server_ping_interval_max
|
26
51
|
|
27
52
|
# Check whether or not connection is established.
|
28
|
-
def connected
|
53
|
+
def connected?
|
54
|
+
@connection&.established?
|
55
|
+
end
|
29
56
|
|
30
57
|
# Get the remote hostname.
|
31
58
|
#
|
32
59
|
# @return [String] the remote hostname.
|
33
|
-
def host
|
60
|
+
def host
|
61
|
+
@options['hostname']
|
62
|
+
end
|
34
63
|
|
35
64
|
# Get the remote port.
|
36
65
|
# If no port is specified, it returns 6697 if using a secure connection,
|
37
66
|
# returns 6667 otherwise.
|
38
67
|
#
|
39
68
|
# @return [Fixnum] the remote port
|
40
|
-
def port
|
41
|
-
|
69
|
+
def port
|
70
|
+
@options['port'] ||= secure? ? 6697 : 6667
|
71
|
+
end
|
72
|
+
|
42
73
|
# Check to see if it's a secure connection.
|
43
|
-
def secure
|
74
|
+
def secure?
|
75
|
+
@options['secure'] == true
|
76
|
+
end
|
44
77
|
|
45
|
-
#
|
46
|
-
def
|
78
|
+
# @return [Boolean] whether we want to authenticate with SASL.
|
79
|
+
def sasl?
|
80
|
+
@options['sasl'] &&
|
81
|
+
@options['sasl']['username'] &&
|
82
|
+
@options['sasl']['password']
|
83
|
+
end
|
47
84
|
|
48
85
|
# Instantiates the network.
|
49
86
|
#
|
@@ -54,51 +91,64 @@ module Blur
|
|
54
91
|
# @option options [optional, String] :username (Copies :nickname)
|
55
92
|
# The username to use. This is also known as the ident.
|
56
93
|
# @option options [optional, String] :realname (Copies :username)
|
57
|
-
# The
|
94
|
+
# The "real name" that we want to use. This is usually what shows up
|
58
95
|
# as "Name" when you whois a user.
|
59
96
|
# @option options [optional, String] :password The password for the network.
|
60
97
|
# This is sometimes needed for private networks.
|
61
|
-
# @option options [optional, Fixnum] :port (6697 if ssl, otherwise 6667)
|
98
|
+
# @option options [optional, Fixnum] :port (6697 if ssl, otherwise 6667)
|
62
99
|
# The remote port we want to connect to.
|
63
100
|
# @option options [optional, Boolean] :secure Set whether this is a secure
|
64
101
|
# (SSL-encrypted) connection.
|
65
102
|
# @option options [optional, String] :ssl_cert_file Local path of a
|
66
103
|
# readable file that contains a X509 CA certificate to validate against.
|
67
|
-
# @option options [optional, String] :ssl_fingerprint Validate that the
|
104
|
+
# @option options [optional, String] :ssl_fingerprint Validate that the
|
68
105
|
# remote certificate matches the specified fingerprint.
|
69
106
|
# @option options [optional, Boolean] :ssl_no_verify Disable verification
|
70
107
|
# alltogether.
|
71
|
-
def initialize options
|
72
|
-
@
|
73
|
-
@
|
108
|
+
def initialize options, client = nil
|
109
|
+
@client = client
|
110
|
+
@options = options
|
111
|
+
# @log = ::Logging.logger[self]
|
112
|
+
@users = {}
|
113
|
+
@channels = {}
|
74
114
|
@isupport = ISupport.new self
|
75
|
-
|
76
|
-
|
77
|
-
|
115
|
+
@capabilities = []
|
116
|
+
@reconnect_interval = 3
|
117
|
+
@server_ping_interval_max = @options.fetch('server_ping_interval',
|
118
|
+
150).to_i
|
119
|
+
|
120
|
+
unless options['nickname']
|
121
|
+
raise ArgumentError, 'Network configuration for ' \
|
122
|
+
"`#{id}' is missing a nickname"
|
78
123
|
end
|
79
|
-
|
80
|
-
@
|
81
|
-
@options[
|
82
|
-
@options[
|
124
|
+
|
125
|
+
@nickname = options['nickname']
|
126
|
+
@options['username'] ||= @options['nickname']
|
127
|
+
@options['realname'] ||= @options['username']
|
128
|
+
@options['channels'] ||= []
|
129
|
+
@id = options.fetch 'id', "#{host}:#{port}"
|
83
130
|
end
|
84
|
-
|
131
|
+
|
85
132
|
# Send a message to a recipient.
|
86
133
|
#
|
87
134
|
# @param [String, #to_s] recipient the recipient.
|
88
135
|
# @param [String] message the message.
|
89
136
|
def say recipient, message
|
90
|
-
if recipient.is_a? Channel and recipient.encrypted?
|
91
|
-
message = "+OK #{recipient.encryption.encrypt message}"
|
92
|
-
end
|
93
|
-
|
94
137
|
transmit :PRIVMSG, recipient.to_s, message
|
95
138
|
end
|
96
|
-
|
139
|
+
|
140
|
+
# Forwards the received message to the client instance.
|
141
|
+
#
|
97
142
|
# Called when the network connection has enough data to form a command.
|
98
|
-
def
|
99
|
-
@
|
143
|
+
def got_message message
|
144
|
+
@client.got_message self, message
|
145
|
+
rescue StandardError => e
|
146
|
+
puts "#{e.class}: #{e.message}"
|
147
|
+
puts
|
148
|
+
puts '---'
|
149
|
+
puts e.backtrace
|
100
150
|
end
|
101
|
-
|
151
|
+
|
102
152
|
# Find a channel by its name.
|
103
153
|
#
|
104
154
|
# @param [String] name the channel name.
|
@@ -106,7 +156,7 @@ module Blur
|
|
106
156
|
def channel_by_name name
|
107
157
|
@channels.find { |channel| channel.name == name }
|
108
158
|
end
|
109
|
-
|
159
|
+
|
110
160
|
# Find all instances of channels in which there is a user with the nick
|
111
161
|
# +nick+.
|
112
162
|
#
|
@@ -120,65 +170,153 @@ module Blur
|
|
120
170
|
#
|
121
171
|
# @return [Array<String>] a list of user prefixes.
|
122
172
|
def user_prefixes
|
123
|
-
isupport[
|
173
|
+
isupport['PREFIX'].values
|
124
174
|
end
|
125
175
|
|
126
176
|
# Returns a list of user modes that also gives a users nick a prefix.
|
127
177
|
#
|
128
178
|
# @return [Array<String>] a list of user modes.
|
129
179
|
def user_prefix_modes
|
130
|
-
isupport[
|
180
|
+
isupport['PREFIX'].keys
|
131
181
|
end
|
132
182
|
|
133
183
|
# Returns a list of channel flags (channel mode D).
|
134
184
|
#
|
135
185
|
# @return [Array<String>] a list of channel flags.
|
136
186
|
def channel_flags
|
137
|
-
isupport[
|
187
|
+
isupport['CHANMODES']['D']
|
138
188
|
end
|
139
189
|
|
140
190
|
# Attempt to establish a connection and send initial data.
|
141
191
|
#
|
142
192
|
# @see Connection
|
143
193
|
def connect
|
144
|
-
@
|
194
|
+
# @log.info "Connecting to #{self}"
|
195
|
+
|
196
|
+
begin
|
197
|
+
@connection = EventMachine.connect host, port, Connection, self
|
198
|
+
rescue EventMachine::ConnectionError => e
|
199
|
+
warn "Establishing connection to #{self} failed!"
|
200
|
+
warn e.message
|
201
|
+
|
202
|
+
schedule_reconnect
|
203
|
+
return
|
204
|
+
end
|
205
|
+
|
206
|
+
@ping_timer = EventMachine.add_periodic_timer DEFAULT_PING_INTERVAL do
|
207
|
+
periodic_ping_check
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# Schedules a reconnect after a user-specified number of seconds.
|
212
|
+
def schedule_reconnect
|
213
|
+
# @log.info "Reconnecting to #{self} in #{@reconnect_interval} seconds"
|
214
|
+
|
215
|
+
EventMachine.add_timer @reconnect_interval do
|
216
|
+
connect
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def server_connection_timeout
|
221
|
+
@connection.close_connection
|
222
|
+
|
223
|
+
warn "Connection to #{self} timed out"
|
224
|
+
end
|
225
|
+
|
226
|
+
def periodic_ping_check
|
227
|
+
now = Time.now
|
228
|
+
seconds_since_pong = now - @last_pong_time
|
229
|
+
|
230
|
+
return unless seconds_since_pong >= @server_ping_interval_max
|
231
|
+
|
232
|
+
# @log.info "No PING request from the server in #{seconds_since_pong}s!"
|
233
|
+
|
234
|
+
transmit 'PING', now.to_s
|
235
|
+
|
236
|
+
# Wait 15 seconds and declare a timeout if we didn't get a PONG.
|
237
|
+
previous_pong_time = @last_pong_time.dup
|
238
|
+
|
239
|
+
EventMachine.add_timer 15 do
|
240
|
+
server_connection_timeout if @last_pong_time == previous_pong_time
|
241
|
+
end
|
145
242
|
end
|
146
243
|
|
147
244
|
# Called when the connection was successfully established.
|
148
245
|
def connected!
|
149
|
-
|
150
|
-
|
151
|
-
|
246
|
+
@waiting_for_cap = true
|
247
|
+
@capabilities.clear
|
248
|
+
|
249
|
+
transmit :CAP, 'LS'
|
250
|
+
transmit :PASS, @options['password'] if @options['password']
|
251
|
+
transmit :NICK, @options['nickname']
|
252
|
+
transmit :USER, @options['username'], 'void', 'void', @options['realname']
|
253
|
+
|
254
|
+
@last_pong_time = Time.now
|
255
|
+
end
|
256
|
+
|
257
|
+
# Called when the server doesn't support capability negotiation.
|
258
|
+
def abort_cap_neg
|
259
|
+
@waiting_for_cap = false
|
260
|
+
|
261
|
+
puts 'Server does not support capability negotiation'
|
262
|
+
end
|
263
|
+
|
264
|
+
# Called when we're done with capability negotiation.
|
265
|
+
def cap_end
|
266
|
+
@waiting_for_cap = false
|
267
|
+
|
268
|
+
transmit :CAP, 'END'
|
152
269
|
end
|
153
270
|
|
154
271
|
# Called when the connection was closed.
|
155
272
|
def disconnected!
|
156
|
-
@channels.each { |channel| channel.users.clear }
|
273
|
+
@channels.each { |_, channel| channel.users.clear }
|
157
274
|
@channels.clear
|
275
|
+
@users.clear
|
276
|
+
@ping_timer.cancel
|
277
|
+
|
278
|
+
# @log.debug "Connection to #{self} lost!"
|
279
|
+
@client.network_connection_closed self
|
158
280
|
|
159
|
-
@
|
281
|
+
return unless @options.fetch('reconnect', DEFAULT_RECONNECT)
|
282
|
+
|
283
|
+
schedule_reconnect
|
160
284
|
end
|
161
|
-
|
285
|
+
|
162
286
|
# Terminate the connection and clear all channels and users.
|
163
287
|
def disconnect
|
164
288
|
@connection.close_connection_after_writing
|
165
289
|
end
|
166
|
-
|
290
|
+
|
167
291
|
# Transmit a command to the server.
|
168
292
|
#
|
169
293
|
# @param [Symbol, String] name the command name.
|
170
294
|
# @param [...] arguments all the prepended parameters.
|
171
295
|
def transmit name, *arguments
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
296
|
+
message = IRCParser::Message.new command: name.to_s, parameters: arguments
|
297
|
+
|
298
|
+
if @client.verbose
|
299
|
+
formatted_command = message.command.to_s.ljust 8, ' '
|
300
|
+
formatted_params = message.parameters.map(&:inspect).join ' '
|
301
|
+
puts "→ #{formatted_command} #{formatted_params}"
|
302
|
+
end
|
303
|
+
|
304
|
+
@connection.send_data "#{message}\r\n"
|
305
|
+
end
|
306
|
+
|
307
|
+
# Send a private message.
|
308
|
+
def send_privmsg recipient, message
|
309
|
+
transmit :PRIVMSG, recipient, message
|
310
|
+
end
|
311
|
+
|
312
|
+
# Join a channel.
|
313
|
+
def join channel
|
314
|
+
transmit :JOIN, channel
|
176
315
|
end
|
177
316
|
|
178
|
-
|
179
317
|
# Convert it to a debug-friendly format.
|
180
318
|
def to_s
|
181
|
-
%
|
319
|
+
%(#<#{self.class.name} "#{host}":#{port}>)
|
182
320
|
end
|
183
321
|
end
|
184
322
|
end
|