blur 1.8.6 → 2.1.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|