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,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codebot/network_manager'
|
4
|
+
|
5
|
+
module Codebot
|
6
|
+
module Options
|
7
|
+
# A class that handles the +codebot network+ command.
|
8
|
+
class Network < Thor
|
9
|
+
check_unknown_options!
|
10
|
+
|
11
|
+
# Sets shared options for connecting to the IRC network.
|
12
|
+
def self.shared_connection_options
|
13
|
+
option :host, aliases: '-H',
|
14
|
+
desc: 'Set the server hostname or address'
|
15
|
+
option :port, type: :numeric, aliases: '-p',
|
16
|
+
desc: 'Set the port to connect to'
|
17
|
+
option :secure, type: :boolean, aliases: '-s',
|
18
|
+
desc: 'Connect securely using TLS'
|
19
|
+
option :server_password, desc: 'Set the server password'
|
20
|
+
option :nick, aliases: '-N',
|
21
|
+
desc: 'Set the nickname'
|
22
|
+
end
|
23
|
+
|
24
|
+
# Sets shared options for authenticating to the IRC network.
|
25
|
+
def self.shared_authentication_options
|
26
|
+
# Not a boolean to prevent thor from generating --no-disable-sasl flag
|
27
|
+
option :disable_sasl, type: :string, banner: '',
|
28
|
+
desc: 'Disable SASL authentication'
|
29
|
+
option :sasl_username, desc: 'Set the username for SASL authentication'
|
30
|
+
option :sasl_password, desc: 'Set the password for SASL authentication'
|
31
|
+
|
32
|
+
option :disable_nickserv, type: :string, banner: '',
|
33
|
+
desc: 'Disable NickServ authentication'
|
34
|
+
option :nickserv_username,
|
35
|
+
desc: 'Set the username for NickServ authentication'
|
36
|
+
option :nickserv_password,
|
37
|
+
desc: 'Set the password for NickServ authentication'
|
38
|
+
end
|
39
|
+
|
40
|
+
# Sets shared options for specifying properties belonging to the
|
41
|
+
# {::Codebot::Network} class.
|
42
|
+
def self.shared_propery_options
|
43
|
+
shared_connection_options
|
44
|
+
shared_authentication_options
|
45
|
+
option :bind, aliases: '-b',
|
46
|
+
desc: 'Bind to the specified IP address or host'
|
47
|
+
option :modes, aliases: '-m',
|
48
|
+
desc: 'Set user modes'
|
49
|
+
end
|
50
|
+
|
51
|
+
desc 'create NAME', 'Add a new IRC network'
|
52
|
+
shared_propery_options
|
53
|
+
|
54
|
+
# Creates a new network with the specified name.
|
55
|
+
#
|
56
|
+
# @param name [String] the name of the new network
|
57
|
+
def create(name)
|
58
|
+
Options.with_core(parent_options, true) do |core|
|
59
|
+
NetworkManager.new(core.config).create(options.merge(name: name))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
desc 'update NAME', 'Edit an IRC network'
|
64
|
+
option :name, aliases: '-n',
|
65
|
+
banner: 'NEW-NAME',
|
66
|
+
desc: 'Rename this network'
|
67
|
+
shared_propery_options
|
68
|
+
|
69
|
+
# Updates the network with the specified name.
|
70
|
+
#
|
71
|
+
# @param name [String] the name of the network
|
72
|
+
def update(name)
|
73
|
+
Options.with_core(parent_options, true) do |core|
|
74
|
+
NetworkManager.new(core.config).update(name, options)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
desc 'list [SEARCH]', 'List networks'
|
79
|
+
|
80
|
+
# Lists all networks, or networks with names containing the given search
|
81
|
+
# term.
|
82
|
+
#
|
83
|
+
# @param search [String, nil] an optional search term
|
84
|
+
def list(search = nil)
|
85
|
+
Options.with_core(parent_options, true) do |core|
|
86
|
+
NetworkManager.new(core.config).list(search)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
desc 'destroy NAME', 'Delete an IRC network'
|
91
|
+
|
92
|
+
# Destroys the network with the specified name.
|
93
|
+
#
|
94
|
+
# @param name [String] the name of the network
|
95
|
+
def destroy(name)
|
96
|
+
Options.with_core(parent_options, true) do |core|
|
97
|
+
NetworkManager.new(core.config).destroy(name, options)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Ensures that thor uses the correct exit code.
|
102
|
+
#
|
103
|
+
# @return true
|
104
|
+
def self.exit_on_failure?
|
105
|
+
true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Codebot
|
6
|
+
# A JSON request payload.
|
7
|
+
class Payload
|
8
|
+
# @return [Object] the JSON object parsed from the request payload
|
9
|
+
attr_reader :json
|
10
|
+
|
11
|
+
# Constructs a new payload.
|
12
|
+
#
|
13
|
+
# @param payload [String] the request payload
|
14
|
+
def initialize(payload)
|
15
|
+
@json = JSON.parse payload
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the JSON payload.
|
19
|
+
#
|
20
|
+
# @return [Object] the JSON object
|
21
|
+
def to_json
|
22
|
+
@json
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the JSON string corresponding to the payload.
|
26
|
+
#
|
27
|
+
# @return [String] the JSON string
|
28
|
+
def to_s
|
29
|
+
@json.to_s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codebot/message'
|
4
|
+
require 'codebot/payload'
|
5
|
+
|
6
|
+
module Codebot
|
7
|
+
# A request which was received by the web server and can be delivered to the
|
8
|
+
# IRC client.
|
9
|
+
class Request
|
10
|
+
# @return [Integration] the integration to deliver this request to
|
11
|
+
attr_reader :integration
|
12
|
+
|
13
|
+
# @return [Symbol] the event that triggered the webhook delivery
|
14
|
+
attr_reader :event
|
15
|
+
|
16
|
+
# @return [Payload] the parsed request payload
|
17
|
+
attr_reader :payload
|
18
|
+
|
19
|
+
# Constructs a new request for delivery to the IRC client.
|
20
|
+
#
|
21
|
+
# @param integration [Integration] the integration for which the request
|
22
|
+
# was made
|
23
|
+
# @param event [Symbol] the event that triggered the webhook delivery
|
24
|
+
# @param payload [String] a JSON string containing the request payload
|
25
|
+
def initialize(integration, event, payload)
|
26
|
+
@integration = integration
|
27
|
+
@event = event
|
28
|
+
@payload = Payload.new payload
|
29
|
+
end
|
30
|
+
|
31
|
+
# Invokes the given block for each network this request needs to be
|
32
|
+
# delivered to.
|
33
|
+
#
|
34
|
+
# @yieldparam [Network] the network
|
35
|
+
# @yieldparam [Array<Channels>] channels that belong to the network and
|
36
|
+
# that a notification should be delivered to
|
37
|
+
def each_network
|
38
|
+
integration.channels.group_by(&:network).each do |network, channels|
|
39
|
+
yield network, channels
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Creates a message for a given channel from this request.
|
44
|
+
#
|
45
|
+
# @param channel [Channel] the channel
|
46
|
+
# @return [Message] the created message
|
47
|
+
def to_message_for(channel)
|
48
|
+
Message.new(channel, @event, @payload, @integration)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codebot/cryptography'
|
4
|
+
require 'codebot/validation_error'
|
5
|
+
|
6
|
+
module Codebot
|
7
|
+
# This module provides data sanitization methods shared among multiple
|
8
|
+
# classes.
|
9
|
+
module Sanitizers
|
10
|
+
# Sanitizes an identifier.
|
11
|
+
#
|
12
|
+
# @param identifier [String, nil] the identifier to sanitize
|
13
|
+
# @return [String, nil] the sanitized value or +nil+ on error
|
14
|
+
def valid_identifier(identifier)
|
15
|
+
identifier.downcase if /\A[[:alnum:]_-]+\z/ =~ identifier
|
16
|
+
end
|
17
|
+
|
18
|
+
# Sanitizes an endpoint name.
|
19
|
+
#
|
20
|
+
# @param endpoint [String, nil] the endpoint name to sanitize
|
21
|
+
# @return [String, nil] the sanitized value or +nil+ on error
|
22
|
+
def valid_endpoint(endpoint)
|
23
|
+
endpoint if /\A[[:alnum:]_-]*\z/ =~ endpoint
|
24
|
+
end
|
25
|
+
|
26
|
+
# Sanitizes a webhook secret.
|
27
|
+
#
|
28
|
+
# @param secret [String, nil] the webhook secret to sanitize
|
29
|
+
# @return [String, nil] the sanitized value or +nil+ on error
|
30
|
+
def valid_secret(secret)
|
31
|
+
secret if /\A[[:print:]]*\z/ =~ secret
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sanitizes a hostname.
|
35
|
+
#
|
36
|
+
# @param host [String, nil] the hostname to sanitize
|
37
|
+
# @return [String, nil] the sanitized value or +nil+ on error
|
38
|
+
def valid_host(host)
|
39
|
+
host if /\A[[:graph:]]+\z/ =~ host
|
40
|
+
end
|
41
|
+
|
42
|
+
# Sanitizes a TCP/IP port number.
|
43
|
+
#
|
44
|
+
# @param port [#to_i, #to_s] the port number to sanitize
|
45
|
+
# @return [Integer, nil] the sanitized value or +nil+ on error
|
46
|
+
def valid_port(port)
|
47
|
+
port_number = port.to_s.to_i(10) if /\A[0-9]+\z/ =~ port.to_s
|
48
|
+
port_number if (1...2**16).cover? port_number
|
49
|
+
end
|
50
|
+
|
51
|
+
# Sanitizes a boolean value.
|
52
|
+
#
|
53
|
+
# @param bool [Boolean, nil] the boolean to sanitize
|
54
|
+
# @return [Boolean, nil] the sanitized value or +nil+ on error
|
55
|
+
def valid_boolean(bool)
|
56
|
+
bool if [true, false].include? bool
|
57
|
+
end
|
58
|
+
|
59
|
+
# Sanitizes a string.
|
60
|
+
#
|
61
|
+
# @param str [String, nil] the string to sanitize
|
62
|
+
# @return [String, nil] the sanitized value or +nil+ on error
|
63
|
+
def valid_string(str)
|
64
|
+
str if str.is_a? String
|
65
|
+
end
|
66
|
+
|
67
|
+
# Sanitizes a channel name.
|
68
|
+
#
|
69
|
+
# @param channel [String, nil] the channel name to sanitize
|
70
|
+
# @return [String, nil] the sanitized value or +nil+ on error
|
71
|
+
def valid_channel_name(channel)
|
72
|
+
# Colons are currently not considered valid characters because some IRCds
|
73
|
+
# use them to delimit channel masks. This might change in the future.
|
74
|
+
channel if /\A[&#\+!][[:graph:]&&[^:,]]{,49}\z/ =~ channel
|
75
|
+
end
|
76
|
+
|
77
|
+
# Sanitizes a channel key.
|
78
|
+
#
|
79
|
+
# @param key [String, nil] the channel key to sanitize
|
80
|
+
# @return [String, nil] the sanitized value or +nil+ on error
|
81
|
+
def valid_channel_key(key)
|
82
|
+
key if /\A[[:graph:]&&[^,]]*\z/ =~ key
|
83
|
+
end
|
84
|
+
|
85
|
+
# Sanitizes a network name.
|
86
|
+
#
|
87
|
+
# @param name [String] the name of the network
|
88
|
+
# @param conf [Hash] the configuration containing all networks
|
89
|
+
# @return [Network, nil] the corresponding network or +nil+ on error
|
90
|
+
def valid_network(name, conf)
|
91
|
+
return if name.nil?
|
92
|
+
|
93
|
+
conf[:networks].find { |net| net.name_eql? name }
|
94
|
+
end
|
95
|
+
|
96
|
+
# This method requires a validation to succeed, raising an exception if it
|
97
|
+
# does not. If no original value was provided, it returns, in this order,
|
98
|
+
# the given fallback, the return value of any block passed to this method,
|
99
|
+
# or, finally, +nil+, unless the +:required+ option is set, in which case
|
100
|
+
# a +ValidationError+ is raised.
|
101
|
+
#
|
102
|
+
# @param original [Object] the original value
|
103
|
+
# @param sanitized [Object] the sanitized value
|
104
|
+
# @param fallback [Object] an optional symbol representing an instance
|
105
|
+
# variable to be returned when the original value
|
106
|
+
# is +nil+
|
107
|
+
# @raise [ValidationError] if the sanitization failed. The error message may
|
108
|
+
# be set using the +:invalid_error+ option.
|
109
|
+
# @raise [ValidationError] if the +:required+ option is set, but neither an
|
110
|
+
# original value nor a fallback value was specified
|
111
|
+
# and no block was given. The error message may be
|
112
|
+
# set using the +:required_error+ option.
|
113
|
+
# @param options [Hash] a hash optionally containing additional settings.
|
114
|
+
def valid!(original, sanitized, fallback = nil, options = {})
|
115
|
+
return sanitized unless sanitized.nil?
|
116
|
+
unless original.nil?
|
117
|
+
raise ValidationError, options[:invalid_error] % original.inspect
|
118
|
+
end
|
119
|
+
return instance_variable_get(fallback) if fallback_exist?(fallback)
|
120
|
+
return yield if block_given?
|
121
|
+
raise ValidationError, options[:required_error] if options[:required]
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def fallback_exist?(fallback)
|
127
|
+
!fallback.nil? && instance_variable_defined?(fallback)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codebot/configuration_error'
|
4
|
+
|
5
|
+
module Codebot
|
6
|
+
# A class that can be serialized. Child classes should override the
|
7
|
+
# {#serialize} and {::deserialize} methods, and change {::serialize_as_hash?}
|
8
|
+
# to return +true+ if the {#serialize} method returns an +Array+ containing
|
9
|
+
# two elements representing key and value of a +Hash+.
|
10
|
+
class Serializable
|
11
|
+
# Serializes an array into an array or a hash.
|
12
|
+
#
|
13
|
+
# @param ary [Array] the data to serialize
|
14
|
+
# @param conf [Hash] the deserialized configuration
|
15
|
+
# @return [Array, Hash] the serialized data
|
16
|
+
def self.serialize_all(ary, conf)
|
17
|
+
data = ary.map { |entry| entry.serialize(conf) }
|
18
|
+
return data.to_h if serialize_as_hash?
|
19
|
+
|
20
|
+
data
|
21
|
+
end
|
22
|
+
|
23
|
+
# Deserializes an array or a hash into an array.
|
24
|
+
#
|
25
|
+
# @param data [Array, Hash] the data to deserialize
|
26
|
+
# @param conf [Hash] the previously deserialized configuration
|
27
|
+
# @return [Array] the deserialized data
|
28
|
+
def self.deserialize_all(data, conf)
|
29
|
+
return [] if data.nil?
|
30
|
+
|
31
|
+
if serialize_as_hash?
|
32
|
+
deserialize_all_from_hash(data, conf)
|
33
|
+
else
|
34
|
+
deserialize_all_from_array(data, conf)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Serializes this object.
|
39
|
+
#
|
40
|
+
# @note Child classes should override this method.
|
41
|
+
# @param _conf [Hash] the deserialized configuration
|
42
|
+
# @return [Array, Hash] the serialized object
|
43
|
+
def serialize(_conf)
|
44
|
+
[]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Deserializes an object.
|
48
|
+
#
|
49
|
+
# @note Child classes should override this method.
|
50
|
+
# @param _key [Object] the hash key if the value was serialized into a hash
|
51
|
+
# @param _val [Hash] the serialized data
|
52
|
+
# @return [Hash] the parameters to pass to the initializer
|
53
|
+
def self.deserialize(_key = nil, _val)
|
54
|
+
{}
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns whether data is serialized into a hash rather than an array.
|
58
|
+
#
|
59
|
+
# @note Child classes might want to override this method.
|
60
|
+
# @return [Boolean] whether data is serialized into an array containing two
|
61
|
+
# elements representing key and value of a +Hash+.
|
62
|
+
def self.serialize_as_hash?
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
# Deserializes an array into an array.
|
67
|
+
#
|
68
|
+
# @param data [Array] the data to deserialize
|
69
|
+
# @param conf [Hash] the previously deserialized configuration
|
70
|
+
# @return [Array] the deserialized data
|
71
|
+
def self.deserialize_all_from_array(data, conf)
|
72
|
+
unless data.is_a? Array
|
73
|
+
raise ConfigurationError, "#{name}: invalid array #{data.inspect}"
|
74
|
+
end
|
75
|
+
|
76
|
+
data.map { |item| new(deserialize(item).merge(config: conf)) }
|
77
|
+
end
|
78
|
+
|
79
|
+
# Deserializes a hash into an array.
|
80
|
+
#
|
81
|
+
# @param data [Hash] the data to deserialize
|
82
|
+
# @param conf [Hash] the previously deserialized configuration
|
83
|
+
# @return [Array] the deserialized data
|
84
|
+
def self.deserialize_all_from_hash(data, conf)
|
85
|
+
unless data.is_a? Hash
|
86
|
+
raise ConfigurationError, "#{name}: invalid hash #{data.inspect}"
|
87
|
+
end
|
88
|
+
|
89
|
+
data.map do |item|
|
90
|
+
unless item.length == 2
|
91
|
+
raise ConfigurationError, "#{name}: invalid member #{item.inspect}"
|
92
|
+
end
|
93
|
+
|
94
|
+
new(deserialize(*item).merge(config: conf))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private_class_method :deserialize_all_from_array
|
99
|
+
private_class_method :deserialize_all_from_hash
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Codebot
|
4
|
+
module Shortener
|
5
|
+
# Shortens URLs using Github shortener
|
6
|
+
class Github
|
7
|
+
# Shortens a URL with GitHub's git.io URL shortener. The domain
|
8
|
+
# must belong to GitHub.
|
9
|
+
#
|
10
|
+
# @param url [String] the long URL
|
11
|
+
# @return [String] the shortened URL, or the original URL if an error
|
12
|
+
# occurred.
|
13
|
+
def shorten_url(url)
|
14
|
+
return url if url.to_s.empty?
|
15
|
+
|
16
|
+
uri = URI('https://git.io')
|
17
|
+
res = Net::HTTP.post_form uri, 'url' => url.to_s
|
18
|
+
res['location'] || url.to_s
|
19
|
+
rescue StandardError
|
20
|
+
url.to_s
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Shortens URLs using a custom shortener
|
25
|
+
class Custom
|
26
|
+
def initialize(shortener_url, shortener_secret)
|
27
|
+
@shortener_url = URI(shortener_url)
|
28
|
+
@shortener_secret = shortener_secret
|
29
|
+
end
|
30
|
+
|
31
|
+
def shorten_url(url)
|
32
|
+
return url if url.to_s.empty?
|
33
|
+
|
34
|
+
res = Net::HTTP.post_form @shortener_url,
|
35
|
+
'url' => url.to_s,
|
36
|
+
'secret' => @shortener_secret
|
37
|
+
res.body.strip || url.to_s
|
38
|
+
rescue StandardError
|
39
|
+
url.to_s
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|