codebot 1.2.0
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 +7 -0
- data/.github/ISSUE_TEMPLATE.md +32 -0
- data/.github/ISSUE_TEMPLATE/formatter_issue.md +20 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +13 -0
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/.rubocop.yml +11 -0
- data/.travis.yml +26 -0
- data/CODE_OF_CONDUCT.md +46 -0
- data/CONTRIBUTING.md +15 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +75 -0
- data/LICENSE +21 -0
- data/README.md +230 -0
- data/Rakefile +29 -0
- data/bin/console +8 -0
- data/codebot.gemspec +49 -0
- data/exe/codebot +7 -0
- data/lib/codebot.rb +8 -0
- data/lib/codebot/channel.rb +134 -0
- data/lib/codebot/command_error.rb +17 -0
- data/lib/codebot/config.rb +125 -0
- data/lib/codebot/configuration_error.rb +17 -0
- data/lib/codebot/core.rb +76 -0
- data/lib/codebot/cryptography.rb +38 -0
- data/lib/codebot/event.rb +62 -0
- data/lib/codebot/ext/cinch/ssl_extensions.rb +37 -0
- data/lib/codebot/formatter.rb +242 -0
- data/lib/codebot/formatters.rb +109 -0
- data/lib/codebot/formatters/.rubocop.yml +2 -0
- data/lib/codebot/formatters/commit_comment.rb +43 -0
- data/lib/codebot/formatters/fork.rb +40 -0
- data/lib/codebot/formatters/gitlab_issue_hook.rb +56 -0
- data/lib/codebot/formatters/gitlab_job_hook.rb +77 -0
- data/lib/codebot/formatters/gitlab_merge_request_hook.rb +57 -0
- data/lib/codebot/formatters/gitlab_note_hook.rb +119 -0
- data/lib/codebot/formatters/gitlab_pipeline_hook.rb +51 -0
- data/lib/codebot/formatters/gitlab_push_hook.rb +83 -0
- data/lib/codebot/formatters/gitlab_wiki_page_hook.rb +56 -0
- data/lib/codebot/formatters/gollum.rb +67 -0
- data/lib/codebot/formatters/issue_comment.rb +41 -0
- data/lib/codebot/formatters/issues.rb +41 -0
- data/lib/codebot/formatters/ping.rb +79 -0
- data/lib/codebot/formatters/public.rb +30 -0
- data/lib/codebot/formatters/pull_request.rb +71 -0
- data/lib/codebot/formatters/pull_request_review_comment.rb +49 -0
- data/lib/codebot/formatters/push.rb +172 -0
- data/lib/codebot/formatters/watch.rb +38 -0
- data/lib/codebot/integration.rb +195 -0
- data/lib/codebot/integration_manager.rb +225 -0
- data/lib/codebot/ipc_client.rb +83 -0
- data/lib/codebot/ipc_server.rb +79 -0
- data/lib/codebot/irc_client.rb +102 -0
- data/lib/codebot/irc_connection.rb +156 -0
- data/lib/codebot/message.rb +37 -0
- data/lib/codebot/metadata.rb +15 -0
- data/lib/codebot/network.rb +240 -0
- data/lib/codebot/network_manager.rb +181 -0
- data/lib/codebot/options.rb +49 -0
- data/lib/codebot/options/base.rb +55 -0
- data/lib/codebot/options/core.rb +126 -0
- data/lib/codebot/options/integration.rb +101 -0
- data/lib/codebot/options/network.rb +109 -0
- data/lib/codebot/payload.rb +32 -0
- data/lib/codebot/request.rb +51 -0
- data/lib/codebot/sanitizers.rb +130 -0
- data/lib/codebot/serializable.rb +101 -0
- data/lib/codebot/shortener.rb +43 -0
- data/lib/codebot/thread_controller.rb +70 -0
- data/lib/codebot/user_error.rb +13 -0
- data/lib/codebot/validation_error.rb +17 -0
- data/lib/codebot/web_listener.rb +107 -0
- data/lib/codebot/web_server.rb +58 -0
- data/webhook.png +0 -0
- metadata +249 -0
@@ -0,0 +1,225 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codebot/network_manager'
|
4
|
+
require 'codebot/command_error'
|
5
|
+
|
6
|
+
module Codebot
|
7
|
+
# This class manages the integrations associated with a configuration.
|
8
|
+
class IntegrationManager # rubocop:disable Metrics/ClassLength
|
9
|
+
# @return [Config] the configuration managed by this class
|
10
|
+
attr_reader :config
|
11
|
+
|
12
|
+
# Constructs a new integration manager for a specified configuration.
|
13
|
+
#
|
14
|
+
# @param config [Config] the configuration to manage
|
15
|
+
def initialize(config)
|
16
|
+
@config = config
|
17
|
+
end
|
18
|
+
|
19
|
+
# Creates a new integration from the given parameters.
|
20
|
+
#
|
21
|
+
# @param params [Hash] the parameters to initialize the integration with
|
22
|
+
def create(params)
|
23
|
+
integration = Integration.new(
|
24
|
+
params.merge(config: { networks: @config.networks })
|
25
|
+
)
|
26
|
+
@config.transaction do
|
27
|
+
check_available!(integration.name, integration.endpoint)
|
28
|
+
NetworkManager.new(@config).check_channels!(integration)
|
29
|
+
@config.integrations << integration
|
30
|
+
integration_feedback(integration, :created) unless params[:quiet]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Updates an integration with the given parameters.
|
35
|
+
#
|
36
|
+
# @param name [String] the current name of the integration to update
|
37
|
+
# @param params [Hash] the parameters to update the integration with
|
38
|
+
def update(name, params)
|
39
|
+
@config.transaction do
|
40
|
+
integration = find_integration!(name)
|
41
|
+
check_available_except!(params[:name], params[:endpoint], integration)
|
42
|
+
update_channels!(integration, params)
|
43
|
+
NetworkManager.new(@config).check_channels!(integration)
|
44
|
+
integration.update!(params)
|
45
|
+
integration_feedback(integration, :updated) unless params[:quiet]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Destroys an integration.
|
50
|
+
#
|
51
|
+
# @param name [String] the name of the integration to destroy
|
52
|
+
# @param params [Hash] the command-line options
|
53
|
+
def destroy(name, params)
|
54
|
+
@config.transaction do
|
55
|
+
integration = find_integration!(name)
|
56
|
+
@config.integrations.delete integration
|
57
|
+
integration_feedback(integration, :destroyed) unless params[:quiet]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Lists all integrations, or integrations with names containing the given
|
62
|
+
# search term.
|
63
|
+
#
|
64
|
+
# @param search [String, nil] an optional search term
|
65
|
+
def list(search)
|
66
|
+
@config.transaction do
|
67
|
+
integrations = @config.integrations.dup
|
68
|
+
unless search.nil?
|
69
|
+
integrations.select! do |intg|
|
70
|
+
intg.name.downcase.include? search.downcase
|
71
|
+
end
|
72
|
+
end
|
73
|
+
puts 'No integrations found' if integrations.empty?
|
74
|
+
integrations.each { |intg| show_integration intg }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Finds an integration given its name.
|
79
|
+
#
|
80
|
+
# @param name [String] the name to search for
|
81
|
+
# @return [Integration, nil] the integration, or +nil+ if none was found
|
82
|
+
def find_integration(name)
|
83
|
+
@config.integrations.find { |intg| intg.name_eql? name }
|
84
|
+
end
|
85
|
+
|
86
|
+
# Finds an integration given its endpoint.
|
87
|
+
#
|
88
|
+
# @param endpoint [String] the endpoint to search for
|
89
|
+
# @return [Integration, nil] the integration, or +nil+ if none was found
|
90
|
+
def find_integration_by_endpoint(endpoint)
|
91
|
+
@config.integrations.find { |intg| intg.endpoint_eql? endpoint }
|
92
|
+
end
|
93
|
+
|
94
|
+
# Finds an integration given its name.
|
95
|
+
#
|
96
|
+
# @param name [String] the name to search for
|
97
|
+
# @raise [CommandError] if no integration with the given name exists
|
98
|
+
# @return [Integration] the integration
|
99
|
+
def find_integration!(name)
|
100
|
+
integration = find_integration(name)
|
101
|
+
return integration unless integration.nil?
|
102
|
+
|
103
|
+
raise CommandError, "an integration with the name #{name.inspect} " \
|
104
|
+
'does not exist'
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
# Checks that the specified name is available for use.
|
110
|
+
#
|
111
|
+
# @param name [String] the name to check for
|
112
|
+
# @raise [CommandError] if the name is already taken
|
113
|
+
def check_name_available!(name)
|
114
|
+
return unless find_integration(name)
|
115
|
+
|
116
|
+
raise CommandError, "an integration with the name #{name.inspect} " \
|
117
|
+
'already exists'
|
118
|
+
end
|
119
|
+
|
120
|
+
# Checks that the specified endpoint is available for use.
|
121
|
+
#
|
122
|
+
# @param endpoint [String] the endpoint to check for
|
123
|
+
# @raise [CommandError] if the endpoint is already taken
|
124
|
+
def check_endpoint_available!(endpoint)
|
125
|
+
return unless find_integration_by_endpoint(endpoint)
|
126
|
+
|
127
|
+
raise CommandError, 'an integration with the endpoint ' \
|
128
|
+
"#{endpoint.inspect} already exists"
|
129
|
+
end
|
130
|
+
|
131
|
+
# Checks that the specified name and endpoint are available for use.
|
132
|
+
#
|
133
|
+
# @param name [String] the name to check for
|
134
|
+
# @param endpoint [String] the endpoint to check for
|
135
|
+
# @raise [CommandError] if name or endpoint are already taken
|
136
|
+
def check_available!(name, endpoint)
|
137
|
+
check_name_available!(name) unless name.nil?
|
138
|
+
check_endpoint_available!(endpoint) unless endpoint.nil?
|
139
|
+
end
|
140
|
+
|
141
|
+
# Checks that the specified name and endpoint are available for use by the
|
142
|
+
# specified integration.
|
143
|
+
#
|
144
|
+
# @param name [String] the name to check for
|
145
|
+
# @param endpoint [String] the endpoint to check for
|
146
|
+
# @param intg [Integration] the integration to ignore
|
147
|
+
# @raise [CommandError] if name or endpoint are already taken
|
148
|
+
def check_available_except!(name, endpoint, intg)
|
149
|
+
check_name_available!(name) unless name.nil? || intg.name_eql?(name)
|
150
|
+
return if endpoint.nil? || intg.endpoint_eql?(endpoint)
|
151
|
+
|
152
|
+
check_endpoint_available!(endpoint)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Updates the channels associated with an integration from the specified
|
156
|
+
# parameters.
|
157
|
+
#
|
158
|
+
# @param integration [Integration] the integration
|
159
|
+
# @param params [Hash] the parameters to update the integration with. Valid
|
160
|
+
# keys are +:clear_channels+ to clear the channel list
|
161
|
+
# before proceeding, +:add_channel+ to add the given
|
162
|
+
# channels, and +:delete_channel+ to delete the given
|
163
|
+
# channels from the integration. All keys are optional.
|
164
|
+
# The value of +:clear_channels+ should be a boolean.
|
165
|
+
# The value of +:add_channel+ should be a hash of the
|
166
|
+
# form +identifier => params+, and +:remove_channel+
|
167
|
+
# should be an array of channel identifiers to remove.
|
168
|
+
def update_channels!(integration, params)
|
169
|
+
integration.channels.clear if params[:clear_channels]
|
170
|
+
if params[:delete_channel]
|
171
|
+
integration.delete_channels!(params[:delete_channel])
|
172
|
+
end
|
173
|
+
return unless params[:add_channel]
|
174
|
+
|
175
|
+
integration.add_channels!(params[:add_channel],
|
176
|
+
networks: @config.networks)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Displays feedback about a change made to an integration.
|
180
|
+
#
|
181
|
+
# @param integration [Integration] the integration
|
182
|
+
# @param action [#to_s] the action (+:created+, +:updated+ or +:destroyed+)
|
183
|
+
def integration_feedback(integration, action)
|
184
|
+
puts "Integration was successfully #{action}"
|
185
|
+
show_integration(integration)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Prints information about an integration.
|
189
|
+
#
|
190
|
+
# @param integration [Integration] the integration
|
191
|
+
def show_integration(integration)
|
192
|
+
puts "Integration: #{integration.name}"
|
193
|
+
puts "\tEndpoint: #{integration.endpoint}"
|
194
|
+
puts "\tSecret: #{show_integration_secret(integration)}"
|
195
|
+
if integration.channels.empty?
|
196
|
+
puts "\tChannels: (none)"
|
197
|
+
else
|
198
|
+
puts "\tChannels:"
|
199
|
+
show_integration_channels(integration)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Returns an integration secret, or "(none required)" if payload integrity
|
204
|
+
# verification is disabled.
|
205
|
+
#
|
206
|
+
# @param integration [Integration] the integration
|
207
|
+
# @return [String] the secret or placeholder
|
208
|
+
def show_integration_secret(integration)
|
209
|
+
return '(none required)' unless integration.verify_payloads?
|
210
|
+
|
211
|
+
integration.secret.to_s
|
212
|
+
end
|
213
|
+
|
214
|
+
# Prints information about the channels associated with an integration.
|
215
|
+
#
|
216
|
+
# @param integration [Integration] the integration
|
217
|
+
def show_integration_channels(integration)
|
218
|
+
integration.channels.each do |channel|
|
219
|
+
puts "\t\t- #{channel.name} on #{channel.network.name}"
|
220
|
+
puts "\t\t\tKey: #{channel.key}" if channel.key?
|
221
|
+
puts "\t\t\tMessages are sent without joining" if channel.send_external
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
module Codebot
|
6
|
+
# A pipe-based IPC client used for communicating with a running Codebot
|
7
|
+
# instance.
|
8
|
+
class IPCClient
|
9
|
+
# @return [String] the path to the named pipe
|
10
|
+
attr_reader :pipe
|
11
|
+
|
12
|
+
# Creates a new IPC client.
|
13
|
+
#
|
14
|
+
# @param pipe [String] the path to the named pipe, or +nil+ to use the
|
15
|
+
# default pipe for this user
|
16
|
+
def initialize(pipe = nil)
|
17
|
+
@pipe = pipe || IPCServer.default_pipe
|
18
|
+
end
|
19
|
+
|
20
|
+
# Sends a REHASH command to the named pipe.
|
21
|
+
#
|
22
|
+
# @param explicit [Boolean] whether this command was invoked explicitly
|
23
|
+
def send_rehash(explicit = true)
|
24
|
+
command('REHASH', explicit)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Sends a STOP command to the named pipe.
|
28
|
+
#
|
29
|
+
# @param explicit [Boolean] whether this command was invoked explicitly
|
30
|
+
def send_stop(explicit = true)
|
31
|
+
command('STOP', explicit)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Checks whether the named pipe exists.
|
35
|
+
#
|
36
|
+
# @return [Boolean] +true+ if the pipe exists, +false+ otherwise
|
37
|
+
def pipe_exist?
|
38
|
+
File.pipe? @pipe
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Sends a command to the named pipe.
|
44
|
+
#
|
45
|
+
# @param cmd [String] the command
|
46
|
+
# @param explicit [Boolean] whether this command was invoked explicitly
|
47
|
+
# @return [Boolean] whether the command was sent successfully
|
48
|
+
def command(cmd, explicit)
|
49
|
+
return false unless check_pipe_exist(explicit)
|
50
|
+
|
51
|
+
Timeout.timeout 5 do
|
52
|
+
File.open @pipe, 'w' do |p|
|
53
|
+
p.puts cmd
|
54
|
+
end
|
55
|
+
end
|
56
|
+
true
|
57
|
+
rescue Timeout::Error
|
58
|
+
communication_error! 'no response'
|
59
|
+
end
|
60
|
+
|
61
|
+
# Checks whether the named pipe exists.
|
62
|
+
#
|
63
|
+
# @param should_raise [Boolean] whether to raise an exception if the pipe
|
64
|
+
# does not exist
|
65
|
+
# @return [Boolean] +true+ if the pipe exists, +false+ otherwise
|
66
|
+
def check_pipe_exist(should_raise)
|
67
|
+
return true if pipe_exist?
|
68
|
+
|
69
|
+
communication_error! "missing pipe #{@pipe.inspect}" if should_raise
|
70
|
+
false
|
71
|
+
end
|
72
|
+
|
73
|
+
# Raise a {CommandError} with a message stating that communication with
|
74
|
+
# an active instance failed.
|
75
|
+
#
|
76
|
+
# @param msg [String] the error message
|
77
|
+
# @raise [CommandError] the requested error
|
78
|
+
def communication_error!(msg)
|
79
|
+
raise CommandError, "unable to communicate with the bot: #{msg} " \
|
80
|
+
'(is the bot running?)'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Codebot
|
4
|
+
# A pipe-based IPC server used for communicating with a running Codebot
|
5
|
+
# instance.
|
6
|
+
class IPCServer < ThreadController
|
7
|
+
# @return [Core] the bot this server belongs to
|
8
|
+
attr_reader :core
|
9
|
+
|
10
|
+
# @return [String] the path to the named pipe
|
11
|
+
attr_reader :pipe
|
12
|
+
|
13
|
+
# Creates a new IPC server.
|
14
|
+
#
|
15
|
+
# @param core [Core] the bot this server belongs to
|
16
|
+
# @param pipe [String] the path to the named pipe, or +nil+ to use the
|
17
|
+
# default pipe for this user
|
18
|
+
def initialize(core, pipe = nil)
|
19
|
+
super()
|
20
|
+
@core = core
|
21
|
+
@pipe = pipe || self.class.default_pipe
|
22
|
+
end
|
23
|
+
|
24
|
+
# Stops the managed thread if a thread is currently running, then deletes
|
25
|
+
# the named pipe.
|
26
|
+
#
|
27
|
+
# @return [Thread, nil] the stopped thread, or +nil+ if
|
28
|
+
# no thread was running
|
29
|
+
def stop
|
30
|
+
thr = super
|
31
|
+
delete_pipe
|
32
|
+
thr
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the path to the default pipe for the current user.
|
36
|
+
#
|
37
|
+
# @return [String] the path to the named pipe
|
38
|
+
def self.default_pipe
|
39
|
+
File.join Dir.home, '.codebot.ipc'
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# Creates the named pipe.
|
45
|
+
def create_pipe
|
46
|
+
return if File.pipe? @pipe
|
47
|
+
|
48
|
+
delete_pipe
|
49
|
+
File.mkfifo @pipe
|
50
|
+
end
|
51
|
+
|
52
|
+
# Deletes the named pipe.
|
53
|
+
def delete_pipe
|
54
|
+
File.delete @pipe if File.exist? @pipe
|
55
|
+
end
|
56
|
+
|
57
|
+
# Starts this IPC server.
|
58
|
+
def run(*)
|
59
|
+
create_pipe
|
60
|
+
file = File.open @pipe, 'r+'
|
61
|
+
while (line = file.gets.strip)
|
62
|
+
handle_command line
|
63
|
+
end
|
64
|
+
ensure
|
65
|
+
delete_pipe
|
66
|
+
end
|
67
|
+
|
68
|
+
# Handles an incoming IPC command.
|
69
|
+
#
|
70
|
+
# @param command [String] the command
|
71
|
+
def handle_command(command)
|
72
|
+
case command
|
73
|
+
when 'REHASH' then @core.config.load!
|
74
|
+
when 'STOP' then Thread.new { @core.stop }
|
75
|
+
else STDERR.puts "Unknown IPC command: #{command.inspect}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codebot/irc_connection'
|
4
|
+
|
5
|
+
module Codebot
|
6
|
+
# This class manages an IRC client.
|
7
|
+
class IRCClient
|
8
|
+
# Creates a new IRC client.
|
9
|
+
#
|
10
|
+
# @param core [Core] the bot this client belongs to
|
11
|
+
def initialize(core)
|
12
|
+
@active = false
|
13
|
+
@checkpoint = []
|
14
|
+
@connections = []
|
15
|
+
@core = core
|
16
|
+
@semaphore = Mutex.new
|
17
|
+
end
|
18
|
+
|
19
|
+
# Dispatches a new request.
|
20
|
+
#
|
21
|
+
# @param request [Request] the request to dispatch
|
22
|
+
def dispatch(request)
|
23
|
+
request.each_network do |network, channels|
|
24
|
+
connection = connection_to(network)
|
25
|
+
next if connection.nil?
|
26
|
+
|
27
|
+
channels.each do |channel|
|
28
|
+
message = request.to_message_for channel
|
29
|
+
connection.enqueue message
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Starts the IRC client.
|
35
|
+
def start
|
36
|
+
@active = true
|
37
|
+
migrate!
|
38
|
+
end
|
39
|
+
|
40
|
+
# Stops the IRC client.
|
41
|
+
def stop
|
42
|
+
@active = false
|
43
|
+
@checkpoint.clear
|
44
|
+
@connections.each(&:stop)
|
45
|
+
join
|
46
|
+
@connections.clear
|
47
|
+
end
|
48
|
+
|
49
|
+
# Waits for active connections to finish.
|
50
|
+
def join
|
51
|
+
@connections.each(&:join)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Connects to and disconnects from networks as necessary in order for the
|
55
|
+
# list of connections to reflect changes to the configuration.
|
56
|
+
def migrate!
|
57
|
+
@semaphore.synchronize do
|
58
|
+
return unless @active
|
59
|
+
|
60
|
+
networks = @core.config.networks
|
61
|
+
(@checkpoint - networks).each { |network| disconnect_from network }
|
62
|
+
(networks - @checkpoint).each { |network| connect_to network }
|
63
|
+
@checkpoint = networks
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# Finds the connection to a given network.
|
70
|
+
#
|
71
|
+
# @param network [Network] the network
|
72
|
+
# @return [IRCConnection, nil] the connection, or +nil+ if none was found
|
73
|
+
def connection_to(network)
|
74
|
+
@connections.find { |con| con.network.eql? network }
|
75
|
+
end
|
76
|
+
|
77
|
+
# Checks whether the client is connected to a given network.
|
78
|
+
#
|
79
|
+
# @param network [Network] the network
|
80
|
+
# @return [Boolean] +true+ if the client is connected, +false+ otherwise
|
81
|
+
def connected_to?(network)
|
82
|
+
!connection_to(network).nil?
|
83
|
+
end
|
84
|
+
|
85
|
+
# Connects to a given network if the same network is not already connected.
|
86
|
+
#
|
87
|
+
# @param network [Network] the network to connect to
|
88
|
+
def connect_to(network)
|
89
|
+
return if connected_to? network
|
90
|
+
|
91
|
+
@connections << IRCConnection.new(@core, network).tap(&:start)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Disconnects from a given network if the network is currently connected.
|
95
|
+
#
|
96
|
+
# @param network [Network] the network to disconnected from
|
97
|
+
def disconnect_from(network)
|
98
|
+
connection = @connections.delete connection_to(network)
|
99
|
+
connection.tap(&:stop).tap(&:join) unless connection.nil?
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|