blur 2.1.3 → 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.
@@ -1,20 +1,22 @@
1
- # encoding: utf-8
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
- NumericParams = %w[CHANNELLEN MODES NICKLEN KICKLEN TOPICLEN AWAYLEN
14
- MAXCHANNELS MAXBANS MAXPARA MAXTARGETS].freeze
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
- Parsers = {
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] => -> (value) do
28
- Hash.new.tap do |result|
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] => -> (value) do
52
- Hash.new.tap do |result|
53
- if value =~ /^\((.+)\)(.*)/
54
- modes, prefix = $~[1..2]
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] => -> (value) do
81
- Hash.new.tap do |r|
82
- r["A"], r["B"], r["C"], r["D"] = value.split(?,).map &:chars
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
- NumericParams => -> (value) do
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["MODES"] = 3
103
- self["PREFIX"] = { "o" => "@", "v" => "+" }
104
- self["KICKLEN"] = 200
105
- self["NICKLEN"] = 9
106
- self["MAXLIST"] = { "#" => Float::INFINITY, "&" => Float::INFINITY }
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["CHANMODES"] = {}
109
- self["CHANTYPES"] = %w{# &}
110
- self["CHANLIMIT"] = { "#" => Float::INFINITY, "&" => Float::INFINITY }
111
- self["CHANNELLEN"] = 200
112
- self["CASEMAPPING"] = "rfc1459"
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 = Parsers.find{|key, value| key.include? name }
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
@@ -6,11 +6,12 @@ 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
+
14
15
  # Returns a unique identifier for this network.
15
16
  #
16
17
  # You can override the id in your network configuration by setting an 'id'
@@ -20,6 +21,8 @@ module Blur
20
21
  #
21
22
  # @return [String] the unique identifier for this network.
22
23
  attr_reader :id
24
+ # @return [String] the current nickname.
25
+ attr_accessor :nickname
23
26
  # @return [Hash] the network options.
24
27
  attr_accessor :options
25
28
  # @return [Hash] the map of users that is known.
@@ -32,8 +35,19 @@ module Blur
32
35
  attr_accessor :connection
33
36
  # @return [Network::ISupport] the network isupport specs.
34
37
  attr_accessor :isupport
38
+ # @return [Array<String>] list of capabilities supported by the network.
39
+ attr_accessor :capabilities
35
40
  # @return [Boolean] true if we're waiting for a capability negotiation.
36
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
37
51
 
38
52
  # Check whether or not connection is established.
39
53
  def connected?
@@ -94,15 +108,21 @@ module Blur
94
108
  def initialize options, client = nil
95
109
  @client = client
96
110
  @options = options
111
+ # @log = ::Logging.logger[self]
97
112
  @users = {}
98
113
  @channels = {}
99
114
  @isupport = ISupport.new self
115
+ @capabilities = []
116
+ @reconnect_interval = 3
117
+ @server_ping_interval_max = @options.fetch('server_ping_interval',
118
+ 150).to_i
100
119
 
101
120
  unless options['nickname']
102
121
  raise ArgumentError, 'Network configuration for ' \
103
122
  "`#{id}' is missing a nickname"
104
123
  end
105
124
 
125
+ @nickname = options['nickname']
106
126
  @options['username'] ||= @options['nickname']
107
127
  @options['realname'] ||= @options['username']
108
128
  @options['channels'] ||= []
@@ -122,11 +142,11 @@ module Blur
122
142
  # Called when the network connection has enough data to form a command.
123
143
  def got_message message
124
144
  @client.got_message self, message
125
- rescue StandardError => exception
126
- puts "#{exception.class}: #{exception.message}"
145
+ rescue StandardError => e
146
+ puts "#{e.class}: #{e.message}"
127
147
  puts
128
148
  puts '---'
129
- puts exception.backtrace
149
+ puts e.backtrace
130
150
  end
131
151
 
132
152
  # Find a channel by its name.
@@ -171,27 +191,74 @@ module Blur
171
191
  #
172
192
  # @see Connection
173
193
  def connect
174
- @connection = EventMachine.connect host, port, Connection, self
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
175
209
  end
176
210
 
177
- # Called when the connection was successfully established.
178
- def connected!
179
- if sasl?
180
- @waiting_for_cap = true
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
181
222
 
182
- transmit :CAP, 'REQ', 'sasl'
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
183
241
  end
242
+ end
243
+
244
+ # Called when the connection was successfully established.
245
+ def connected!
246
+ @waiting_for_cap = true
247
+ @capabilities.clear
184
248
 
249
+ transmit :CAP, 'LS'
185
250
  transmit :PASS, @options['password'] if @options['password']
186
251
  transmit :NICK, @options['nickname']
187
252
  transmit :USER, @options['username'], 'void', 'void', @options['realname']
253
+
254
+ @last_pong_time = Time.now
188
255
  end
189
256
 
190
257
  # Called when the server doesn't support capability negotiation.
191
258
  def abort_cap_neg
192
259
  @waiting_for_cap = false
193
260
 
194
- puts "Server does not support capability negotiation"
261
+ puts 'Server does not support capability negotiation'
195
262
  end
196
263
 
197
264
  # Called when we're done with capability negotiation.
@@ -203,11 +270,17 @@ module Blur
203
270
 
204
271
  # Called when the connection was closed.
205
272
  def disconnected!
206
- @channels.each { |_name, channel| channel.users.clear }
273
+ @channels.each { |_, channel| channel.users.clear }
207
274
  @channels.clear
208
275
  @users.clear
276
+ @ping_timer.cancel
209
277
 
278
+ # @log.debug "Connection to #{self} lost!"
210
279
  @client.network_connection_closed self
280
+
281
+ return unless @options.fetch('reconnect', DEFAULT_RECONNECT)
282
+
283
+ schedule_reconnect
211
284
  end
212
285
 
213
286
  # Terminate the connection and clear all channels and users.
@@ -225,7 +298,7 @@ module Blur
225
298
  if @client.verbose
226
299
  formatted_command = message.command.to_s.ljust 8, ' '
227
300
  formatted_params = message.parameters.map(&:inspect).join ' '
228
- log "#{'' ^ :red} #{formatted_command} #{formatted_params}"
301
+ puts "→ #{formatted_command} #{formatted_params}"
229
302
  end
230
303
 
231
304
  @connection.send_data "#{message}\r\n"
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Blur
4
4
  module Commands
@@ -27,7 +27,7 @@ module Blur
27
27
  # command! '!ping' do |user, channel, args|
28
28
  # channel.say "#{user}: pong"
29
29
  # end
30
- def command! command, *args, &block
30
+ def command! command, *_args, &block
31
31
  id = (command_lut << command)
32
32
  define_method :"_command_#{id}", &block
33
33
  end
@@ -40,11 +40,11 @@ module Blur
40
40
 
41
41
  klass.extend ClassMethods
42
42
  klass.command_lut = CommandLUT.new
43
- klass.register! message: -> (script, user, channel, line) {
43
+ klass.register! message: lambda { |script, user, channel, line|
44
44
  command, args = line.split ' ', 2
45
45
  return unless command
46
46
 
47
- if id = klass.command_lut.commands[command.downcase]
47
+ if (id = klass.command_lut.commands[command.downcase])
48
48
  script.__send__ :"_command_#{id}", user, channel, args
49
49
  end
50
50
  }
@@ -56,7 +56,7 @@ module Blur
56
56
  attr_accessor :name, :authors, :version, :description, :events
57
57
 
58
58
  # Sets the author.
59
- #
59
+ #
60
60
  # @example
61
61
  # Author 'John Doe <john.doe@example.com>'
62
62
  def Author *authors
@@ -104,11 +104,16 @@ module Blur
104
104
  (@events[name] ||= []) << method_name
105
105
  end
106
106
 
107
- def to_s; inspect end
108
- def inspect; %%#<SuperScript:0x#{self.object_id.to_s 16}>% end
107
+ def to_s
108
+ inspect
109
+ end
110
+
111
+ def inspect
112
+ %(#<SuperScript:0x#{object_id.to_s 16}>)
113
+ end
109
114
 
110
- alias :author :authors
111
- alias :Authors :Author
115
+ alias author authors
116
+ alias Authors Author
112
117
  end
113
118
 
114
119
  # Called when when the superscript has been loaded and added to the list of
@@ -122,7 +127,7 @@ module Blur
122
127
  # Reference to the main client that holds the script.
123
128
  attr_accessor :_client_ref
124
129
 
125
- # Script-specific configuration that is read from the main configuration
130
+ # Script-specific configuration that is read from the main configuration
126
131
  # file.
127
132
  attr_accessor :config
128
133
 
@@ -132,7 +137,9 @@ module Blur
132
137
  def unloaded; end
133
138
 
134
139
  # Gets the instantiated script with +name+.
135
- def script name; _client_ref.scripts[name] end
140
+ def script name
141
+ _client_ref.scripts[name]
142
+ end
136
143
 
137
144
  # Gets a human-readable representation of the script.
138
145
  def inspect
@@ -142,6 +149,8 @@ module Blur
142
149
  "@description=#{self.class.description.inspect}>"
143
150
  end
144
151
 
145
- def to_s; inspect end
152
+ def to_s
153
+ inspect
154
+ end
146
155
  end
147
156
  end
@@ -1,7 +1,7 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Blur
4
- class ScriptCache < Hash
4
+ class ScriptCache
5
5
  def initialize script_name, path, hash
6
6
  @script_name = script_name
7
7
  @path = path
@@ -9,20 +9,22 @@ module Blur
9
9
  end
10
10
 
11
11
  # Gets a cache +value+ by its +key+.
12
- def [] key; @hash[key] end
12
+ def [] key
13
+ @hash[key]
14
+ end
13
15
 
14
16
  # Sets the cache +key+ to the provided +value+.
15
- def []= key, value; @hash[key] = value end
17
+ def []= key, value
18
+ @hash[key] = value
19
+ end
16
20
 
17
21
  # Saves the cache as a YAML file.
18
22
  def save
19
23
  directory = File.dirname @path
20
-
21
- unless File.directory? directory
22
- Dir.mkdir directory
23
- end
24
24
 
25
- File.open @path, ?w do |file|
25
+ Dir.mkdir directory unless File.directory? directory
26
+
27
+ File.open @path, 'w' do |file|
26
28
  YAML.dump @hash, file
27
29
  end
28
30
  end
@@ -31,7 +33,7 @@ module Blur
31
33
  def self.load script_name, cache_dir
32
34
  cache_path = File.join cache_dir, "#{script_name}.yml"
33
35
 
34
- if File.exists? cache_path
36
+ if File.exist? cache_path
35
37
  object = YAML.load_file cache_path
36
38
 
37
39
  ScriptCache.new script_name, cache_path, object
data/library/blur/user.rb CHANGED
@@ -1,11 +1,11 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Blur
4
4
  # The +User+ class is used for encapsulating a user and its properties.
5
5
  #
6
6
  # The user owns a reference to its parent channel.
7
7
  #
8
- # Modes can be set for a user, but Blur is not
8
+ # Modes can be set for a user, but Blur is not
9
9
  # {http://www.irc.org/tech_docs/005.html ISupport}-compliant yet.
10
10
  #
11
11
  # @todo make so that channels *and* users belongs to the network, and not
@@ -24,16 +24,39 @@ module Blur
24
24
  attr_accessor :network
25
25
  attr_accessor :channels
26
26
 
27
+ # @return [Hash<String, String>] a map of symbols to channel user modes.
28
+ COMMON_SYMBOL_MODES = {
29
+ '@' => 'o',
30
+ '+' => 'v',
31
+ '%' => 'h',
32
+ '&' => 'a',
33
+ '~' => 'q'
34
+ }.freeze
35
+
27
36
  # Check to see if the user is an admin (+a)
28
- def admin?; @modes.include? "a" end
37
+ def admin?
38
+ @modes.include? 'a'
39
+ end
40
+
29
41
  # Check to see if the user has voice (+v)
30
- def voice?; @modes.include? "v" end
42
+ def voice?
43
+ @modes.include? 'v'
44
+ end
45
+
31
46
  # Check to see if the user is the owner (+q)
32
- def owner?; @modes.include? "q" end
47
+ def owner?
48
+ @modes.include? 'q'
49
+ end
50
+
33
51
  # Check to see if the user is an operator (+o)
34
- def operator?; @modes.include? "o" end
52
+ def operator?
53
+ @modes.include? 'o'
54
+ end
55
+
35
56
  # Check to see if the user is an half-operator (+h)
36
- def half_operator?; @modes.include? "h" end
57
+ def half_operator?
58
+ @modes.include? 'h'
59
+ end
37
60
 
38
61
  # Instantiate a user with a nickname.
39
62
  def initialize nick, network = nil
@@ -41,11 +64,11 @@ module Blur
41
64
  @modes = String.new
42
65
  @channels = []
43
66
  @network = network
44
-
45
- if modes = prefix_to_mode(nick[0])
46
- @nick = nick[1..-1]
47
- @modes = modes
48
- end
67
+
68
+ return unless (modes = prefix_to_mode(nick[0]))
69
+
70
+ @nick = nick[1..]
71
+ @modes = modes
49
72
  end
50
73
 
51
74
  # Merge the users mode corresponding to the leading character (+ or -).
@@ -56,26 +79,26 @@ module Blur
56
79
 
57
80
  modes.each_char do |char|
58
81
  case char
59
- when ?+
82
+ when '+'
60
83
  addition = true
61
- when ?-
84
+ when '-'
62
85
  addition = false
63
86
  else
64
87
  addition ? @modes.concat(char) : @modes.delete!(char)
65
88
  end
66
89
  end
67
90
  end
68
-
91
+
69
92
  # Send a private message to the user.
70
93
  #
71
94
  # @param [String] message the message to send.
72
95
  def say message
73
96
  @network.say self, message
74
97
  end
75
-
98
+
76
99
  # Convert it to a debug-friendly format.
77
100
  def inspect
78
- %{#<#{self.class.name}:0x#{self.object_id.to_s 16} @nick=#{@nick.inspect}>}
101
+ %(#<#{self.class.name}:0x#{object_id.to_s 16} @nick=#{@nick.inspect}>)
79
102
  end
80
103
 
81
104
  # Called when YAML attempts to save the object, which happens when a
@@ -83,23 +106,17 @@ module Blur
83
106
  def to_yaml options = {}
84
107
  @nick.to_yaml options
85
108
  end
86
-
109
+
87
110
  # Get the users nickname.
88
111
  def to_s
89
112
  @nick
90
113
  end
91
114
 
92
- private
115
+ private
93
116
 
94
117
  # Translate a nickname-prefix to a mode character.
95
118
  def prefix_to_mode prefix
96
- case prefix
97
- when '@' then 'o'
98
- when '+' then 'v'
99
- when '%' then 'h'
100
- when '&' then 'a'
101
- when '~' then 'q'
102
- end
119
+ COMMON_SYMBOL_MODES[prefix]
103
120
  end
104
121
  end
105
122
  end
@@ -1,13 +1,13 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Blur
4
4
  # The current version of Blur.
5
- Version = '2.1.3'.freeze
5
+ VERSION = '2.1.6'
6
6
 
7
7
  # Get the current version.
8
8
  #
9
9
  # @return [String] The current version of Blur.
10
10
  def self.version
11
- Version
11
+ VERSION
12
12
  end
13
13
  end
data/library/blur.rb CHANGED
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'yaml'
4
4
  require 'socket'
@@ -11,28 +11,30 @@ require 'eventmachine'
11
11
  require 'ircparser'
12
12
 
13
13
  # Require all library files.
14
- require 'blur/logging'
15
- require 'blur/version'
16
- require 'blur/callbacks'
17
- require 'blur/script'
18
- require 'blur/script_cache'
19
- require 'blur/network'
20
- require 'blur/client'
21
- require 'blur/user'
22
- require 'blur/channel'
23
- require 'blur/enhancements'
24
- require 'blur/network/isupport'
25
- require 'blur/network/connection'
14
+ require_relative './blur/version'
15
+ require_relative './blur/callbacks'
16
+ require_relative './blur/script'
17
+ require_relative './blur/script_cache'
18
+ require_relative './blur/network'
19
+ require_relative './blur/client'
20
+ require_relative './blur/user'
21
+ require_relative './blur/channel'
22
+ require_relative './blur/network/isupport'
23
+ require_relative './blur/network/connection'
26
24
 
27
25
  # Blur is a very modular IRC-framework for ruby.
28
26
  #
29
27
  # It allows the developer to extend it in multiple ways.
30
28
  # It can be by handlers, scripts, communications, and what have you.
31
29
  module Blur
30
+ # Client error.
31
+ class ClientError < StandardError; end
32
+
33
+ # Configuration file error.
32
34
  class ConfigError < StandardError; end
33
35
 
34
36
  # Creates a new superscript class and inserts it into the list of scripts.
35
- def self.Script name, *args, &block
37
+ def self.Script name, *_args, &block
36
38
  klass = Class.new SuperScript
37
39
  klass.name = name
38
40
  klass.events = {}