blur 2.1.2 → 2.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d3972a328bb47b223b4795b59b416636411a9aa5ab1930ca3cb1b924b2c24f2c
4
- data.tar.gz: 8aa0590f665280b6d637bbe7e14a0a44a1afa3c090cd3d0eb6d6c1218269671b
3
+ metadata.gz: 8e3a0bf486ae6b8b30c925220195462b412e23bd62e767e2996c698273d5bf18
4
+ data.tar.gz: 733539cbd3f671eb799f541523031ca42303184db7866b5113058486c664c95a
5
5
  SHA512:
6
- metadata.gz: d2cfc568774d57ab0ec786237c0055a5953936b2fbf39a2815dc1d94375ec8e0805f76ad09f194db852f5d80a944dd328e94dd8f63b770775d749002b2e1bb49
7
- data.tar.gz: 5983e426f4f1e045e203005c9f43b8f252935d326ad1f10bdf342d796676ff143d6ed60271596d77d4aca01d941ebd32241eafa64d9f682e6a7c8ec61baf8bc3
6
+ metadata.gz: 8517510ecd51f045b995fcd42d00d1da7587d7fb2f36911e59bc6ed9f2478e7076b2bcdfca1290001e276392f3ad63dc9829fb90a0b67432e1223ca4bde5e48f
7
+ data.tar.gz: 0bac6a2de2bc5d1d8294ba2d9342ffde0d39fedc40b318d73a5c5e187c1de298ad371a94bf4c67bd6433ee7ba896d24cd945408c869d3c4a4149a289b54ac6fd
data/README.md CHANGED
@@ -1,5 +1,4 @@
1
- Blur
2
- ====
1
+ # Blur
3
2
  Blur is an event-driven IRC-framework written in and for Ruby.
4
3
 
5
4
  There are a bunch of other well-written, well-running IRC libraries made for
@@ -7,18 +6,16 @@ Ruby, but for me, they don't quite cut it as **the** library I wanted to use for
7
6
  my IRC services. That's how Blur came to be.
8
7
 
9
8
  [![Build Status](https://travis-ci.org/mkroman/blur.svg?branch=isupport)](https://travis-ci.org/mkroman/blur)
10
- [![Dependency Status](https://gemnasium.com/mkroman/blur.svg)](https://gemnasium.com/mkroman/blur)
11
9
 
12
- Features
13
- --------
14
- * SSL/TLS encryption
15
- * Connect to multiple networks
16
- * Non-blocking connections (no threading)
17
- * Extensible with scripts, (re)loadable during runtime
18
- * Modular, makes it a piece of cake to extend its IRC-capability
10
+ ## Getting started
19
11
 
20
- Future Plans
21
- ------------
22
- * DCC File-transfers
23
- * DH1080 Key-Exchange
24
- * Better event-handling in scripts
12
+
13
+ ## Documentation
14
+
15
+ Documentation is available [here](https://www.rubydoc.info/github/mkroman/blur)
16
+
17
+ ## Features
18
+ * SSL/TLS connections
19
+ * Connect to multiple networks in a single process
20
+ * Non-blocking connections (no threading just for networking)
21
+ * Extensible with scripts that are (re)loadable during runtime
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'yaml'
4
4
  require 'socket'
5
+ require 'base64'
5
6
  require 'ostruct'
6
7
  require 'openssl'
7
8
 
@@ -28,8 +29,7 @@ require 'blur/network/connection'
28
29
  # It allows the developer to extend it in multiple ways.
29
30
  # It can be by handlers, scripts, communications, and what have you.
30
31
  module Blur
31
- # Contains all superscript classes for scripts that may be used.
32
- @@scripts = {}
32
+ class ConfigError < StandardError; end
33
33
 
34
34
  # Creates a new superscript class and inserts it into the list of scripts.
35
35
  def self.Script name, *args, &block
@@ -39,12 +39,12 @@ module Blur
39
39
  klass.class_exec &block
40
40
  klass.init
41
41
 
42
- @@scripts[name] = klass
42
+ scripts[name] = klass
43
43
  end
44
44
 
45
45
  # Gets all superscript classes.
46
46
  def self.scripts
47
- @@scripts
47
+ @scripts ||= {}
48
48
  end
49
49
 
50
50
  # Resets all scripts.
@@ -52,8 +52,8 @@ module Blur
52
52
  # This method will call `deinit` on each script class before removing them to
53
53
  # give them a chance to clean up.
54
54
  def self.reset_scripts!
55
- @@scripts.each_value &:deinit
56
- @@scripts.clear
55
+ scripts.each_value &:deinit
56
+ scripts.clear
57
57
  end
58
58
 
59
59
  # Instantiates a client with given options and then makes the client instance
@@ -2,29 +2,27 @@
2
2
 
3
3
  module Blur
4
4
  module Callbacks
5
- # Our list of callbacks.
6
- @@callbacks = {}
7
-
8
5
  # Get a list of callbacks registered.
9
6
  #
10
7
  # @returns [Array] the list of callbacks
11
8
  def callbacks
12
- @@callbacks
9
+ @callbacks ||= {}
13
10
  end
14
11
 
15
12
  # Emit a new event with given arguments.
16
13
  #
17
14
  # @param name [Symbol] The event name.
18
15
  # @param args [optional, Array] The list of arguments to pass.
16
+ # @return [true, false] True if any callbacks were invoked, nil otherwise
19
17
  def emit name, *args
20
- EM.defer do
21
- notify_scripts name, *args
22
- end
18
+ # Trigger callbacks in scripts before triggering events in the client.
19
+ EM.defer { notify_scripts name, *args }
23
20
 
24
- if (callbacks = @@callbacks[name]) and callbacks.any?
25
- EM.defer do
26
- callbacks.each{|callback| callback.call *args }
27
- end
21
+ matching_callbacks = callbacks[name]
22
+ return false unless matching_callbacks&.any?
23
+
24
+ EM.defer do
25
+ matching_callbacks.each { |callback| callback.call *args }
28
26
  end
29
27
  end
30
28
 
@@ -33,10 +31,10 @@ module Blur
33
31
  # @param name [Symbol] The event name.
34
32
  # @yield [args, ...] The arguments passed from #emit.
35
33
  def on name, &block
36
- (@@callbacks[name] ||= []) << block
34
+ (callbacks[name] ||= []) << block
37
35
  end
38
36
 
39
- protected
37
+ protected
40
38
 
41
39
  def notify_scripts name, *args
42
40
  scripts = @scripts.values.select{|script| script.class.events.key? name }
@@ -9,13 +9,14 @@ module Blur
9
9
  # distributing the incoming commands to the right networks and scripts.
10
10
  class Client
11
11
  include Callbacks
12
- include Handling, Logging
12
+ include Handling
13
+ include Logging
13
14
 
14
15
  # Client error.
15
16
  Error = Class.new StandardError
16
17
 
17
18
  # The default environment.
18
- ENVIRONMENT = ENV['BLUR_ENV'] || 'development'
19
+ ENVIRONMENT = ENV['BLUR_ENV'] || 'development'.freeze
19
20
 
20
21
  # The default configuration.
21
22
  DEFAULT_CONFIG = {
@@ -33,8 +34,10 @@ module Blur
33
34
  attr_accessor :config
34
35
  # @return [Hash] initialized scripts.
35
36
  attr_accessor :scripts
36
-
37
+ # @return [Boolean] whether verbose logging is enabled.
37
38
  attr_accessor :verbose
39
+ # @return [String] the path to the currently used config file.
40
+ attr_accessor :config_path
38
41
 
39
42
  # Instantiates the client, stores the options, instantiates the networks
40
43
  # and then loads available scripts.
@@ -49,11 +52,15 @@ module Blur
49
52
  @environment = options[:environment]
50
53
  @verbose = options[:verbose] == true
51
54
 
55
+ unless @config_path
56
+ raise ConfigError, 'missing config file path in :config_path option'
57
+ end
58
+
52
59
  load_config!
53
60
 
54
61
  networks = @config['blur']['networks']
55
62
 
56
- if networks and networks.any?
63
+ if networks&.any?
57
64
  networks.each do |network_options|
58
65
  @networks.<< Network.new network_options, self
59
66
  end
@@ -106,10 +113,10 @@ module Blur
106
113
  # @param [optional, Symbol] signal The signal received by the system, if any.
107
114
  def quit signal = :SIGINT
108
115
  @networks.each do |network|
109
- network.transmit :QUIT, "Got SIGINT?"
116
+ network.transmit :QUIT, 'Got SIGINT?'
110
117
  network.disconnect
111
118
  end
112
-
119
+
113
120
  EventMachine.stop
114
121
  end
115
122
 
@@ -127,20 +134,49 @@ module Blur
127
134
  # Loads all scripts in the script directory.
128
135
  def load_scripts!
129
136
  scripts_dir = File.expand_path @config['blur']['scripts_dir']
130
- scripts_cache_dir = File.expand_path @config['blur']['cache_dir']
131
-
132
- Dir.glob File.join(scripts_dir, '*.rb') do |file|
133
- begin
134
- load file, true
135
- rescue Exception => e
136
- STDERR.puts "The script `#{file}' failed to load"
137
- STDERR.puts "#{e.class}: #{e.message}"
138
- STDERR.puts
139
- STDERR.puts 'Backtrace:', '---', e.backtrace
140
- end
137
+ script_file_paths = Dir.glob File.join scripts_dir, '*.rb'
138
+
139
+ # Sort the script file paths by file name so they load by alphabetical
140
+ # order.
141
+ #
142
+ # This will make it possible to create a script called '10_database.rb'
143
+ # which will be loaded before '20_settings.rb' and non-numeric prefixes
144
+ # will be loaded after that.
145
+ script_file_paths = script_file_paths.sort do |a, b|
146
+ File.basename(a) <=> File.basename(b)
141
147
  end
142
148
 
149
+ script_file_paths.each { |script_path| load_script_file script_path }
150
+
151
+ initialize_superscripts
152
+
153
+ emit :scripts_loaded
154
+ end
155
+
156
+ # Loads the given +file_path+ as a Ruby script, wrapping it in an anonymous
157
+ # module to protect our global namespace.
158
+ #
159
+ # @param [String] file_path the path to the ruby script.
160
+ #
161
+ # @raise [Exception] if there was any problems loading the file
162
+ def load_script_file file_path
163
+ load file_path, true
164
+ rescue Exception => exception
165
+ warn "The script `#{file_path}' failed to load"
166
+ warn "#{exception.class}: #{exception.message}"
167
+ warn ''
168
+ warn 'Backtrace:', '---', exception.backtrace
169
+ end
170
+
171
+ # Instantiates each +SuperScript+ in the +Blur.scripts+ list by manually
172
+ # allocating an instance and calling #initialize on it, then the instance is
173
+ # stored in +Client#scripts+.
174
+ #
175
+ # @raise [Exception] any exception that might occur in any scripts'
176
+ # #initialize method.
177
+ def initialize_superscripts
143
178
  scripts_config = @config['scripts']
179
+ scripts_cache_dir = File.expand_path @config['blur']['cache_dir']
144
180
 
145
181
  Blur.scripts.each do |name, superscript|
146
182
  script = superscript.allocate
@@ -151,8 +187,6 @@ module Blur
151
187
 
152
188
  @scripts[name] = script
153
189
  end
154
-
155
- emit :scripts_loaded
156
190
  end
157
191
 
158
192
  # Unloads initialized scripts and superscripts.
@@ -167,7 +201,7 @@ module Blur
167
201
  Blur.reset_scripts!
168
202
  end
169
203
 
170
- private
204
+ private
171
205
 
172
206
  # Load the user-specified configuration file.
173
207
  #
@@ -247,6 +247,67 @@ module Blur
247
247
  network.isupport.parse *params
248
248
  end
249
249
 
250
+ # Received when the server supports capability negotiation.
251
+ def got_cap network, message
252
+ id, command = message.parameters[0..1]
253
+
254
+ case command
255
+ when 'ACK'
256
+ capabilities = message.parameters[2]&.split
257
+
258
+ if capabilities&.include? 'sasl' and network.sasl?
259
+ network.transmit :AUTHENTICATE, 'PLAIN'
260
+ else
261
+ network.cap_end
262
+ end
263
+ when 'NAK'
264
+ capabilities = message.parameters[2]&.split
265
+
266
+ if capabilities&.include? 'sasl' and network.sasl?
267
+ puts "The server does not support SASL, but you've configured it " \
268
+ "as such! Disconnecting!"
269
+
270
+ network.disconnect
271
+ end
272
+
273
+ end
274
+ end
275
+
276
+ def got_001 network, message
277
+ if network.waiting_for_cap
278
+ network.abort_cap_neg
279
+ end
280
+ end
281
+
282
+ def got_authenticate network, message
283
+ case message.parameters[0]
284
+ when '+'
285
+ return unless network.sasl?
286
+ sasl = network.options['sasl']
287
+
288
+ response = "#{sasl['username']}\x00#{sasl['username']}\x00#{sasl['password']}"
289
+ network.transmit :AUTHENTICATE, Base64.encode64(response).strip
290
+ end
291
+ end
292
+
293
+ # :server 900 <nick> <nick>!<ident>@<host> <account> :You are now logged in as <user>
294
+ # RPL_LOGGEDIN SASL
295
+ def got_900 network, message
296
+ if network.waiting_for_cap
297
+ network.cap_end
298
+ end
299
+ end
300
+
301
+ # :server 904 <nick> :SASL authentication failed
302
+ # ERR_SASLFAIL
303
+ def got_904 network, message
304
+ nick, message = message.parameters
305
+
306
+ puts "SASL authentication failed! Disconnecting!"
307
+
308
+ network.disconnect
309
+ end
310
+
250
311
  alias_method :got_353, :got_name_reply
251
312
  alias_method :got_422, :got_end_of_motd
252
313
  alias_method :got_376, :got_end_of_motd
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Blur
4
4
  # The +Network+ module is to be percieved as an IRC network.
@@ -7,13 +7,21 @@ module Blur
7
7
  # for network-related structures, such as {User}, {Channel} and {Command}.
8
8
  class Network
9
9
  include Logging
10
-
10
+
11
11
  # +ConnectionError+ should only be triggered from within {Connection}.
12
12
  class ConnectionError < StandardError; end
13
13
 
14
+ # Returns a unique identifier for this network.
15
+ #
16
+ # You can override the id in your network configuration by setting an 'id'
17
+ # key with the id you want.. If no id is specified, the the id will be
18
+ # constructed from the hostname and port number
19
+ # in the format "<host>:<port>"
20
+ #
21
+ # @return [String] the unique identifier for this network.
22
+ attr_reader :id
14
23
  # @return [Hash] the network options.
15
24
  attr_accessor :options
16
-
17
25
  # @return [Hash] the map of users that is known.
18
26
  attr_accessor :users
19
27
  # @return [Hash] the map of channels the client is in.
@@ -24,24 +32,41 @@ module Blur
24
32
  attr_accessor :connection
25
33
  # @return [Network::ISupport] the network isupport specs.
26
34
  attr_accessor :isupport
35
+ # @return [Boolean] true if we're waiting for a capability negotiation.
36
+ attr_reader :waiting_for_cap
27
37
 
28
38
  # Check whether or not connection is established.
29
- def connected?; @connection and @connection.established? end
39
+ def connected?
40
+ @connection&.established?
41
+ end
30
42
 
31
43
  # Get the remote hostname.
32
44
  #
33
45
  # @return [String] the remote hostname.
34
- def host; @options['hostname'] end
46
+ def host
47
+ @options['hostname']
48
+ end
35
49
 
36
50
  # Get the remote port.
37
51
  # If no port is specified, it returns 6697 if using a secure connection,
38
52
  # returns 6667 otherwise.
39
53
  #
40
54
  # @return [Fixnum] the remote port
41
- def port; @options['port'] ||= secure? ? 6697 : 6667 end
42
-
55
+ def port
56
+ @options['port'] ||= secure? ? 6697 : 6667
57
+ end
58
+
43
59
  # Check to see if it's a secure connection.
44
- def secure?; @options['secure'] == true end
60
+ def secure?
61
+ @options['secure'] == true
62
+ end
63
+
64
+ # @return [Boolean] whether we want to authenticate with SASL.
65
+ def sasl?
66
+ @options['sasl'] &&
67
+ @options['sasl']['username'] &&
68
+ @options['sasl']['password']
69
+ end
45
70
 
46
71
  # Instantiates the network.
47
72
  #
@@ -52,17 +77,17 @@ module Blur
52
77
  # @option options [optional, String] :username (Copies :nickname)
53
78
  # The username to use. This is also known as the ident.
54
79
  # @option options [optional, String] :realname (Copies :username)
55
- # The real name that we want to use. This is usually what shows up
80
+ # The "real name" that we want to use. This is usually what shows up
56
81
  # as "Name" when you whois a user.
57
82
  # @option options [optional, String] :password The password for the network.
58
83
  # This is sometimes needed for private networks.
59
- # @option options [optional, Fixnum] :port (6697 if ssl, otherwise 6667)
84
+ # @option options [optional, Fixnum] :port (6697 if ssl, otherwise 6667)
60
85
  # The remote port we want to connect to.
61
86
  # @option options [optional, Boolean] :secure Set whether this is a secure
62
87
  # (SSL-encrypted) connection.
63
88
  # @option options [optional, String] :ssl_cert_file Local path of a
64
89
  # readable file that contains a X509 CA certificate to validate against.
65
- # @option options [optional, String] :ssl_fingerprint Validate that the
90
+ # @option options [optional, String] :ssl_fingerprint Validate that the
66
91
  # remote certificate matches the specified fingerprint.
67
92
  # @option options [optional, Boolean] :ssl_no_verify Disable verification
68
93
  # alltogether.
@@ -72,20 +97,18 @@ module Blur
72
97
  @users = {}
73
98
  @channels = {}
74
99
  @isupport = ISupport.new self
75
-
100
+
76
101
  unless options['nickname']
77
- if options['hostname']
78
- raise ArgumentError, "Network configuration for `#{options['hostname']}' is missing a nickname"
79
- else
80
- raise ArgumentError, "Network configuration is missing a nickname"
81
- end
102
+ raise ArgumentError, 'Network configuration for ' \
103
+ "`#{id}' is missing a nickname"
82
104
  end
83
-
105
+
84
106
  @options['username'] ||= @options['nickname']
85
107
  @options['realname'] ||= @options['username']
86
108
  @options['channels'] ||= []
109
+ @id = options.fetch 'id', "#{host}:#{port}"
87
110
  end
88
-
111
+
89
112
  # Send a message to a recipient.
90
113
  #
91
114
  # @param [String, #to_s] recipient the recipient.
@@ -93,53 +116,55 @@ module Blur
93
116
  def say recipient, message
94
117
  transmit :PRIVMSG, recipient.to_s, message
95
118
  end
96
-
119
+
120
+ # Forwards the received message to the client instance.
121
+ #
97
122
  # Called when the network connection has enough data to form a command.
98
123
  def got_message message
99
124
  @client.got_message self, message
100
- rescue => e
101
- puts "#{e.class}: #{e.message}"
125
+ rescue StandardError => exception
126
+ puts "#{exception.class}: #{exception.message}"
102
127
  puts
103
- puts "---"
104
- puts e.backtrace
128
+ puts '---'
129
+ puts exception.backtrace
105
130
  end
106
-
131
+
107
132
  # Find a channel by its name.
108
133
  #
109
134
  # @param [String] name the channel name.
110
135
  # @return [Network::Channel] the matching channel, or nil.
111
136
  def channel_by_name name
112
- @channels.find {|channel| channel.name == name }
137
+ @channels.find { |channel| channel.name == name }
113
138
  end
114
-
139
+
115
140
  # Find all instances of channels in which there is a user with the nick
116
141
  # +nick+.
117
142
  #
118
143
  # @param [String] nick the nickname.
119
144
  # @return [Array] a list of channels in which the user is located, or nil.
120
145
  def channels_with_user nick
121
- @channels.select {|channel| channel.user_by_nick nick }
146
+ @channels.select { |channel| channel.user_by_nick nick }
122
147
  end
123
148
 
124
149
  # Returns a list of user prefixes that a nick might contain.
125
150
  #
126
151
  # @return [Array<String>] a list of user prefixes.
127
152
  def user_prefixes
128
- isupport["PREFIX"].values
153
+ isupport['PREFIX'].values
129
154
  end
130
155
 
131
156
  # Returns a list of user modes that also gives a users nick a prefix.
132
157
  #
133
158
  # @return [Array<String>] a list of user modes.
134
159
  def user_prefix_modes
135
- isupport["PREFIX"].keys
160
+ isupport['PREFIX'].keys
136
161
  end
137
162
 
138
163
  # Returns a list of channel flags (channel mode D).
139
164
  #
140
165
  # @return [Array<String>] a list of channel flags.
141
166
  def channel_flags
142
- isupport["CHANMODES"]["D"]
167
+ isupport['CHANMODES']['D']
143
168
  end
144
169
 
145
170
  # Attempt to establish a connection and send initial data.
@@ -151,25 +176,45 @@ module Blur
151
176
 
152
177
  # Called when the connection was successfully established.
153
178
  def connected!
179
+ if sasl?
180
+ @waiting_for_cap = true
181
+
182
+ transmit :CAP, 'REQ', 'sasl'
183
+ end
184
+
154
185
  transmit :PASS, @options['password'] if @options['password']
155
186
  transmit :NICK, @options['nickname']
156
187
  transmit :USER, @options['username'], 'void', 'void', @options['realname']
157
188
  end
158
189
 
190
+ # Called when the server doesn't support capability negotiation.
191
+ def abort_cap_neg
192
+ @waiting_for_cap = false
193
+
194
+ puts "Server does not support capability negotiation"
195
+ end
196
+
197
+ # Called when we're done with capability negotiation.
198
+ def cap_end
199
+ @waiting_for_cap = false
200
+
201
+ transmit :CAP, 'END'
202
+ end
203
+
159
204
  # Called when the connection was closed.
160
205
  def disconnected!
161
- @channels.each {|name, channel| channel.users.clear }
206
+ @channels.each { |_name, channel| channel.users.clear }
162
207
  @channels.clear
163
208
  @users.clear
164
209
 
165
210
  @client.network_connection_closed self
166
211
  end
167
-
212
+
168
213
  # Terminate the connection and clear all channels and users.
169
214
  def disconnect
170
215
  @connection.close_connection_after_writing
171
216
  end
172
-
217
+
173
218
  # Transmit a command to the server.
174
219
  #
175
220
  # @param [Symbol, String] name the command name.
@@ -178,9 +223,11 @@ module Blur
178
223
  message = IRCParser::Message.new command: name.to_s, parameters: arguments
179
224
 
180
225
  if @client.verbose
181
- log "#{'→' ^ :red} #{message.command.to_s.ljust(8, ' ') ^ :light_gray} #{message.parameters.map(&:inspect).join ' '}"
226
+ formatted_command = message.command.to_s.ljust 8, ' '
227
+ formatted_params = message.parameters.map(&:inspect).join ' '
228
+ log "#{'→' ^ :red} #{formatted_command} #{formatted_params}"
182
229
  end
183
-
230
+
184
231
  @connection.send_data "#{message}\r\n"
185
232
  end
186
233
 
@@ -196,7 +243,7 @@ module Blur
196
243
 
197
244
  # Convert it to a debug-friendly format.
198
245
  def to_s
199
- %{#<#{self.class.name} "#{host}":#{port}>}
246
+ %(#<#{self.class.name} "#{host}":#{port}>)
200
247
  end
201
248
  end
202
249
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Blur
4
4
  # The current version of Blur.
5
- Version = '2.1.2'.freeze
5
+ Version = '2.1.3'.freeze
6
6
 
7
7
  # Get the current version.
8
8
  #
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blur
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.2
4
+ version: 2.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikkel Kroman
8
8
  autorequire:
9
9
  bindir: executables
10
10
  cert_chain: []
11
- date: 2018-03-04 00:00:00.000000000 Z
11
+ date: 2019-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: deep_merge
@@ -96,8 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
96
  - !ruby/object:Gem::Version
97
97
  version: '0'
98
98
  requirements: []
99
- rubyforge_project:
100
- rubygems_version: 2.7.6
99
+ rubygems_version: 3.0.3
101
100
  signing_key:
102
101
  specification_version: 4
103
102
  summary: An event-driven IRC-framework for Ruby.