blur 2.1.2 → 2.1.3

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