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