smsru-ruby 2.0.0 → 3.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.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/CHANGELOG.md +42 -5
- data/README.md +96 -101
- data/lib/sms_ru/client.rb +2 -2
- data/lib/sms_ru/coerce.rb +3 -3
- data/lib/sms_ru/data.rb +3 -3
- data/lib/sms_ru/statuses.rb +2 -2
- data/lib/sms_ru/version.rb +1 -1
- data/lib/sms_ru/webhook.rb +3 -3
- data/smsru-ruby.gemspec +4 -4
- metadata +9 -9
- /data/{LICENSE.txt → LICENSE} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9fa32fb7428f230423bda4d5a4b8e776c719df31daebe9ffb30e488e8bc7c620
|
|
4
|
+
data.tar.gz: 8dd26fbc54609529e34df51e2ff374e64d3657be1187e2ff76a9d33dbbcbb7ed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a4fb447fbe3057aaab9b6e92ee90645bb2294ef0b7a75fc697c4befd820e77f9115f485b1f4c12ac05c624a0b5224ab82b8b5f375fa7ee958b7eeb39b793f7d9
|
|
7
|
+
data.tar.gz: 235d9ca8fba5a74229ee3416a2fc58bc704aabb2f1b93d8bf18daff8916f948d10d2f56ef7aaa6a5ee3522e059515832dce806fe3fe755adbcdba09c82bea509
|
data/.yardopts
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -5,16 +5,51 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
The public API that Semantic Versioning covers is every constant and method
|
|
9
|
+
under `SmsRu` that YARD documents as public: the `SmsRu` client and its `auth`,
|
|
10
|
+
`callbacks`, `call_check`, `my`, and `stoplist` sub-resources, the result and
|
|
11
|
+
event `Data` classes, the `SmsRu::Statuses` constants and predicates, the error
|
|
12
|
+
hierarchy under `SmsRu::Error`, and `SmsRu::Webhook.parse`. Anything marked
|
|
13
|
+
`@api private`, including `SmsRu::Coerce` and the sub-resource constructors, may
|
|
14
|
+
change in any release. The minimum supported Ruby version is part of the public
|
|
15
|
+
API: raising it takes a MAJOR release.
|
|
16
|
+
|
|
8
17
|
## [Unreleased]
|
|
9
18
|
|
|
19
|
+
## [3.0.0] - 2026-09-07
|
|
20
|
+
|
|
21
|
+
### Removed
|
|
22
|
+
|
|
23
|
+
- Ruby 3.2 support. The minimum supported Ruby is now 3.3.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- This changelog now declares which constants and methods Semantic Versioning
|
|
28
|
+
covers, so a version range says something checkable about what may change.
|
|
29
|
+
|
|
30
|
+
## [2.0.1] - 2026-07-31
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- Releases are now built and published by GitHub Actions over OIDC, and every
|
|
35
|
+
published gem carries a Sigstore provenance attestation. Verify one with
|
|
36
|
+
`https://rubygems.org/api/v1/attestations/smsru-ruby-VERSION.json`.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- Renamed `LICENSE.txt` to `LICENSE`. The license text is untouched and the gem
|
|
41
|
+
is still MIT.
|
|
42
|
+
- Rewrote the README and widened the gemspec summary and description to describe
|
|
43
|
+
the full API surface.
|
|
44
|
+
|
|
10
45
|
## [2.0.0] - 2026-07-13
|
|
11
46
|
|
|
12
47
|
### Changed
|
|
13
48
|
|
|
14
|
-
- **
|
|
49
|
+
- **Breaking:** Renamed the gem from `smsru_ruby` to `smsru-ruby` for
|
|
15
50
|
consistency with the `-ruby` suffix convention. Update your `Gemfile`
|
|
16
51
|
(`gem "smsru-ruby"`) and requires (`require "smsru-ruby"`). The Ruby API is
|
|
17
|
-
unchanged
|
|
52
|
+
unchanged. The top-level class is still `SmsRu`.
|
|
18
53
|
|
|
19
54
|
## [1.0.0] - 2026-06-26
|
|
20
55
|
|
|
@@ -34,8 +69,8 @@ same API, reworked to be idiomatic Ruby. How it differs from the original:
|
|
|
34
69
|
and `Cost`; plus `#confirmed?` and `#available_today`. No raw decoded JSON or
|
|
35
70
|
magic numbers.
|
|
36
71
|
- **Typed error hierarchy** under `SmsRu::Error` (`AuthError`,
|
|
37
|
-
`InsufficientFundsError`, `ResponseError`, `ConnectionError`)
|
|
38
|
-
raised
|
|
72
|
+
`InsufficientFundsError`, `ResponseError`, `ConnectionError`). Errors are
|
|
73
|
+
raised rather than returned as status codes you have to inspect.
|
|
39
74
|
- **First-class inbound webhooks**: `SmsRu::Webhook.parse` decodes the callback
|
|
40
75
|
POST into typed events (`SmsRu::Events::SmsStatus`, `CallcheckStatus`, `Test`,
|
|
41
76
|
`Unknown`), and `SmsRu::Webhook.valid?` verifies the signature.
|
|
@@ -47,6 +82,8 @@ same API, reworked to be idiomatic Ruby. How it differs from the original:
|
|
|
47
82
|
SMS.ru's loosely-typed JSON is normalized to the declared types at the parse
|
|
48
83
|
boundary, so result objects never surface raw wire values.
|
|
49
84
|
|
|
50
|
-
[Unreleased]: https://github.com/svyatov/smsru-ruby/compare/
|
|
85
|
+
[Unreleased]: https://github.com/svyatov/smsru-ruby/compare/v3.0.0...HEAD
|
|
86
|
+
[3.0.0]: https://github.com/svyatov/smsru-ruby/compare/v2.0.1...v3.0.0
|
|
87
|
+
[2.0.1]: https://github.com/svyatov/smsru-ruby/compare/v2.0.0...v2.0.1
|
|
51
88
|
[2.0.0]: https://github.com/svyatov/smsru-ruby/compare/v1.0.0...v2.0.0
|
|
52
89
|
[1.0.0]: https://github.com/svyatov/smsru-ruby/releases/tag/v1.0.0
|
data/README.md
CHANGED
|
@@ -1,104 +1,71 @@
|
|
|
1
1
|
# smsru-ruby
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
[
|
|
49
|
-
- [What's covered](#whats-covered)
|
|
50
|
-
- [Supported Ruby versions](#supported-ruby-versions)
|
|
51
|
-
- [Installation](#installation)
|
|
52
|
-
- [Quick start](#quick-start)
|
|
53
|
-
- [Configuration](#configuration)
|
|
54
|
-
- [Sending messages](#sending-messages)
|
|
55
|
-
- [Cost and status](#cost-and-status)
|
|
56
|
-
- [Verify by phone call](#verify-by-phone-call)
|
|
57
|
-
- [Account information](#account-information)
|
|
58
|
-
- [Stoplist](#stoplist)
|
|
59
|
-
- [Callbacks (webhooks)](#callbacks-webhooks)
|
|
60
|
-
- [Error handling](#error-handling)
|
|
61
|
-
- [Development](#development)
|
|
62
|
-
- [Recording test cassettes](#recording-test-cassettes)
|
|
63
|
-
- [License](#license)
|
|
64
|
-
|
|
65
|
-
## Supported Ruby versions
|
|
66
|
-
|
|
67
|
-
Ruby **3.2+** (the result objects use [`Data`](https://docs.ruby-lang.org/en/3.2/Data.html)).
|
|
68
|
-
CI runs against `ruby-head`, `4.0`, `3.4`, `3.3`, and `3.2`.
|
|
3
|
+
smsru-ruby is a Ruby client for the [SMS.ru](https://sms.ru) HTTP API, for applications that send SMS,
|
|
4
|
+
check delivery, and verify users by phone call.
|
|
5
|
+
|
|
6
|
+
[](https://rubygems.org/gems/smsru-ruby)
|
|
7
|
+
[](https://github.com/svyatov/smsru-ruby/actions/workflows/main.yml)
|
|
8
|
+
[](https://app.codecov.io/gh/svyatov/smsru-ruby)
|
|
9
|
+
|
|
10
|
+
- **Covers the whole SMS.ru HTTP API.** Sending, cost, delivery status, flash call and callcheck
|
|
11
|
+
verification, balance and limits, stoplist, callback registration, and inbound webhook parsing.
|
|
12
|
+
- **11 typed result objects and 12 named status constants.** Every response arrives as a frozen `Data`
|
|
13
|
+
object rather than a decoded Hash.
|
|
14
|
+
- **No runtime dependencies.** The gem loads `net/http`, `json`, and `openssl` from the standard
|
|
15
|
+
library and nothing else.
|
|
16
|
+
- **A Ruby port of the official [SMS.ru PHP library](https://sms.ru/php).** Same API coverage, with
|
|
17
|
+
keyword arguments, namespaced sub-resources, and raised errors in place of flat `get_*` methods and
|
|
18
|
+
returned status codes.
|
|
19
|
+
- **100% line coverage and 100% documented public API.** Both gates run in CI on every push.
|
|
69
20
|
|
|
70
21
|
## Installation
|
|
71
22
|
|
|
23
|
+
Add the gem to your Gemfile.
|
|
24
|
+
|
|
72
25
|
```ruby
|
|
73
|
-
# Gemfile
|
|
74
26
|
gem "smsru-ruby"
|
|
75
27
|
```
|
|
76
28
|
|
|
77
|
-
|
|
78
|
-
bundle install
|
|
79
|
-
# or
|
|
80
|
-
gem install smsru-ruby
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
```ruby
|
|
84
|
-
require "smsru-ruby"
|
|
85
|
-
```
|
|
29
|
+
Then run `bundle install`. Without Bundler, run `gem install smsru-ruby`.
|
|
86
30
|
|
|
87
31
|
## Quick start
|
|
88
32
|
|
|
33
|
+
Create a client with your API id, then send a message.
|
|
34
|
+
|
|
89
35
|
```ruby
|
|
90
|
-
|
|
36
|
+
require "smsru-ruby"
|
|
91
37
|
|
|
38
|
+
client = SmsRu.new("YOUR_API_ID")
|
|
92
39
|
result = client.deliver("79991234567", "Hello from Ruby!")
|
|
93
|
-
|
|
94
|
-
|
|
40
|
+
|
|
41
|
+
result.messages.first.sms_id # => "000000-10000000"
|
|
42
|
+
client.my.balance # => 4762.58
|
|
95
43
|
```
|
|
96
44
|
|
|
97
45
|
Get your `api_id` in the SMS.ru dashboard under
|
|
98
|
-
[Settings
|
|
46
|
+
[Settings, API](https://sms.ru/?panel=api).
|
|
47
|
+
|
|
48
|
+
## API coverage
|
|
49
|
+
|
|
50
|
+
The full SMS.ru API, mapped to an idiomatic Ruby surface:
|
|
51
|
+
|
|
52
|
+
| Capability | Method |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| Send a single, bulk, or per-number text | `client.deliver` |
|
|
55
|
+
| Price a message before sending | `client.cost` |
|
|
56
|
+
| Delivery status, with state predicates | `client.status` |
|
|
57
|
+
| Verify by flash call (outbound) | `client.call` |
|
|
58
|
+
| Verify by callcheck (inbound) | `client.callcheck` |
|
|
59
|
+
| Balance, limits, free limit, senders | `client.my` |
|
|
60
|
+
| Validate credentials | `client.auth.ok?` |
|
|
61
|
+
| Stoplist: add, remove, list | `client.stoplist` |
|
|
62
|
+
| Webhook URLs: add, remove, list | `client.callbacks` |
|
|
63
|
+
| Parse and verify incoming webhooks | `SmsRu::Webhook` |
|
|
99
64
|
|
|
100
65
|
## Configuration
|
|
101
66
|
|
|
67
|
+
Every client option is a keyword argument on `SmsRu.new`:
|
|
68
|
+
|
|
102
69
|
```ruby
|
|
103
70
|
SmsRu.new(
|
|
104
71
|
"YOUR_API_ID",
|
|
@@ -111,11 +78,11 @@ SmsRu.new(
|
|
|
111
78
|
```
|
|
112
79
|
|
|
113
80
|
Retries apply only to transport-level problems (timeouts, refused connections).
|
|
114
|
-
API errors are never retried
|
|
81
|
+
API errors are never retried. They are raised immediately.
|
|
115
82
|
|
|
116
83
|
`from` is a per-client default so you don't repeat your sender name on every call;
|
|
117
84
|
a per-call `from:` always wins. The `logger` logs only the request path and
|
|
118
|
-
transport failures
|
|
85
|
+
transport failures, never your `api_id`, phone numbers, or message text.
|
|
119
86
|
|
|
120
87
|
## Sending messages
|
|
121
88
|
|
|
@@ -128,7 +95,7 @@ client.deliver("79991234567", "Hi there")
|
|
|
128
95
|
# 2. Same text to many numbers (Array)
|
|
129
96
|
client.deliver(["79991234567", "79991234568"], "Hi everyone")
|
|
130
97
|
|
|
131
|
-
# 3. A different text per number (Hash
|
|
98
|
+
# 3. A different text per number (Hash, with no separate text argument).
|
|
132
99
|
# Use braces so Ruby treats it as a positional Hash, not keyword arguments.
|
|
133
100
|
client.deliver({
|
|
134
101
|
"79991234567" => "Hi Alice",
|
|
@@ -143,7 +110,7 @@ client.deliver(
|
|
|
143
110
|
"79991234567", "Hi",
|
|
144
111
|
from: "MyCompany", # approved sender name
|
|
145
112
|
time: Time.now.to_i + 3600, # scheduled send (UNIX time, up to 2 months ahead)
|
|
146
|
-
ttl: 60, # message lifetime in minutes (1
|
|
113
|
+
ttl: 60, # message lifetime in minutes (1 to 1440)
|
|
147
114
|
daytime: true, # defer night-time sends to the recipient's daytime
|
|
148
115
|
translit: true, # transliterate Cyrillic to Latin
|
|
149
116
|
test: true, # test mode for this call (overrides the client default)
|
|
@@ -174,6 +141,8 @@ result.failed # => [SmsRu::Sms, ...] rejected recipients
|
|
|
174
141
|
|
|
175
142
|
## Cost and status
|
|
176
143
|
|
|
144
|
+
Price a message before sending, and read the delivery state afterwards:
|
|
145
|
+
|
|
177
146
|
```ruby
|
|
178
147
|
# Price a message before sending (text is optional; omit it for the price of 1 SMS)
|
|
179
148
|
cost = client.cost("79991234567", "How much?")
|
|
@@ -185,15 +154,15 @@ cost.ok? # => true only if every recipient was priced
|
|
|
185
154
|
cost.failed # => [SmsRu::CostItem, ...] recipients that errored
|
|
186
155
|
cost.failed.first.error_code # => 207
|
|
187
156
|
|
|
188
|
-
# Delivery status
|
|
157
|
+
# Delivery status takes one id or an Array of ids
|
|
189
158
|
status = client.status("000000-10000000")
|
|
190
159
|
status.status_code # => 103 (the delivery state code)
|
|
191
160
|
status.status_text # => "Сообщение доставлено"
|
|
192
161
|
|
|
193
162
|
# State predicates instead of memorizing codes:
|
|
194
163
|
status.delivered? # => true (code 103)
|
|
195
|
-
status.pending? # => false (codes 100
|
|
196
|
-
status.failed? # => false (codes 104
|
|
164
|
+
status.pending? # => false (codes 100 to 102, still in transit)
|
|
165
|
+
status.failed? # => false (codes 104 to 108, 150)
|
|
197
166
|
status.found? # => true (false only when the id is unknown, code -1)
|
|
198
167
|
|
|
199
168
|
statuses = client.status(["000000-10000000", "000000-10000001"]) # => [SmsRu::Status, ...]
|
|
@@ -204,15 +173,15 @@ Every code has a named constant under `SmsRu::Statuses` (e.g.
|
|
|
204
173
|
predicates don't cover. The same predicates are available on
|
|
205
174
|
`SmsRu::Events::SmsStatus` from webhook payloads.
|
|
206
175
|
|
|
207
|
-
> **Outcome
|
|
176
|
+
> **Outcome and delivery state are two ideas with two names.** `ok?` (with
|
|
208
177
|
> `error_code`/`error_text` on a rejected `Sms`/`CostItem`) answers *did the
|
|
209
178
|
> request succeed for this recipient*. `status_code` (with
|
|
210
|
-
> `delivered?`/`pending?`/`failed?`) answers *where the message is in delivery
|
|
179
|
+
> `delivered?`/`pending?`/`failed?`) answers *where the message is in delivery*,
|
|
211
180
|
> and only `Status` and webhook events carry it.
|
|
212
181
|
|
|
213
182
|
## Verify by phone call
|
|
214
183
|
|
|
215
|
-
Two ways to verify a user by phone call
|
|
184
|
+
Two ways to verify a user by phone call, with no SMS required.
|
|
216
185
|
|
|
217
186
|
**Outbound (flash call).** SMS.ru calls the user; the last 4 digits of the
|
|
218
187
|
calling number are the code. You receive the expected `code` to compare against
|
|
@@ -220,7 +189,7 @@ what the user enters:
|
|
|
220
189
|
|
|
221
190
|
```ruby
|
|
222
191
|
call = client.call("79991234567")
|
|
223
|
-
call.code # => "1435"
|
|
192
|
+
call.code # => "1435", the last 4 digits the user will see
|
|
224
193
|
call.call_id # => "000000-10000000"
|
|
225
194
|
```
|
|
226
195
|
|
|
@@ -229,7 +198,7 @@ call (free for the caller) and marks the check confirmed:
|
|
|
229
198
|
|
|
230
199
|
```ruby
|
|
231
200
|
check = client.callcheck.add("79991234567")
|
|
232
|
-
check.call_phone_pretty # => "+7 (800) 500-8275"
|
|
201
|
+
check.call_phone_pretty # => "+7 (800) 500-8275", show this to the user
|
|
233
202
|
|
|
234
203
|
# Poll until the user has called (or receive it via a callback/webhook):
|
|
235
204
|
client.callcheck.status(check.check_id).confirmed? # => true
|
|
@@ -286,9 +255,9 @@ In your webhook handler, verify the signature, parse the payload, and
|
|
|
286
255
|
acknowledge it by replying with the string `"100"`:
|
|
287
256
|
|
|
288
257
|
```ruby
|
|
289
|
-
# In Rails, params[:data] is ActionController::Parameters
|
|
290
|
-
# it with .to_unsafe_h first, or the numeric-key ordering the signature
|
|
291
|
-
# on is skipped and the check below rejects the payload. The payload is
|
|
258
|
+
# In Rails, params[:data] is ActionController::Parameters rather than a Hash.
|
|
259
|
+
# Convert it with .to_unsafe_h first, or the numeric-key ordering the signature
|
|
260
|
+
# depends on is skipped and the check below rejects the payload. The payload is
|
|
292
261
|
# signature-verified, so to_unsafe_h is safe here (.to_h would drop keys).
|
|
293
262
|
# In bare Rack params["data"] is already a Hash; pass it as-is.
|
|
294
263
|
data = params[:data].to_unsafe_h
|
|
@@ -328,6 +297,8 @@ SmsRu::Error # base class
|
|
|
328
297
|
└─ SmsRu::InsufficientFundsError # not enough money (code 201)
|
|
329
298
|
```
|
|
330
299
|
|
|
300
|
+
Rescue whichever level of that hierarchy your code needs, around any call.
|
|
301
|
+
|
|
331
302
|
```ruby
|
|
332
303
|
begin
|
|
333
304
|
client.deliver("79991234567", "Hi")
|
|
@@ -342,11 +313,14 @@ rescue SmsRu::ConnectionError => e
|
|
|
342
313
|
end
|
|
343
314
|
```
|
|
344
315
|
|
|
345
|
-
Note that per-recipient failures in a bulk `deliver` are **not** raised
|
|
316
|
+
Note that per-recipient failures in a bulk `deliver` are **not** raised. They are
|
|
346
317
|
reported on each `SmsRu::Sms` in `result.messages` (see above).
|
|
347
318
|
|
|
348
319
|
## Development
|
|
349
320
|
|
|
321
|
+
Ruby 3.3 or newer is required. CI runs against `ruby-head`, `4.0`, `3.4`, and
|
|
322
|
+
`3.3`.
|
|
323
|
+
|
|
350
324
|
```sh
|
|
351
325
|
bin/setup # install dependencies
|
|
352
326
|
bundle exec rake # run RuboCop, validate RBS signatures, and the test suite
|
|
@@ -361,12 +335,12 @@ diagnostics (no implicit `untyped`, no unannotated collections) at **100% type
|
|
|
361
335
|
coverage**, gated in CI. Loosely-typed JSON from SMS.ru (which returns, say,
|
|
362
336
|
`total_limit` as the string `"10"`) is normalized into the declared types at the
|
|
363
337
|
parse boundary, and `rbs:test` checks that the values flowing through the suite
|
|
364
|
-
actually match `sig/` at runtime
|
|
338
|
+
actually match `sig/` at runtime, so the types cannot drift from the code.
|
|
365
339
|
|
|
366
340
|
## Recording test cassettes
|
|
367
341
|
|
|
368
342
|
End-to-end tests replay real SMS.ru responses recorded with [VCR](https://github.com/vcr/vcr).
|
|
369
|
-
The cassettes are not committed with secrets
|
|
343
|
+
The cassettes are not committed with secrets: your `api_id` is filtered out. To
|
|
370
344
|
record them once against your own account (message sends use `test=1`, so they are
|
|
371
345
|
free):
|
|
372
346
|
|
|
@@ -378,6 +352,27 @@ This writes `test/cassettes/*.yml`. Commit them, then `COVERAGE=true bundle exec
|
|
|
378
352
|
runs fully offline at 100% coverage. Before cassettes are recorded, the end-to-end
|
|
379
353
|
tests are skipped (the unit and transport tests still run).
|
|
380
354
|
|
|
381
|
-
##
|
|
382
|
-
|
|
383
|
-
|
|
355
|
+
## Help and project status
|
|
356
|
+
|
|
357
|
+
Ask a question or report a defect in the
|
|
358
|
+
[issue tracker](https://github.com/svyatov/smsru-ruby/issues). Both go to the same
|
|
359
|
+
place. For a security vulnerability, follow
|
|
360
|
+
[SECURITY.md](https://github.com/svyatov/smsru-ruby/blob/main/SECURITY.md) instead
|
|
361
|
+
of opening an issue.
|
|
362
|
+
|
|
363
|
+
Leonid Svyatov maintains this gem alone. He reads every issue, fixes defects in the
|
|
364
|
+
SMS.ru API coverage, and keeps the gem running on supported Ruby versions. Feature
|
|
365
|
+
work depends on the time he has.
|
|
366
|
+
[CONTRIBUTING.md](https://github.com/svyatov/smsru-ruby/blob/main/CONTRIBUTING.md#maintenance)
|
|
367
|
+
records who merges and releases.
|
|
368
|
+
|
|
369
|
+
## Links
|
|
370
|
+
|
|
371
|
+
- [CHANGELOG.md](https://github.com/svyatov/smsru-ruby/blob/main/CHANGELOG.md) records every released
|
|
372
|
+
change.
|
|
373
|
+
- [CONTRIBUTING.md](https://github.com/svyatov/smsru-ruby/blob/main/CONTRIBUTING.md) covers setup,
|
|
374
|
+
tests, and the pull request process.
|
|
375
|
+
- [SECURITY.md](https://github.com/svyatov/smsru-ruby/blob/main/SECURITY.md) explains how to report a
|
|
376
|
+
vulnerability privately.
|
|
377
|
+
- [API documentation](https://rubydoc.info/gems/smsru-ruby) is generated from the source with YARD.
|
|
378
|
+
- [LICENSE](https://github.com/svyatov/smsru-ruby/blob/main/LICENSE) is the MIT License.
|
data/lib/sms_ru/client.rb
CHANGED
|
@@ -20,7 +20,7 @@ class SmsRu
|
|
|
20
20
|
# @param retries [Integer] retry attempts on transport failure (0 disables; PHP default is 5)
|
|
21
21
|
# @param from [String, nil] default sender name for #deliver (overridable per call)
|
|
22
22
|
# @param logger [Logger, nil] optional logger; logs the request path and transport
|
|
23
|
-
# failures only
|
|
23
|
+
# failures only, never the api_id, phone numbers, or message text
|
|
24
24
|
def initialize(api_id, timeout: 30, test: false, retries: 5, from: nil, logger: nil)
|
|
25
25
|
@api_id = api_id
|
|
26
26
|
@timeout = timeout
|
|
@@ -39,7 +39,7 @@ class SmsRu
|
|
|
39
39
|
# forms, must be omitted for the Hash form
|
|
40
40
|
# @param from [String, nil] an approved sender name
|
|
41
41
|
# @param time [Integer, nil] schedule the send at this UNIX timestamp
|
|
42
|
-
# @param ttl [Integer, nil] message lifetime in minutes (1
|
|
42
|
+
# @param ttl [Integer, nil] message lifetime in minutes (1 to 1440); undelivered
|
|
43
43
|
# messages are discarded after this period
|
|
44
44
|
# @param daytime [Boolean] when true, defer night-time sends to the recipient's daytime
|
|
45
45
|
# @param translit [Boolean] transliterate Cyrillic to Latin
|
data/lib/sms_ru/coerce.rb
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
class SmsRu
|
|
4
4
|
# Normalizes loosely-typed SMS.ru JSON values into the types the result
|
|
5
|
-
# objects declare. SMS.ru is inconsistent on the wire
|
|
5
|
+
# objects declare. SMS.ru is inconsistent on the wire: `/my/limit` returns
|
|
6
6
|
# `total_limit` as the string `"10"` but `used_today` as the number `0`, and
|
|
7
|
-
# some counters arrive as `null
|
|
7
|
+
# some counters arrive as `null`. Each field is coerced here rather than
|
|
8
8
|
# trusted as-is.
|
|
9
9
|
#
|
|
10
10
|
# Each type has two helpers: the `?` variant returns nil for a
|
|
11
11
|
# missing/blank/unparseable value (for the nullable fields), while the plain
|
|
12
12
|
# variant falls back to a default (`""`/`0`/`0.0`, overridable) for the fields
|
|
13
|
-
# the API always populates
|
|
13
|
+
# the API always populates, so call sites declare their nullability by name.
|
|
14
14
|
#
|
|
15
15
|
# @api private
|
|
16
16
|
module Coerce
|
data/lib/sms_ru/data.rb
CHANGED
|
@@ -155,7 +155,7 @@ class SmsRu
|
|
|
155
155
|
end
|
|
156
156
|
|
|
157
157
|
# Result of SmsRu#call (flash call). `code` is the last 4 digits of the number
|
|
158
|
-
# that calls the user
|
|
158
|
+
# that calls the user, which is what they read off the incoming call and enter.
|
|
159
159
|
#
|
|
160
160
|
# @!attribute [r] code
|
|
161
161
|
# @return [String] the 4-digit code (the calling number's last 4 digits)
|
|
@@ -212,7 +212,7 @@ class SmsRu
|
|
|
212
212
|
def available_today = total_free - used_today
|
|
213
213
|
end
|
|
214
214
|
|
|
215
|
-
# Result of SmsRu::CallCheck#add
|
|
215
|
+
# Result of SmsRu::CallCheck#add: the number the user must call to authorize.
|
|
216
216
|
#
|
|
217
217
|
# @!attribute [r] check_id
|
|
218
218
|
# @return [String] the check id to poll with SmsRu::CallCheck#status
|
|
@@ -235,7 +235,7 @@ class SmsRu
|
|
|
235
235
|
end
|
|
236
236
|
end
|
|
237
237
|
|
|
238
|
-
# Result of SmsRu::CallCheck#status
|
|
238
|
+
# Result of SmsRu::CallCheck#status: whether the authorizing call has arrived.
|
|
239
239
|
#
|
|
240
240
|
# @!attribute [r] status_code
|
|
241
241
|
# @return [Integer] the check status code (401 once confirmed)
|
data/lib/sms_ru/statuses.rb
CHANGED
|
@@ -41,10 +41,10 @@ class SmsRu
|
|
|
41
41
|
# @return [Boolean] true once the message reached the handset (code 103)
|
|
42
42
|
def delivered? = status_code == Statuses::DELIVERED
|
|
43
43
|
|
|
44
|
-
# @return [Boolean] true while the message is still in transit (codes 100
|
|
44
|
+
# @return [Boolean] true while the message is still in transit (codes 100 to 102)
|
|
45
45
|
def pending? = !status_code.nil? && Statuses::PENDING.include?(status_code)
|
|
46
46
|
|
|
47
|
-
# @return [Boolean] true when the message will not be delivered (codes 104
|
|
47
|
+
# @return [Boolean] true when the message will not be delivered (codes 104 to 108, and 150)
|
|
48
48
|
def failed? = !status_code.nil? && Statuses::FAILED.include?(status_code)
|
|
49
49
|
end
|
|
50
50
|
end
|
data/lib/sms_ru/version.rb
CHANGED
data/lib/sms_ru/webhook.rb
CHANGED
|
@@ -8,14 +8,14 @@ class SmsRu
|
|
|
8
8
|
#
|
|
9
9
|
# The `data` param arrives as a Hash in bare Rack and an Array in PHP-style
|
|
10
10
|
# clients. In Rails it is an `ActionController::Parameters`, which is **not** a
|
|
11
|
-
# Hash
|
|
11
|
+
# Hash. Convert it with `.to_unsafe_h` first, or the numeric-key ordering the
|
|
12
12
|
# signature depends on is skipped and {valid?} rejects the payload. The
|
|
13
13
|
# payload is signature-verified, so `to_unsafe_h` is safe here (`.to_h` would
|
|
14
14
|
# drop unpermitted keys).
|
|
15
15
|
#
|
|
16
|
-
# {parse} returns one typed event per record
|
|
16
|
+
# {parse} returns one typed event per record: a {SmsRu::Events::SmsStatus},
|
|
17
17
|
# {SmsRu::Events::CallcheckStatus}, {SmsRu::Events::Test}, or
|
|
18
|
-
# {SmsRu::Events::Unknown}
|
|
18
|
+
# {SmsRu::Events::Unknown}. A case match handles them best:
|
|
19
19
|
#
|
|
20
20
|
# data = params[:data].to_unsafe_h # Rails; pass params["data"] as-is in bare Rack
|
|
21
21
|
# return head(:forbidden) unless SmsRu::Webhook.valid?(data, params[:hash], api_id)
|
data/smsru-ruby.gemspec
CHANGED
|
@@ -8,18 +8,18 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.authors = ["Leonid Svyatov"]
|
|
9
9
|
spec.email = ["leonid@svyatov.com"]
|
|
10
10
|
|
|
11
|
-
spec.summary = "
|
|
12
|
-
spec.description = "A
|
|
11
|
+
spec.summary = "Ruby client for the SMS.ru HTTP API: send SMS, check delivery, verify users by phone call."
|
|
12
|
+
spec.description = "A Ruby client for the SMS.ru HTTP API. Send single or bulk SMS, " \
|
|
13
13
|
"schedule delivery, check cost and delivery status, verify users by phone call, inspect " \
|
|
14
14
|
"balance/limits/senders, manage the stoplist, and register delivery callbacks."
|
|
15
15
|
spec.homepage = "https://github.com/svyatov/smsru-ruby"
|
|
16
16
|
spec.license = "MIT"
|
|
17
17
|
|
|
18
|
-
spec.required_ruby_version = ">= 3.
|
|
18
|
+
spec.required_ruby_version = ">= 3.3.0"
|
|
19
19
|
|
|
20
20
|
spec.require_paths = ["lib"]
|
|
21
21
|
spec.files = Dir["lib/**/*.rb"] + Dir["sig/**/*"] +
|
|
22
|
-
%w[.yardopts CHANGELOG.md LICENSE
|
|
22
|
+
%w[.yardopts CHANGELOG.md LICENSE README.md smsru-ruby.gemspec]
|
|
23
23
|
|
|
24
24
|
spec.metadata["rubygems_mfa_required"] = "true"
|
|
25
25
|
spec.metadata["documentation_uri"] = "https://rubydoc.info/gems/smsru-ruby"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smsru-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 3.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Leonid Svyatov
|
|
@@ -9,10 +9,9 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
|
-
description: A
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
callbacks.
|
|
12
|
+
description: A Ruby client for the SMS.ru HTTP API. Send single or bulk SMS, schedule
|
|
13
|
+
delivery, check cost and delivery status, verify users by phone call, inspect balance/limits/senders,
|
|
14
|
+
manage the stoplist, and register delivery callbacks.
|
|
16
15
|
email:
|
|
17
16
|
- leonid@svyatov.com
|
|
18
17
|
executables: []
|
|
@@ -21,7 +20,7 @@ extra_rdoc_files: []
|
|
|
21
20
|
files:
|
|
22
21
|
- ".yardopts"
|
|
23
22
|
- CHANGELOG.md
|
|
24
|
-
- LICENSE
|
|
23
|
+
- LICENSE
|
|
25
24
|
- README.md
|
|
26
25
|
- lib/sms_ru/auth.rb
|
|
27
26
|
- lib/sms_ru/call_check.rb
|
|
@@ -68,14 +67,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
68
67
|
requirements:
|
|
69
68
|
- - ">="
|
|
70
69
|
- !ruby/object:Gem::Version
|
|
71
|
-
version: 3.
|
|
70
|
+
version: 3.3.0
|
|
72
71
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
73
72
|
requirements:
|
|
74
73
|
- - ">="
|
|
75
74
|
- !ruby/object:Gem::Version
|
|
76
75
|
version: '0'
|
|
77
76
|
requirements: []
|
|
78
|
-
rubygems_version:
|
|
77
|
+
rubygems_version: 3.6.9
|
|
79
78
|
specification_version: 4
|
|
80
|
-
summary:
|
|
79
|
+
summary: 'Ruby client for the SMS.ru HTTP API: send SMS, check delivery, verify users
|
|
80
|
+
by phone call.'
|
|
81
81
|
test_files: []
|
/data/{LICENSE.txt → LICENSE}
RENAMED
|
File without changes
|