sms_traffic_sdk 0.0.8 → 0.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 +4 -4
- data/CHANGELOG.md +87 -0
- data/README.md +104 -39
- data/lib/sms_traffic/client/account_reply.rb +14 -0
- data/lib/sms_traffic/client/deliver_reply.rb +19 -38
- data/lib/sms_traffic/client/reply.rb +28 -7
- data/lib/sms_traffic/client/status_reply.rb +77 -24
- data/lib/sms_traffic/client.rb +110 -40
- data/lib/sms_traffic/configuration.rb +15 -56
- data/lib/sms_traffic/errors.rb +45 -0
- data/lib/sms_traffic/sms.rb +50 -25
- data/lib/sms_traffic/status.rb +26 -0
- data/lib/sms_traffic/version.rb +1 -1
- data/lib/sms_traffic.rb +7 -9
- data/lib/sms_traffic_sdk.rb +1 -0
- metadata +28 -33
- data/.env.template +0 -4
- data/.fasterer.yml +0 -2
- data/.github/workflows/ci.yml +0 -72
- data/.gitignore +0 -12
- data/.rspec +0 -2
- data/.rubocop.yml +0 -20
- data/.tool-versions +0 -1
- data/Gemfile +0 -17
- data/Rakefile +0 -10
- data/bin/console +0 -18
- data/bin/setup +0 -7
- data/exe/konsierge-sms.rb +0 -2
- data/lib/sms_traffic/client/response.rb +0 -39
- data/sms_traffic_sdk.gemspec +0 -31
data/lib/sms_traffic/client.rb
CHANGED
|
@@ -1,78 +1,148 @@
|
|
|
1
1
|
require 'net/http'
|
|
2
|
+
require 'openssl'
|
|
3
|
+
require 'uri'
|
|
4
|
+
require_relative 'client/reply'
|
|
5
|
+
require_relative 'client/deliver_reply'
|
|
6
|
+
require_relative 'client/status_reply'
|
|
7
|
+
require_relative 'client/account_reply'
|
|
2
8
|
|
|
3
9
|
module SmsTraffic
|
|
4
10
|
class Client
|
|
5
|
-
|
|
6
|
-
|
|
11
|
+
ENDPOINT = 'multi.php'.freeze
|
|
12
|
+
FILTERED = '[FILTERED]'.freeze
|
|
13
|
+
FILTERED_PARAMS = %i[login password message].freeze
|
|
14
|
+
CONNECTION_ERRORS = [
|
|
15
|
+
SocketError, SystemCallError, IOError, Net::OpenTimeout, Net::ReadTimeout, OpenSSL::SSL::SSLError
|
|
16
|
+
].freeze
|
|
17
|
+
# Failures that prove the request never reached the server.
|
|
18
|
+
UNREACHABLE_ERRORS = [
|
|
19
|
+
SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, Net::OpenTimeout
|
|
20
|
+
].freeze
|
|
7
21
|
|
|
8
|
-
|
|
22
|
+
class << self
|
|
23
|
+
# @param phone [String] phone number or phone numbers separated by comma
|
|
9
24
|
# @param message [String] message
|
|
10
|
-
# @param originator [String] originator (subject)
|
|
25
|
+
# @param originator [String] originator (subject), defaults to the configured one
|
|
11
26
|
# @param options [Hash] additional options
|
|
27
|
+
# @return [DeliverReply]
|
|
28
|
+
# @raise [ConnectionError, HttpError, ApiError]
|
|
12
29
|
def deliver(phone, message, originator = nil, **options)
|
|
13
30
|
originator ||= SmsTraffic.configuration.originator
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
31
|
+
request(DeliverReply, deliver_params(phone, message, originator, **options), read_only: false)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @param sms_ids [Array<String>] sms ids
|
|
35
|
+
# @return [StatusReply]
|
|
36
|
+
# @raise [ConnectionError, HttpError, ApiError]
|
|
37
|
+
def status(*sms_ids)
|
|
38
|
+
request(StatusReply, status_params(sms_ids), read_only: true)
|
|
17
39
|
end
|
|
18
40
|
|
|
19
|
-
# @
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Response.new(response, type: :status)
|
|
41
|
+
# @return [AccountReply]
|
|
42
|
+
# @raise [ConnectionError, HttpError, ApiError]
|
|
43
|
+
def balance
|
|
44
|
+
request(AccountReply, account_params, read_only: true)
|
|
24
45
|
end
|
|
25
46
|
|
|
26
47
|
private
|
|
27
48
|
|
|
28
|
-
#
|
|
29
|
-
|
|
30
|
-
|
|
49
|
+
# Sends the request to the configured host and, when a fallback host is set, repeats it there once if the
|
|
50
|
+
# failure allows it.
|
|
51
|
+
def request(reply_class, form_data, read_only:)
|
|
52
|
+
config = SmsTraffic.configuration
|
|
53
|
+
attempt(reply_class, form_data, config.host)
|
|
54
|
+
rescue Error => e
|
|
55
|
+
raise unless config.fallback_host && retry_on_fallback?(e, read_only:)
|
|
56
|
+
|
|
57
|
+
log("retry on #{config.fallback_host} after #{retry_reason(e)}")
|
|
58
|
+
attempt(reply_class, form_data, config.fallback_host)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Names the root cause and, for an API error, its code; never the message text.
|
|
62
|
+
def retry_reason(error)
|
|
63
|
+
reason = (error.cause&.class || error.class).to_s
|
|
64
|
+
error.is_a?(ApiError) ? "#{reason} #{error.code}" : reason
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def attempt(reply_class, form_data, host)
|
|
68
|
+
reply = reply_class.new(post(form_data, host))
|
|
69
|
+
raise ApiError.new(reply.code, reply.description) if reply.api_error?
|
|
70
|
+
|
|
71
|
+
reply
|
|
72
|
+
end
|
|
31
73
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
74
|
+
# A read-only request is safe to repeat after any transport failure. A deliver is repeated only when the
|
|
75
|
+
# request never reached the server, as the provider may already have queued the SMS otherwise.
|
|
76
|
+
def retry_on_fallback?(error, read_only:)
|
|
77
|
+
case error
|
|
78
|
+
when ApiError then error.retryable?
|
|
79
|
+
when ConnectionError then read_only || UNREACHABLE_ERRORS.any? { |klass| error.cause.is_a?(klass) }
|
|
80
|
+
when HttpError then read_only && error.status >= 500
|
|
81
|
+
else false
|
|
82
|
+
end
|
|
38
83
|
end
|
|
39
84
|
|
|
40
85
|
# @param form_data [Hash<Symbol, String|Integer>]
|
|
41
|
-
# @return [
|
|
42
|
-
def
|
|
43
|
-
|
|
86
|
+
# @return [String] response body
|
|
87
|
+
def post(form_data, host)
|
|
88
|
+
config = SmsTraffic.configuration
|
|
89
|
+
uri = URI.join(host, ENDPOINT)
|
|
90
|
+
log("request POST #{uri} #{filter(form_data)}")
|
|
91
|
+
|
|
92
|
+
response = perform(uri, form_data, config)
|
|
93
|
+
log("response #{response.code} (#{response.body.to_s.bytesize} bytes)")
|
|
94
|
+
successful_body(response)
|
|
95
|
+
rescue *CONNECTION_ERRORS => e
|
|
96
|
+
raise ConnectionError, "#{e.class}: #{e.message}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def successful_body(response)
|
|
100
|
+
raise HttpError.new(response.code.to_i, response.body) unless response.is_a?(Net::HTTPSuccess)
|
|
101
|
+
|
|
102
|
+
response.body
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def perform(uri, form_data, config)
|
|
106
|
+
options = { use_ssl: uri.scheme == 'https', open_timeout: config.open_timeout,
|
|
107
|
+
read_timeout: config.read_timeout }
|
|
108
|
+
Net::HTTP.start(uri.host, uri.port, **options) do |http|
|
|
44
109
|
request = Net::HTTP::Post.new(uri.request_uri)
|
|
45
110
|
request.set_form_data(form_data)
|
|
46
111
|
http.request(request)
|
|
47
112
|
end
|
|
48
113
|
end
|
|
49
114
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
115
|
+
def log(line)
|
|
116
|
+
config = SmsTraffic.configuration
|
|
117
|
+
config.logger.debug("[SmsTraffic] #{line}") if config.debug
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def filter(form_data)
|
|
121
|
+
form_data.to_h { |key, value| [key, FILTERED_PARAMS.include?(key) ? FILTERED : value] }
|
|
122
|
+
end
|
|
123
|
+
|
|
55
124
|
def deliver_params(phone, message, originator, **options)
|
|
56
125
|
{
|
|
57
126
|
login: SmsTraffic.configuration.login,
|
|
58
127
|
password: SmsTraffic.configuration.password,
|
|
59
128
|
phones: phone,
|
|
60
|
-
message
|
|
129
|
+
message:,
|
|
61
130
|
want_sms_ids: 1,
|
|
62
131
|
rus: 5,
|
|
63
|
-
originator:
|
|
132
|
+
originator:
|
|
64
133
|
}.merge(options)
|
|
65
134
|
end
|
|
66
135
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
136
|
+
def status_params(sms_ids)
|
|
137
|
+
operation_params('status').merge(sms_id: sms_ids.join(','))
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def account_params
|
|
141
|
+
operation_params('account')
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def operation_params(operation)
|
|
145
|
+
{ login: SmsTraffic.configuration.login, password: SmsTraffic.configuration.password, operation: }
|
|
76
146
|
end
|
|
77
147
|
end
|
|
78
148
|
end
|
|
@@ -1,75 +1,34 @@
|
|
|
1
|
-
require 'singleton'
|
|
2
1
|
require 'nori'
|
|
3
2
|
|
|
4
3
|
module SmsTraffic
|
|
5
4
|
class Configuration
|
|
6
|
-
|
|
5
|
+
ATTRIBUTES = %i[
|
|
6
|
+
login password host fallback_host originator debug logger open_timeout read_timeout validate_phone xml_parser
|
|
7
|
+
].freeze
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
# Attributes not listed here default to nil.
|
|
10
|
+
DEFAULTS = { debug: false, open_timeout: 10, read_timeout: 30, validate_phone: true }.freeze
|
|
11
|
+
|
|
12
|
+
attr_accessor(*ATTRIBUTES)
|
|
9
13
|
|
|
10
14
|
def initialize
|
|
11
|
-
|
|
12
|
-
self.logger = default_configuration[:logger]
|
|
13
|
-
self.xml_parser = default_configuration[:xml_parser]
|
|
14
|
-
self.validate_phone = default_configuration[:validate_phone]
|
|
15
|
-
super
|
|
15
|
+
reset!
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def reset!
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
end
|
|
19
|
+
ATTRIBUTES.each { |attribute| public_send("#{attribute}=", DEFAULTS[attribute]) }
|
|
20
|
+
self.xml_parser = Nori.new(advanced_typecasting: false)
|
|
22
21
|
end
|
|
23
22
|
|
|
24
23
|
def validate!
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
raise ArgumentError, 'login should be defined' if login.nil?
|
|
25
|
+
raise ArgumentError, 'password should be defined' if password.nil?
|
|
26
|
+
raise ArgumentError, 'host should be defined' if host.nil?
|
|
27
|
+
raise ArgumentError, 'logger should be defined when debug is enabled' if debug && logger.nil?
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def to_h
|
|
31
|
-
{
|
|
32
|
-
login: login,
|
|
33
|
-
password: password,
|
|
34
|
-
host: host,
|
|
35
|
-
originator: originator,
|
|
36
|
-
debug: debug,
|
|
37
|
-
logger: logger,
|
|
38
|
-
xml_parser: xml_parser
|
|
39
|
-
}
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def values_at(*keys)
|
|
43
|
-
to_h.values_at(*keys)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
private
|
|
47
|
-
|
|
48
|
-
def default_configuration
|
|
49
|
-
{
|
|
50
|
-
login: nil,
|
|
51
|
-
password: nil,
|
|
52
|
-
host: nil,
|
|
53
|
-
originator: nil,
|
|
54
|
-
debug: false,
|
|
55
|
-
logger: $stdout,
|
|
56
|
-
xml_parser: default_xml_parser,
|
|
57
|
-
validate_phone: true
|
|
58
|
-
}
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def validation_rules
|
|
62
|
-
{
|
|
63
|
-
'Login' => login,
|
|
64
|
-
'Password' => password,
|
|
65
|
-
'Server' => host,
|
|
66
|
-
'Originator' => originator,
|
|
67
|
-
'Debug logger' => debug && !logger ? nil : true
|
|
68
|
-
}
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def default_xml_parser
|
|
72
|
-
@default_xml_parser ||= Nori.new(advanced_typecasting: true).freeze
|
|
31
|
+
ATTRIBUTES.to_h { |attribute| [attribute, public_send(attribute)] }
|
|
73
32
|
end
|
|
74
33
|
end
|
|
75
34
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module SmsTraffic
|
|
2
|
+
class Error < StandardError; end
|
|
3
|
+
|
|
4
|
+
# Network or TLS failure; the original exception is available as +cause+.
|
|
5
|
+
class ConnectionError < Error; end
|
|
6
|
+
|
|
7
|
+
# Non-2xx HTTP status.
|
|
8
|
+
class HttpError < Error
|
|
9
|
+
attr_reader :status, :body
|
|
10
|
+
|
|
11
|
+
def initialize(status, body)
|
|
12
|
+
@status = status
|
|
13
|
+
@body = body
|
|
14
|
+
super("HTTP #{status}")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# HTTP 200 reply whose `result` is not `OK` (`ERROR`, `ERRO`).
|
|
19
|
+
class ApiError < Error
|
|
20
|
+
attr_reader :code, :description
|
|
21
|
+
|
|
22
|
+
def initialize(code, description)
|
|
23
|
+
@code = code
|
|
24
|
+
@description = description
|
|
25
|
+
super("#{code}: #{description}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Temporary server problem: retry later or use the backup host.
|
|
29
|
+
def retryable?
|
|
30
|
+
code.to_i == 1000
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def access_denied?
|
|
34
|
+
[411, 412].include?(code.to_i)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def insufficient_funds?
|
|
38
|
+
code.to_i == 415
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def blocked_phone?
|
|
42
|
+
code.to_i == 432
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/sms_traffic/sms.rb
CHANGED
|
@@ -1,55 +1,80 @@
|
|
|
1
1
|
module SmsTraffic
|
|
2
2
|
class Sms
|
|
3
|
+
PHONE_FORMAT = /\A\d{11,}\z/
|
|
4
|
+
|
|
3
5
|
attr_accessor :phone, :message, :originator
|
|
4
|
-
attr_reader :id, :status, :errors, :
|
|
6
|
+
attr_reader :id, :status, :errors, :reply
|
|
5
7
|
|
|
6
8
|
def initialize(phone, message, originator: nil)
|
|
7
9
|
@phone = phone
|
|
8
10
|
@message = message
|
|
9
11
|
@originator = originator || SmsTraffic.configuration.originator
|
|
10
|
-
@
|
|
12
|
+
@reply = nil
|
|
11
13
|
@status = 'not-sent'
|
|
12
14
|
@errors = []
|
|
13
15
|
validate!
|
|
14
16
|
end
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@
|
|
19
|
-
|
|
20
|
-
unless @response.success?
|
|
21
|
-
@errors << @response.error_description
|
|
22
|
-
return false
|
|
23
|
-
end
|
|
18
|
+
# @return [Boolean] true when the provider enqueued the sms
|
|
19
|
+
def deliver
|
|
20
|
+
@reply = Client.deliver(phone, message, originator)
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
reply
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@id = reply.sms_id || reply.sms_ids.values.first
|
|
31
|
-
true
|
|
32
|
-
else
|
|
33
|
-
@errors << (reply.error_description || 'Sms has been not enqueued')
|
|
34
|
-
false
|
|
35
|
-
end
|
|
22
|
+
reply.ok? ? mark_sent : add_error(reply.error_description)
|
|
23
|
+
reply.ok?
|
|
24
|
+
rescue SmsTraffic::Error => e
|
|
25
|
+
add_error(e.message)
|
|
26
|
+
false
|
|
36
27
|
end
|
|
37
28
|
|
|
29
|
+
# @return [String] the current status; unchanged when the check fails
|
|
38
30
|
def update_status
|
|
39
31
|
return status if id.nil?
|
|
40
32
|
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
apply_entry(Client.status(id).entry(id))
|
|
34
|
+
status
|
|
35
|
+
rescue SmsTraffic::Error => e
|
|
36
|
+
add_error(e.message)
|
|
37
|
+
status
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [Boolean] the stored status is a final provider status
|
|
41
|
+
def final?
|
|
42
|
+
Status.final?(status)
|
|
43
|
+
end
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
# @return [Boolean] the stored status is Delivered
|
|
46
|
+
def delivered?
|
|
47
|
+
Status.delivered?(status)
|
|
45
48
|
end
|
|
46
49
|
|
|
47
50
|
private
|
|
48
51
|
|
|
52
|
+
def mark_sent
|
|
53
|
+
@status = 'sent'
|
|
54
|
+
@id = reply.sms_id
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def add_error(error)
|
|
58
|
+
@errors << error
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def apply_entry(entry)
|
|
62
|
+
if entry&.error?
|
|
63
|
+
add_error(entry.error)
|
|
64
|
+
elsif entry&.status
|
|
65
|
+
@status = entry.status.downcase
|
|
66
|
+
elsif entry&.status_reported?
|
|
67
|
+
# An empty status element is an intermediate provider status: keep the current one.
|
|
68
|
+
nil
|
|
69
|
+
else
|
|
70
|
+
add_error("No status returned for sms #{id}")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
49
74
|
def validate! # rubocop:disable Metrics/AbcSize
|
|
50
75
|
raise ArgumentError, "Phone should be assigned to #{self.class}." if phone.nil?
|
|
51
76
|
|
|
52
|
-
if SmsTraffic.configuration.validate_phone && phone.to_s
|
|
77
|
+
if SmsTraffic.configuration.validate_phone && !PHONE_FORMAT.match?(phone.to_s)
|
|
53
78
|
raise ArgumentError, 'Phone number should contain only numbers. Minimum length is 11.'
|
|
54
79
|
end
|
|
55
80
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module SmsTraffic
|
|
2
|
+
# Provider delivery statuses (HTTP API v1, operation=status).
|
|
3
|
+
module Status
|
|
4
|
+
INTERMEDIATE = ['', 'Buffered SMSC'].freeze
|
|
5
|
+
DELIVERED = 'Delivered'.freeze
|
|
6
|
+
FINAL = [DELIVERED, 'Non Delivered', 'Rejected', 'Expired', 'Deleted', 'Unknown status'].freeze
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
# @param status [String, nil] provider status, compared case-insensitively
|
|
10
|
+
def final?(status)
|
|
11
|
+
matches?(FINAL, status)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def delivered?(status)
|
|
15
|
+
matches?([DELIVERED], status)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def matches?(list, status)
|
|
21
|
+
value = status.to_s.strip
|
|
22
|
+
list.any? { |item| item.casecmp?(value) }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/sms_traffic/version.rb
CHANGED
data/lib/sms_traffic.rb
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
require 'zeitwerk'
|
|
2
|
-
|
|
3
|
-
loader = Zeitwerk::Loader.for_gem
|
|
4
|
-
loader.ignore("#{__dir__}/sms_traffic/version.rb")
|
|
5
|
-
loader.enable_reloading
|
|
6
|
-
loader.setup
|
|
7
|
-
|
|
8
1
|
require_relative 'sms_traffic/version'
|
|
2
|
+
require_relative 'sms_traffic/errors'
|
|
3
|
+
require_relative 'sms_traffic/status'
|
|
4
|
+
require_relative 'sms_traffic/configuration'
|
|
5
|
+
require_relative 'sms_traffic/client'
|
|
6
|
+
require_relative 'sms_traffic/sms'
|
|
9
7
|
|
|
10
8
|
module SmsTraffic
|
|
11
9
|
class << self
|
|
12
10
|
def configuration
|
|
13
|
-
@configuration ||= Configuration.
|
|
11
|
+
@configuration ||= Configuration.new
|
|
14
12
|
end
|
|
15
13
|
|
|
16
|
-
def configure
|
|
14
|
+
def configure
|
|
17
15
|
yield(configuration) if block_given?
|
|
18
16
|
configuration.validate!
|
|
19
17
|
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require_relative 'sms_traffic'
|
metadata
CHANGED
|
@@ -1,96 +1,90 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sms_traffic_sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Rybolovlev
|
|
8
|
-
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-11-03 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
14
|
+
name: bigdecimal
|
|
14
15
|
requirement: !ruby/object:Gem::Requirement
|
|
15
16
|
requirements:
|
|
16
|
-
- - "
|
|
17
|
+
- - ">="
|
|
17
18
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '1
|
|
19
|
+
version: '3.1'
|
|
19
20
|
type: :runtime
|
|
20
21
|
prerelease: false
|
|
21
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
23
|
requirements:
|
|
23
|
-
- - "
|
|
24
|
+
- - ">="
|
|
24
25
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '1
|
|
26
|
+
version: '3.1'
|
|
26
27
|
- !ruby/object:Gem::Dependency
|
|
27
|
-
name:
|
|
28
|
+
name: nokogiri
|
|
28
29
|
requirement: !ruby/object:Gem::Requirement
|
|
29
30
|
requirements:
|
|
30
31
|
- - "~>"
|
|
31
32
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '
|
|
33
|
+
version: '1.12'
|
|
33
34
|
type: :runtime
|
|
34
35
|
prerelease: false
|
|
35
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
37
|
requirements:
|
|
37
38
|
- - "~>"
|
|
38
39
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '
|
|
40
|
+
version: '1.12'
|
|
40
41
|
- !ruby/object:Gem::Dependency
|
|
41
|
-
name:
|
|
42
|
+
name: nori
|
|
42
43
|
requirement: !ruby/object:Gem::Requirement
|
|
43
44
|
requirements:
|
|
44
45
|
- - "~>"
|
|
45
46
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '2.
|
|
47
|
+
version: '2.8'
|
|
47
48
|
type: :runtime
|
|
48
49
|
prerelease: false
|
|
49
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
51
|
requirements:
|
|
51
52
|
- - "~>"
|
|
52
53
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '2.
|
|
54
|
+
version: '2.8'
|
|
54
55
|
description: |-
|
|
55
56
|
Ruby Gem as a SDK for interacting with the SMS Traffic HTTP API (smstraffic.ru/api).
|
|
56
57
|
This gem provides a convenient way to integrate SMS Traffic services into Ruby applications,
|
|
57
58
|
enabling tasks such as sending SMS messages, checking delivery status, and others.
|
|
58
59
|
email:
|
|
59
60
|
- i@golifox.ru
|
|
60
|
-
executables:
|
|
61
|
-
- konsierge-sms.rb
|
|
61
|
+
executables: []
|
|
62
62
|
extensions: []
|
|
63
63
|
extra_rdoc_files: []
|
|
64
64
|
files:
|
|
65
|
-
-
|
|
66
|
-
- ".fasterer.yml"
|
|
67
|
-
- ".github/workflows/ci.yml"
|
|
68
|
-
- ".gitignore"
|
|
69
|
-
- ".rspec"
|
|
70
|
-
- ".rubocop.yml"
|
|
71
|
-
- ".tool-versions"
|
|
72
|
-
- Gemfile
|
|
65
|
+
- CHANGELOG.md
|
|
73
66
|
- LICENSE
|
|
74
67
|
- README.md
|
|
75
|
-
- Rakefile
|
|
76
|
-
- bin/console
|
|
77
|
-
- bin/setup
|
|
78
|
-
- exe/konsierge-sms.rb
|
|
79
68
|
- lib/sms_traffic.rb
|
|
80
69
|
- lib/sms_traffic/client.rb
|
|
70
|
+
- lib/sms_traffic/client/account_reply.rb
|
|
81
71
|
- lib/sms_traffic/client/deliver_reply.rb
|
|
82
72
|
- lib/sms_traffic/client/reply.rb
|
|
83
|
-
- lib/sms_traffic/client/response.rb
|
|
84
73
|
- lib/sms_traffic/client/status_reply.rb
|
|
85
74
|
- lib/sms_traffic/configuration.rb
|
|
75
|
+
- lib/sms_traffic/errors.rb
|
|
86
76
|
- lib/sms_traffic/sms.rb
|
|
77
|
+
- lib/sms_traffic/status.rb
|
|
87
78
|
- lib/sms_traffic/version.rb
|
|
88
|
-
- sms_traffic_sdk.
|
|
89
|
-
homepage: https://github.com/golifox/
|
|
79
|
+
- lib/sms_traffic_sdk.rb
|
|
80
|
+
homepage: https://github.com/golifox/sms_traffic_sdk
|
|
90
81
|
licenses:
|
|
91
82
|
- MIT
|
|
92
83
|
metadata:
|
|
93
84
|
rubygems_mfa_required: 'true'
|
|
85
|
+
source_code_uri: https://github.com/golifox/sms_traffic_sdk
|
|
86
|
+
changelog_uri: https://github.com/golifox/sms_traffic_sdk/blob/main/CHANGELOG.md
|
|
87
|
+
post_install_message:
|
|
94
88
|
rdoc_options: []
|
|
95
89
|
require_paths:
|
|
96
90
|
- lib
|
|
@@ -98,14 +92,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
98
92
|
requirements:
|
|
99
93
|
- - ">="
|
|
100
94
|
- !ruby/object:Gem::Version
|
|
101
|
-
version:
|
|
95
|
+
version: '3.1'
|
|
102
96
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
97
|
requirements:
|
|
104
98
|
- - ">="
|
|
105
99
|
- !ruby/object:Gem::Version
|
|
106
100
|
version: '0'
|
|
107
101
|
requirements: []
|
|
108
|
-
rubygems_version: 3.
|
|
102
|
+
rubygems_version: 3.5.22
|
|
103
|
+
signing_key:
|
|
109
104
|
specification_version: 4
|
|
110
105
|
summary: Send sms via SmsTraffic service.
|
|
111
106
|
test_files: []
|
data/.env.template
DELETED
data/.fasterer.yml
DELETED