smsru_ruby 1.0.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.
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SmsRu
4
+ # Manages the account stoplist (numbers that never receive messages and are
5
+ # never charged). Reached via SmsRu#stoplist, e.g. `client.stoplist.add(...)`.
6
+ class Stoplist
7
+ # @api private
8
+ # @param request [Method] the client's bound `request` method
9
+ def initialize(request)
10
+ @request = request
11
+ end
12
+
13
+ # Adds a number to the stoplist. `note` is visible only to you.
14
+ #
15
+ # @param phone [String, Integer] the number to stoplist
16
+ # @param note [String, nil] an optional private note
17
+ # @return [Boolean] true on success
18
+ # @raise [SmsRu::ResponseError] if SMS.ru rejects the request
19
+ def add(phone, note: nil)
20
+ @request.call("/stoplist/add", stoplist_phone: phone.to_s, stoplist_text: note.to_s)
21
+ true
22
+ end
23
+
24
+ # Removes a number from the stoplist.
25
+ #
26
+ # @param phone [String, Integer] the number to remove
27
+ # @return [Boolean] true on success
28
+ # @raise [SmsRu::ResponseError] if SMS.ru rejects the request
29
+ def remove(phone)
30
+ @request.call("/stoplist/del", stoplist_phone: phone.to_s)
31
+ true
32
+ end
33
+
34
+ # Returns every stoplisted number as an Array of SmsRu::StoplistEntry.
35
+ #
36
+ # @return [Array<SmsRu::StoplistEntry>]
37
+ # @raise [SmsRu::ResponseError] if SMS.ru rejects the request
38
+ def list
39
+ data = @request.call("/stoplist/get")
40
+ Coerce.records(data["stoplist"]).map { |phone, note| StoplistEntry.new(phone: String(phone), note: String(note)) }
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SmsRu
4
+ # The gem version.
5
+ VERSION = "1.0.0"
6
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SmsRu
4
+ # Parses the inbound webhook payload SMS.ru POSTs to your callback URL and
5
+ # verifies its signature. SMS.ru sends the records as POST fields
6
+ # `data[0]..data[N]` (so `params["data"]` is a Hash in Rack/Rails, an Array
7
+ # in PHP) plus a `hash` field; acknowledge the webhook by replying with "100".
8
+ #
9
+ # {parse} returns one typed event per record — a {SmsRu::Events::SmsStatus},
10
+ # {SmsRu::Events::CallcheckStatus}, {SmsRu::Events::Test}, or
11
+ # {SmsRu::Events::Unknown} — best handled with a case match:
12
+ #
13
+ # return head(:forbidden) unless SmsRu::Webhook.valid?(params["data"], params["hash"], api_id)
14
+ # SmsRu::Webhook.parse(params["data"]).each do |event|
15
+ # case event
16
+ # when SmsRu::Events::SmsStatus then update_delivery(event.id, event.status_code)
17
+ # when SmsRu::Events::CallcheckStatus then confirm(event.id) if event.confirmed?
18
+ # end
19
+ # end
20
+ module Webhook
21
+ # @param data [Hash, Array<String>, String, nil] the POST "data" parameter
22
+ # @return [Array<SmsRu::Events::SmsStatus, SmsRu::Events::CallcheckStatus,
23
+ # SmsRu::Events::Test, SmsRu::Events::Unknown>] one event per record
24
+ def self.parse(data)
25
+ entries(data).map do |entry|
26
+ lines = entry.to_s.split("\n")
27
+ case lines[0]
28
+ when "sms_status" then Events::SmsStatus.new(**status_fields(lines))
29
+ when "callcheck_status" then Events::CallcheckStatus.new(**status_fields(lines))
30
+ when "test" then Events::Test.new(created_at: time(lines[1]), raw: lines)
31
+ else Events::Unknown.new(type: lines[0].to_s, raw: lines)
32
+ end
33
+ end
34
+ end
35
+
36
+ # Verifies the payload genuinely came from SMS.ru (constant-time compare of
37
+ # SMS.ru's `hash` against `sha256(api_id + concatenated data entries)`).
38
+ #
39
+ # @param data [Hash, Array<String>, String, nil] the POST "data" parameter
40
+ # @param hash [String, nil] the POST "hash" parameter
41
+ # @param api_id [String] your SMS.ru API id
42
+ # @return [Boolean] true when the signature matches
43
+ def self.valid?(data, hash, api_id)
44
+ return false unless hash.is_a?(String)
45
+
46
+ expected = OpenSSL::Digest::SHA256.hexdigest("#{api_id}#{entries(data).join}")
47
+ expected.bytesize == hash.bytesize && OpenSSL.fixed_length_secure_compare(expected, hash)
48
+ end
49
+
50
+ # Common fields of the "type / id / status / timestamp" status records.
51
+ # @api private
52
+ def self.status_fields(lines)
53
+ { id: lines[1].to_s, status_code: Coerce.integer?(lines[2]), created_at: time(lines[3]), raw: lines }
54
+ end
55
+
56
+ # Normalizes the `data` param to an ordered Array of record strings. SMS.ru
57
+ # numbers the fields data[0..N]; Rack delivers them as a Hash, so sort by
58
+ # the numeric key to preserve SMS.ru's order (the signature depends on it).
59
+ #
60
+ # @api private
61
+ # @param data [Hash, Array<String>, String, nil] the POST "data" parameter
62
+ # @return [Array<String>] the records in the order SMS.ru sent them
63
+ def self.entries(data)
64
+ data.is_a?(Hash) ? data.sort_by { |k, _| Coerce.integer(k) }.map(&:last) : Array(data)
65
+ end
66
+
67
+ # Converts a unix-timestamp line into a Time, or nil when absent.
68
+ # @api private
69
+ def self.time(str)
70
+ unix = Coerce.integer?(str)
71
+ unix && Time.at(unix)
72
+ end
73
+ end
74
+ end
data/lib/smsru_ruby.rb ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "openssl"
6
+
7
+ require_relative "sms_ru/version"
8
+ require_relative "sms_ru/errors"
9
+ require_relative "sms_ru/statuses"
10
+ require_relative "sms_ru/coerce"
11
+ require_relative "sms_ru/data"
12
+ require_relative "sms_ru/events"
13
+ require_relative "sms_ru/webhook"
14
+ require_relative "sms_ru/my"
15
+ require_relative "sms_ru/auth"
16
+ require_relative "sms_ru/stoplist"
17
+ require_relative "sms_ru/callbacks"
18
+ require_relative "sms_ru/call_check"
19
+ require_relative "sms_ru/client"
data/sig/manifest.yaml ADDED
@@ -0,0 +1,9 @@
1
+ # Standard-library signatures these RBS files reference. Consumers' RBS/Steep
2
+ # load these automatically when they depend on smsru_ruby. (The gem has no
3
+ # runtime gem dependencies, so none are auto-derived from the gemspec.)
4
+ dependencies:
5
+ - name: logger # Logger? in SmsRu#initialize
6
+ - name: openssl # Webhook signature verification
7
+ - name: json # client request parsing
8
+ - name: net-http # client transport
9
+ - name: uri # client transport
@@ -0,0 +1,9 @@
1
+ class SmsRu
2
+ # Authentication checks against the account.
3
+ class Auth
4
+ @request: _Request
5
+
6
+ def initialize: (_Request request) -> void
7
+ def ok?: () -> bool
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ class SmsRu
2
+ # Authorizes a user by an incoming call from their own number.
3
+ class CallCheck
4
+ @request: _Request
5
+
6
+ def initialize: (_Request request) -> void
7
+ def add: (String | Integer phone) -> CallCheckResult
8
+ def status: (String | Integer check_id) -> CallCheckStatus
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ class SmsRu
2
+ # Manages callback (webhook) URLs that SMS.ru notifies with delivery statuses.
3
+ class Callbacks
4
+ @request: _Request
5
+
6
+ def initialize: (_Request request) -> void
7
+ def add: (String url) -> Array[String]
8
+ def remove: (String url) -> Array[String]
9
+ def list: () -> Array[String]
10
+
11
+ private
12
+
13
+ def urls: (Hash[String, untyped] data) -> Array[String]
14
+ end
15
+ end
@@ -0,0 +1,53 @@
1
+ # Ruby client for the SMS.ru HTTP API (https://sms.ru/api).
2
+ class SmsRu
3
+ # The client's bound `request` method as handed to each sub-resource. A
4
+ # `Method` conforms structurally, so `method(:request)` is accepted here while
5
+ # callers still see a typed `Hash[String, untyped]` return instead of untyped.
6
+ interface _Request
7
+ def call: (String path, **untyped params) -> Hash[String, untyped]
8
+ end
9
+
10
+ BASE_URL: String
11
+ # Singletons (not bare Class) so `rescue *RETRIABLE => e` types e as StandardError.
12
+ RETRIABLE: Array[singleton(StandardError)]
13
+
14
+ @api_id: String
15
+ @timeout: Integer
16
+ @test: bool
17
+ @retries: Integer
18
+ @from: String?
19
+ @logger: Logger?
20
+ @my: My?
21
+ @auth: Auth?
22
+ @stoplist: Stoplist?
23
+ @callbacks: Callbacks?
24
+ @callcheck: CallCheck?
25
+
26
+ def initialize: (String api_id, ?timeout: Integer, ?test: bool, ?retries: Integer, ?from: String?, ?logger: Logger?) -> void
27
+
28
+ def deliver: (String | Array[String] | Hash[String, String] to, ?String? text,
29
+ ?from: String?, ?time: Integer?, ?ttl: Integer?, ?daytime: bool,
30
+ ?translit: bool, ?test: bool?, ?ip: String?, ?partner_id: Integer?) -> SendResult
31
+
32
+ def cost: (String | Array[String] to, ?String? text, ?translit: bool) -> Cost
33
+
34
+ def status: (String | Array[String] sms_id) -> (Status | Array[Status])
35
+
36
+ def call: (String | Integer phone, ?ip: String, ?partner_id: Integer?) -> Call
37
+
38
+ def my: () -> My
39
+ def auth: () -> Auth
40
+ def stoplist: () -> Stoplist
41
+ def callbacks: () -> Callbacks
42
+ def callcheck: () -> CallCheck
43
+
44
+ private
45
+
46
+ def add_recipients: (Hash[untyped, untyped] params, String | Array[String] | Hash[String, String] to, String? text) -> void
47
+ def request: (String path, **untyped params) -> Hash[String, untyped]
48
+ def perform: (URI::HTTP uri, String body) -> Hash[String, untyped]
49
+ def http: (URI::HTTP uri) -> Net::HTTP
50
+ def parse: (String? raw) -> Hash[String, untyped]
51
+ def error_for: (Hash[String, untyped] data) -> ResponseError
52
+ def error_class: (Integer code) -> singleton(ResponseError)
53
+ end
@@ -0,0 +1,15 @@
1
+ class SmsRu
2
+ # Normalizes loosely-typed SMS.ru JSON values into the result objects' types.
3
+ # `self?.` mirrors `module_function`: each helper is both a private instance
4
+ # method and a public singleton method. The `?` variant is nullable; the plain
5
+ # variant falls back to a (overridable) default, so it never returns nil.
6
+ module Coerce
7
+ def self?.string?: (untyped value) -> String?
8
+ def self?.string: (untyped value, ?String default) -> String
9
+ def self?.integer?: (untyped value) -> Integer?
10
+ def self?.integer: (untyped value, ?Integer default) -> Integer
11
+ def self?.float?: (untyped value) -> Float?
12
+ def self?.float: (untyped value, ?Float default) -> Float
13
+ def self?.records: (untyped value) -> Hash[untyped, untyped]
14
+ end
15
+ end
@@ -0,0 +1,141 @@
1
+ class SmsRu
2
+ # Self-type element for MessageCollection: each entry responds to #ok?.
3
+ interface _OkItem
4
+ def ok?: () -> bool
5
+ end
6
+
7
+ # Self-type host for MessageCollection: exposes a `messages` array of _OkItem.
8
+ interface _MessageHost[E]
9
+ def messages: () -> Array[E]
10
+ end
11
+
12
+ # Collection helpers shared by results wrapping a `messages` array.
13
+ module MessageCollection[E < _OkItem] : _MessageHost[E]
14
+ def ok?: () -> bool
15
+ def ok: () -> Array[E]
16
+ def failed: () -> Array[E]
17
+ end
18
+
19
+ # A single message inside a send response.
20
+ class Sms
21
+ attr_reader phone: String
22
+ attr_reader sms_id: String?
23
+ attr_reader error_code: Integer?
24
+ attr_reader error_text: String?
25
+
26
+ def initialize: (phone: String, sms_id: String?, error_code: Integer?, error_text: String?) -> void
27
+ def self.build: (String phone, Hash[String, untyped] hash) -> Sms
28
+ def ok?: () -> bool
29
+ end
30
+
31
+ # Result of SmsRu#deliver.
32
+ class SendResult
33
+ include MessageCollection[Sms]
34
+
35
+ attr_reader balance: Float
36
+ attr_reader messages: Array[Sms]
37
+
38
+ def initialize: (balance: Float, messages: Array[Sms]) -> void
39
+ def self.build: (Hash[String, untyped] hash) -> SendResult
40
+ end
41
+
42
+ # Delivery status of one message.
43
+ class Status
44
+ include DeliveryStatus
45
+
46
+ attr_reader sms_id: String
47
+ attr_reader status_code: Integer
48
+ attr_reader status_text: String
49
+ attr_reader cost: Float?
50
+
51
+ def initialize: (sms_id: String, status_code: Integer, status_text: String, cost: Float?) -> void
52
+ def self.build: (String sms_id, Hash[String, untyped] hash) -> Status
53
+ def self.build_all: (Hash[String, untyped] hash) -> Array[Status]
54
+ def found?: () -> bool
55
+ end
56
+
57
+ # Per-recipient cost (one entry of a /sms/cost response).
58
+ class CostItem
59
+ attr_reader phone: String
60
+ attr_reader cost: Float?
61
+ attr_reader sms_count: Integer?
62
+ attr_reader error_code: Integer?
63
+ attr_reader error_text: String?
64
+
65
+ def initialize: (phone: String, cost: Float?, sms_count: Integer?, error_code: Integer?, error_text: String?) -> void
66
+ def self.build: (String phone, Hash[String, untyped] hash) -> CostItem
67
+ def ok?: () -> bool
68
+ end
69
+
70
+ # Result of SmsRu#cost.
71
+ class Cost
72
+ include MessageCollection[CostItem]
73
+
74
+ attr_reader total_cost: Float
75
+ attr_reader total_sms: Integer
76
+ attr_reader messages: Array[CostItem]
77
+
78
+ def initialize: (total_cost: Float, total_sms: Integer, messages: Array[CostItem]) -> void
79
+ def self.build: (Hash[String, untyped] hash) -> Cost
80
+ end
81
+
82
+ # Result of SmsRu#call (flash call).
83
+ class Call
84
+ attr_reader code: String
85
+ attr_reader call_id: String
86
+ attr_reader cost: Float
87
+ attr_reader balance: Float
88
+
89
+ def initialize: (code: String, call_id: String, cost: Float, balance: Float) -> void
90
+ def self.build: (Hash[String, untyped] hash) -> Call
91
+ end
92
+
93
+ # Result of SmsRu::My#limit (daily sending limit).
94
+ class Limit
95
+ attr_reader total_limit: Integer
96
+ attr_reader used_today: Integer
97
+
98
+ def initialize: (total_limit: Integer, used_today: Integer) -> void
99
+ def self.build: (Hash[String, untyped] hash) -> Limit
100
+ def available_today: () -> Integer
101
+ end
102
+
103
+ # Result of SmsRu::My#free_limit (free daily messages).
104
+ class FreeLimit
105
+ attr_reader total_free: Integer
106
+ attr_reader used_today: Integer
107
+
108
+ def initialize: (total_free: Integer, used_today: Integer) -> void
109
+ def self.build: (Hash[String, untyped] hash) -> FreeLimit
110
+ def available_today: () -> Integer
111
+ end
112
+
113
+ # Result of SmsRu::CallCheck#add.
114
+ class CallCheckResult
115
+ attr_reader check_id: String
116
+ attr_reader call_phone: String
117
+ attr_reader call_phone_pretty: String
118
+ attr_reader call_phone_html: String
119
+
120
+ def initialize: (check_id: String, call_phone: String, call_phone_pretty: String, call_phone_html: String) -> void
121
+ def self.build: (Hash[String, untyped] hash) -> CallCheckResult
122
+ end
123
+
124
+ # Result of SmsRu::CallCheck#status.
125
+ class CallCheckStatus
126
+ attr_reader status_code: Integer
127
+ attr_reader status_text: String
128
+
129
+ def initialize: (status_code: Integer, status_text: String) -> void
130
+ def self.build: (Hash[String, untyped] hash) -> CallCheckStatus
131
+ def confirmed?: () -> bool
132
+ end
133
+
134
+ # One stoplist entry returned by SmsRu::Stoplist#list.
135
+ class StoplistEntry
136
+ attr_reader phone: String
137
+ attr_reader note: String
138
+
139
+ def initialize: (phone: String, note: String) -> void
140
+ end
141
+ end
@@ -0,0 +1,25 @@
1
+ class SmsRu
2
+ # Base class for every error raised by the gem.
3
+ class Error < StandardError
4
+ end
5
+
6
+ # Raised when SMS.ru cannot be reached or returns an unparseable body.
7
+ class ConnectionError < Error
8
+ end
9
+
10
+ # Raised when SMS.ru replies with a non-OK status.
11
+ class ResponseError < Error
12
+ attr_reader code: Integer
13
+ attr_reader text: String
14
+
15
+ def initialize: (code: Integer, text: String) -> void
16
+ end
17
+
18
+ # Invalid api_id / token / unconfirmed account (codes 200, 300, 301, 302).
19
+ class AuthError < ResponseError
20
+ end
21
+
22
+ # Not enough money on the account (code 201).
23
+ class InsufficientFundsError < ResponseError
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ class SmsRu
2
+ # Typed events decoded from an inbound SMS.ru webhook payload.
3
+ module Events
4
+ # A delivery-status notification (record type "sms_status"). Its `status_code`
5
+ # is nullable (a malformed line yields nil); DeliveryStatus's predicates
6
+ # nil-guard, so the module is included directly here as it is at runtime.
7
+ class SmsStatus
8
+ include DeliveryStatus
9
+
10
+ attr_reader id: String
11
+ attr_reader status_code: Integer?
12
+ attr_reader created_at: Time?
13
+ attr_reader raw: Array[String]
14
+
15
+ def initialize: (id: String, status_code: Integer?, created_at: Time?, raw: Array[String]) -> void
16
+ def type: () -> String
17
+ end
18
+
19
+ # A call-authorization notification (record type "callcheck_status").
20
+ class CallcheckStatus
21
+ attr_reader id: String
22
+ attr_reader status_code: Integer?
23
+ attr_reader created_at: Time?
24
+ attr_reader raw: Array[String]
25
+
26
+ def initialize: (id: String, status_code: Integer?, created_at: Time?, raw: Array[String]) -> void
27
+ def type: () -> String
28
+ def confirmed?: () -> bool
29
+ def expired?: () -> bool
30
+ end
31
+
32
+ # SMS.ru's periodic heartbeat record (record type "test").
33
+ class Test
34
+ attr_reader created_at: Time?
35
+ attr_reader raw: Array[String]
36
+
37
+ def initialize: (created_at: Time?, raw: Array[String]) -> void
38
+ def type: () -> String
39
+ end
40
+
41
+ # Any record type this gem does not model explicitly.
42
+ class Unknown
43
+ attr_reader type: String
44
+ attr_reader raw: Array[String]
45
+
46
+ def initialize: (type: String, raw: Array[String]) -> void
47
+ end
48
+ end
49
+ end
data/sig/sms_ru/my.rbs ADDED
@@ -0,0 +1,12 @@
1
+ class SmsRu
2
+ # Account information: balance, daily limit, free quota, approved sender names.
3
+ class My
4
+ @request: _Request
5
+
6
+ def initialize: (_Request request) -> void
7
+ def balance: () -> Float
8
+ def limit: () -> Limit
9
+ def free_limit: () -> FreeLimit
10
+ def senders: () -> Array[String]
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ class SmsRu
2
+ # Delivery status codes returned by /sms/status and carried in `sms_status` webhooks.
3
+ module Statuses
4
+ NOT_FOUND: Integer
5
+ QUEUED: Integer
6
+ SENT_TO_OPERATOR: Integer
7
+ IN_TRANSIT: Integer
8
+ DELIVERED: Integer
9
+ EXPIRED: Integer
10
+ DELETED: Integer
11
+ PHONE_FAILURE: Integer
12
+ UNKNOWN_FAILURE: Integer
13
+ REJECTED: Integer
14
+ READ: Integer
15
+ NO_ROUTE: Integer
16
+
17
+ PENDING: Array[Integer]
18
+ FAILED: Array[Integer]
19
+ end
20
+
21
+ # Self-type for DeliveryStatus: the including object must expose `status_code`.
22
+ # Nullable so both SmsRu::Status (always present) and SmsRu::Events::SmsStatus
23
+ # (nil for a malformed webhook line) can include the module; the predicates
24
+ # nil-guard before the Array#include? lookups.
25
+ interface _HasStatusCode
26
+ def status_code: () -> Integer?
27
+ end
28
+
29
+ # Delivery-state predicates shared by SmsRu::Status and SmsRu::Events::SmsStatus.
30
+ module DeliveryStatus : _HasStatusCode
31
+ def delivered?: () -> bool
32
+ def pending?: () -> bool
33
+ def failed?: () -> bool
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ class SmsRu
2
+ # Manages the account stoplist.
3
+ class Stoplist
4
+ @request: _Request
5
+
6
+ def initialize: (_Request request) -> void
7
+ def add: (String | Integer phone, ?note: String?) -> bool
8
+ def remove: (String | Integer phone) -> bool
9
+ def list: () -> Array[StoplistEntry]
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ class SmsRu
2
+ VERSION: String
3
+ end
@@ -0,0 +1,17 @@
1
+ class SmsRu
2
+ # Parses the inbound webhook payload SMS.ru POSTs to your callback URL and
3
+ # verifies its signature.
4
+ module Webhook
5
+ type payload = Hash[untyped, untyped] | Array[String] | String | nil
6
+ type event = Events::SmsStatus | Events::CallcheckStatus | Events::Test | Events::Unknown
7
+
8
+ def self.parse: (payload data) -> Array[event]
9
+ def self.valid?: (payload data, String? hash, String api_id) -> bool
10
+
11
+ # @api private helpers. status_fields returns a record so Steep can expand it
12
+ # into the SmsStatus/CallcheckStatus keyword constructors via `**`.
13
+ def self.status_fields: (Array[String] lines) -> { id: String, status_code: Integer?, created_at: Time?, raw: Array[String] }
14
+ def self.entries: (payload data) -> Array[String]
15
+ def self.time: (String? str) -> Time?
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/sms_ru/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "smsru_ruby"
7
+ spec.version = SmsRu::VERSION
8
+ spec.authors = ["Leonid Svyatov"]
9
+ spec.email = ["leonid@svyatov.com"]
10
+
11
+ spec.summary = "Modern, dependency-free Ruby client for the SMS.ru API."
12
+ spec.description = "A modern, dependency-free Ruby client for the SMS.ru HTTP API. Send single or bulk SMS, " \
13
+ "schedule delivery, check cost and delivery status, verify users by phone call, inspect " \
14
+ "balance/limits/senders, manage the stoplist, and register delivery callbacks."
15
+ spec.homepage = "https://github.com/svyatov/smsru_ruby"
16
+ spec.license = "MIT"
17
+
18
+ spec.required_ruby_version = ">= 3.2.0"
19
+
20
+ spec.require_paths = ["lib"]
21
+ spec.files = Dir["lib/**/*.rb"] + Dir["sig/**/*"] +
22
+ %w[.yardopts CHANGELOG.md LICENSE.txt README.md smsru_ruby.gemspec]
23
+
24
+ spec.metadata["rubygems_mfa_required"] = "true"
25
+ spec.metadata["documentation_uri"] = "https://rubydoc.info/gems/smsru_ruby"
26
+ spec.metadata["source_code_uri"] = "https://github.com/svyatov/smsru_ruby"
27
+ spec.metadata["changelog_uri"] = "https://github.com/svyatov/smsru_ruby/blob/main/CHANGELOG.md"
28
+ spec.metadata["bug_tracker_uri"] = "https://github.com/svyatov/smsru_ruby/issues"
29
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smsru_ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Leonid Svyatov
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: A modern, dependency-free Ruby client for the SMS.ru HTTP API. Send single
13
+ or bulk SMS, schedule delivery, check cost and delivery status, verify users by
14
+ phone call, inspect balance/limits/senders, manage the stoplist, and register delivery
15
+ callbacks.
16
+ email:
17
+ - leonid@svyatov.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - ".yardopts"
23
+ - CHANGELOG.md
24
+ - LICENSE.txt
25
+ - README.md
26
+ - lib/sms_ru/auth.rb
27
+ - lib/sms_ru/call_check.rb
28
+ - lib/sms_ru/callbacks.rb
29
+ - lib/sms_ru/client.rb
30
+ - lib/sms_ru/coerce.rb
31
+ - lib/sms_ru/data.rb
32
+ - lib/sms_ru/errors.rb
33
+ - lib/sms_ru/events.rb
34
+ - lib/sms_ru/my.rb
35
+ - lib/sms_ru/statuses.rb
36
+ - lib/sms_ru/stoplist.rb
37
+ - lib/sms_ru/version.rb
38
+ - lib/sms_ru/webhook.rb
39
+ - lib/smsru_ruby.rb
40
+ - sig/manifest.yaml
41
+ - sig/sms_ru/auth.rbs
42
+ - sig/sms_ru/call_check.rbs
43
+ - sig/sms_ru/callbacks.rbs
44
+ - sig/sms_ru/client.rbs
45
+ - sig/sms_ru/coerce.rbs
46
+ - sig/sms_ru/data.rbs
47
+ - sig/sms_ru/errors.rbs
48
+ - sig/sms_ru/events.rbs
49
+ - sig/sms_ru/my.rbs
50
+ - sig/sms_ru/statuses.rbs
51
+ - sig/sms_ru/stoplist.rbs
52
+ - sig/sms_ru/version.rbs
53
+ - sig/sms_ru/webhook.rbs
54
+ - smsru_ruby.gemspec
55
+ homepage: https://github.com/svyatov/smsru_ruby
56
+ licenses:
57
+ - MIT
58
+ metadata:
59
+ rubygems_mfa_required: 'true'
60
+ documentation_uri: https://rubydoc.info/gems/smsru_ruby
61
+ source_code_uri: https://github.com/svyatov/smsru_ruby
62
+ changelog_uri: https://github.com/svyatov/smsru_ruby/blob/main/CHANGELOG.md
63
+ bug_tracker_uri: https://github.com/svyatov/smsru_ruby/issues
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 3.2.0
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 4.0.12
79
+ specification_version: 4
80
+ summary: Modern, dependency-free Ruby client for the SMS.ru API.
81
+ test_files: []