sms-pilot-api-v1 0.0.3 → 0.0.8
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/.gitignore +2 -3
- data/.yardopts +8 -0
- data/CHANGELOG.md +25 -2
- data/Gemfile +1 -0
- data/Gemfile.lock +8 -25
- data/README.md +42 -10
- data/bin/console +2 -5
- data/lib/sms_pilot.rb +5 -1
- data/lib/sms_pilot/client.rb +377 -62
- data/lib/sms_pilot/errors.rb +6 -1
- data/lib/sms_pilot/version.rb +4 -1
- data/sms-pilot-api-v1.gemspec +10 -9
- metadata +8 -22
- data/sms-pilot-api-v1-0.0.2.gem +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb76209647bdda0c204159de74aeb9afd98cb01b6d8979ae9857407e76ff61b1
|
4
|
+
data.tar.gz: 6bcea2c04668198bcbf07babcfec333e90e1cb920a06eb9cb371d2a291f1ca66
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b21cecdb44421ffe495e0963b9928dedab5e8740680f94937b85eceb1de256bdef23d967e8012e46c124f3a24f27f3fb8eedd541391964ec6dcdd83c21af7300
|
7
|
+
data.tar.gz: a693db49eac9e534a6be8c3132804376783a42c56ae532782cd6459f9c516c6f4610ff234d4a9cb522fe30019c64724c042a649e43a475c6c10bbe7e65baf27e
|
data/.gitignore
CHANGED
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,28 @@
|
|
1
|
-
|
1
|
+
# Changelog
|
2
2
|
|
3
|
-
## [0.
|
3
|
+
## [0.0.7] - 9 May 2021
|
4
|
+
|
5
|
+
- Returns original values from validation methods
|
6
|
+
- Offloads parsing response body to a method
|
7
|
+
- Improves documentation
|
8
|
+
- Adds CodeClimate badges
|
9
|
+
- Writes tests for `#initialize` and `#api_key`
|
10
|
+
|
11
|
+
## [0.0.6] - 9 May 2021
|
12
|
+
|
13
|
+
- Corrects cost type
|
14
|
+
- Switches to PRY in console
|
15
|
+
|
16
|
+
## [0.0.5] - 9 May 2021
|
17
|
+
|
18
|
+
- Adds locale support (RU / EN)
|
19
|
+
|
20
|
+
## [0.0.4] - 9 May 2021
|
21
|
+
|
22
|
+
- Drop dependence on HTTP.rb gem
|
23
|
+
- Corrects what `#send_sms` returns (could return String errors instead of Booleans)
|
24
|
+
- Adds extensive [documentation](https://rubydoc.info/github/sergeypedan/sms-pilot-api-v1/master/SmsPilot/Client) via YARD & RubyDoc
|
25
|
+
|
26
|
+
## [0.0.3] - 6 May 2021
|
4
27
|
|
5
28
|
- Initial release
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,32 +1,17 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
sms-pilot-api-v1 (0.
|
5
|
-
http (>= 4.4)
|
4
|
+
sms-pilot-api-v1 (0.0.6)
|
6
5
|
|
7
6
|
GEM
|
8
7
|
remote: https://rubygems.org/
|
9
8
|
specs:
|
10
|
-
|
11
|
-
public_suffix (>= 2.0.2, < 5.0)
|
9
|
+
coderay (1.1.3)
|
12
10
|
diff-lcs (1.4.4)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
ffi (>= 1.0.0)
|
18
|
-
rake
|
19
|
-
http (4.4.1)
|
20
|
-
addressable (~> 2.3)
|
21
|
-
http-cookie (~> 1.0)
|
22
|
-
http-form_data (~> 2.2)
|
23
|
-
http-parser (~> 1.2.0)
|
24
|
-
http-cookie (1.0.3)
|
25
|
-
domain_name (~> 0.5)
|
26
|
-
http-form_data (2.3.0)
|
27
|
-
http-parser (1.2.3)
|
28
|
-
ffi-compiler (>= 1.0, < 2.0)
|
29
|
-
public_suffix (4.0.6)
|
11
|
+
method_source (1.0.0)
|
12
|
+
pry (0.14.1)
|
13
|
+
coderay (~> 1.1)
|
14
|
+
method_source (~> 1.0)
|
30
15
|
rake (13.0.3)
|
31
16
|
rspec (3.10.0)
|
32
17
|
rspec-core (~> 3.10.0)
|
@@ -41,17 +26,15 @@ GEM
|
|
41
26
|
diff-lcs (>= 1.2.0, < 2.0)
|
42
27
|
rspec-support (~> 3.10.0)
|
43
28
|
rspec-support (3.10.2)
|
44
|
-
unf (0.1.4)
|
45
|
-
unf_ext
|
46
|
-
unf_ext (0.0.7.7)
|
47
29
|
|
48
30
|
PLATFORMS
|
49
31
|
x86_64-darwin-17
|
50
32
|
|
51
33
|
DEPENDENCIES
|
34
|
+
pry
|
52
35
|
rake (~> 13.0)
|
53
36
|
rspec (~> 3.0)
|
54
37
|
sms-pilot-api-v1!
|
55
38
|
|
56
39
|
BUNDLED WITH
|
57
|
-
2.2.
|
40
|
+
2.2.17
|
data/README.md
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
# SmsPilot API v1 client
|
2
2
|
|
3
|
-
|
3
|
+
[](https://badge.fury.io/rb/sms-pilot-api-v1)
|
4
|
+
[](https://codeclimate.com/github/sergeypedan/sms-pilot-api-v1/maintainability)
|
5
|
+
[](https://codeclimate.com/github/sergeypedan/sms-pilot-api-v1/test_coverage)
|
6
|
+
[](https://inch-ci.org/github/sergeypedan/sms-pilot-api-v1)
|
7
|
+
|
8
|
+
Simple wrapper around SMS pilot API v1. Version 1 because it returns more data within its standard response.
|
4
9
|
|
5
10
|
## Installation
|
6
11
|
|
@@ -16,7 +21,7 @@ from GitHub:
|
|
16
21
|
gem "sms-pilot-api-v1", git: "https://github.com/sergeypedan/sms-pilot-api-v1.git"
|
17
22
|
```
|
18
23
|
|
19
|
-
##
|
24
|
+
## Playground
|
20
25
|
|
21
26
|
Test sending SMS from console with a test API key (find it at the end of this page):
|
22
27
|
|
@@ -25,24 +30,36 @@ cd $(bundle info sms-pilot-api-v1 --path)
|
|
25
30
|
bin/console
|
26
31
|
```
|
27
32
|
|
33
|
+
|
34
|
+
## Usage
|
35
|
+
|
28
36
|
### Initialize
|
29
37
|
|
30
38
|
```ruby
|
31
|
-
|
32
|
-
|
39
|
+
require "sms_pilot"
|
40
|
+
|
41
|
+
key = "XXXXXXXXXXXXYYYYYYYYYYYYZZZZZZZZXXXXXXXXXXXXYYYYYYYYYYYYZZZZZZZZ"
|
42
|
+
|
43
|
+
client = SmsPilot::Client.new(api_key: key)
|
44
|
+
client = SmsPilot::Client.new(api_key: key, locale: :en) # Available locales are [:en, :ru]
|
33
45
|
```
|
34
46
|
|
47
|
+
Method [documentation](https://rubydoc.info/github/sergeypedan/sms-pilot-api-v1/master/SmsPilot/Client#initialize-instance_method) at RubyDoc.
|
48
|
+
|
35
49
|
### Before sending
|
36
50
|
|
51
|
+
There are a bunch of methods describing the state of affairs:
|
52
|
+
|
37
53
|
```ruby
|
38
54
|
client.api_key # => "YOUR API KEY"
|
39
55
|
client.balance # => nil
|
56
|
+
client.broadcast_id # => nil
|
40
57
|
client.error # => nil
|
41
58
|
client.phone # => nil
|
42
59
|
client.rejected? # => false
|
43
60
|
client.response_body # => nil
|
44
61
|
client.response_data # => {}
|
45
|
-
client.response_headers # =>
|
62
|
+
client.response_headers # => {}
|
46
63
|
client.response_status # => nil
|
47
64
|
client.sender_blocked? # => false
|
48
65
|
client.sms_cost # => nil
|
@@ -51,20 +68,27 @@ client.sms_status # => nil
|
|
51
68
|
client.url # => nil
|
52
69
|
```
|
53
70
|
|
71
|
+
before the request is sent they return obvious nils or empty structures; after the request they are populated with data.
|
72
|
+
|
73
|
+
See [structured documentation](https://rubydoc.info/github/sergeypedan/sms-pilot-api-v1/master/SmsPilot/Client) for those methods at RubyDoc.
|
74
|
+
|
54
75
|
### Sending SMS
|
55
76
|
|
56
77
|
```ruby
|
57
|
-
client.send_sms("+7 (902) 123-45-67", "Привет, мир!")
|
78
|
+
client.send_sms("+7 (902) 123-45-67", "Привет, мир!")
|
79
|
+
# => true
|
58
80
|
```
|
59
81
|
|
60
82
|
Returns result of `sms_sent?`, so it’s either `true` or `false`.
|
61
83
|
|
84
|
+
Method [documentation](https://rubydoc.info/github/sergeypedan/sms-pilot-api-v1/master/SmsPilot/Client#send_sms-instance_method) at RubyDoc.
|
62
85
|
|
63
86
|
### Sending SMS succeeded
|
64
87
|
|
65
88
|
```ruby
|
66
89
|
client.api_key # => "YOUR API KEY"
|
67
90
|
client.balance # => 20006.97
|
91
|
+
client.broadcast_id # => 10000
|
68
92
|
client.error # => nil
|
69
93
|
client.phone # => "79021234567"
|
70
94
|
client.rejected? # => false
|
@@ -84,6 +108,7 @@ client.url # => "https://smspilot.ru/api.php?apikey=1234567890&form
|
|
84
108
|
```ruby
|
85
109
|
client.api_key # => "YOUR API KEY"
|
86
110
|
client.balance # => nil
|
111
|
+
client.broadcast_id # => 10000
|
87
112
|
client.error # => "Неправильный API-ключ (см. настройки API в личном кабинете) (код ошибки: 101)"
|
88
113
|
client.phone # => "79021234567"
|
89
114
|
client.rejected? # => true
|
@@ -103,6 +128,7 @@ client.url # => "https://smspilot.ru/api.php?apikey=1234567890&form
|
|
103
128
|
```ruby
|
104
129
|
client.api_key # => "YOUR API KEY"
|
105
130
|
client.balance # => nil
|
131
|
+
client.broadcast_id # => 10000
|
106
132
|
client.error # => "HTTP request failed with code 404"
|
107
133
|
client.phone # => "79021234567"
|
108
134
|
client.rejected? # => false
|
@@ -120,9 +146,9 @@ client.url # => "https://smspilot.ru/api.php?apikey=1234567890&form
|
|
120
146
|
|
121
147
|
## SMS pilot API docs
|
122
148
|
|
123
|
-
- [
|
149
|
+
- [Web version](https://smspilot.ru/apikey.php) — см. вкладку PHP, в остальных ничего нет
|
124
150
|
- [PDF version](https://smspilot.ru/download/SMSPilotRu-HTTP-v1.9.19.pdf) — тут намного подробнее
|
125
|
-
- [
|
151
|
+
- [API error code](https://smspilot.ru/apikey.php#err)
|
126
152
|
|
127
153
|
|
128
154
|
## Test API key
|
@@ -140,10 +166,11 @@ SMS sent:
|
|
140
166
|
|
141
167
|
```json
|
142
168
|
{
|
169
|
+
"balance": "11908.50",
|
170
|
+
"cost": "1.68",
|
143
171
|
"send": [
|
144
172
|
{ "server_id": "10000", "phone": "79021234567", "price": "1.68", "status": "0" }
|
145
|
-
]
|
146
|
-
"balance": "11908.50", "cost": "1.68"
|
173
|
+
]
|
147
174
|
}
|
148
175
|
```
|
149
176
|
|
@@ -158,3 +185,8 @@ SMS rejected:
|
|
158
185
|
}
|
159
186
|
}
|
160
187
|
```
|
188
|
+
|
189
|
+
|
190
|
+
## Documentation
|
191
|
+
|
192
|
+
See [structured documentation](https://rubydoc.info/github/sergeypedan/sms-pilot-api-v1/master/SmsPilot/Client) at RubyDoc.
|
data/bin/console
CHANGED
@@ -8,8 +8,5 @@ require "sms_pilot"
|
|
8
8
|
# with your gem easier. You can also use a different console, if you like.
|
9
9
|
|
10
10
|
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
require "irb"
|
15
|
-
IRB.start(__FILE__)
|
11
|
+
require "pry"
|
12
|
+
Pry.start
|
data/lib/sms_pilot.rb
CHANGED
data/lib/sms_pilot/client.rb
CHANGED
@@ -1,146 +1,461 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "json"
|
4
|
+
require "net/https"
|
4
5
|
require "uri"
|
5
6
|
|
6
7
|
module SmsPilot
|
7
8
|
|
8
|
-
|
9
|
-
|
9
|
+
# @!attribute [r] api_key
|
10
|
+
# @return [String] your API key
|
11
|
+
# @example
|
12
|
+
# client.api_key #=> "XXX..."
|
13
|
+
#
|
14
|
+
# @!attribute [r] error
|
15
|
+
# Error message returned from the API, combined with the error code
|
16
|
+
# @example
|
17
|
+
# client.error #=> "Пользователь временно блокирован (спорная ситуация) (error code: 122)"
|
18
|
+
# @return [nil, String]
|
19
|
+
# @see #error_code
|
20
|
+
# @see #error_description
|
21
|
+
#
|
22
|
+
# @!attribute [r] locale
|
23
|
+
# Chosen locale (affects only the language of errors)
|
24
|
+
#
|
25
|
+
# @return [Symbol]
|
26
|
+
# @example
|
27
|
+
# client.locale #=> :ru
|
28
|
+
#
|
29
|
+
# @!attribute [r] phone
|
30
|
+
# @return [nil, String] phone after normalization
|
31
|
+
# @example
|
32
|
+
# client.phone #=> "79021234567"
|
33
|
+
#
|
34
|
+
# @!attribute [r] response_body
|
35
|
+
# Response format is JSON (because we request it that way in {#build_uri}).
|
36
|
+
# @example
|
37
|
+
# "{\"send\":[{\"server_id\":\"10000\",\"phone\":\"79021234567\",\"price\":\"1.68\",\"status\":\"0\"}],\"balance\":\"20006.97\",\"cost\":\"1.68\"}"
|
38
|
+
# @return [nil, String] Unmodified HTTP resonse body that API returned
|
39
|
+
# @see #response_data
|
40
|
+
# @see #response_headers
|
41
|
+
# @see #response_status
|
42
|
+
#
|
43
|
+
# @!attribute [r] response_headers
|
44
|
+
# @example
|
45
|
+
# client.response_headers #=>
|
46
|
+
# {
|
47
|
+
# "Access-Control-Allow-Origin" => "*",
|
48
|
+
# "Connection" => "close",
|
49
|
+
# "Content-Length" => "179",
|
50
|
+
# "Content-Type" => "application/json; charset=utf-8",
|
51
|
+
# "Date" => "Thu, 06 May 2021 04:52:58 GMT",
|
52
|
+
# "Server" => "nginx"
|
53
|
+
# }
|
54
|
+
# @return [nil, String] Unmodified HTTP resonse headers that API returned.
|
55
|
+
# @see #response_body
|
56
|
+
# @see #response_data
|
57
|
+
# @see #response_status
|
58
|
+
#
|
59
|
+
# @!attribute [r] response_status
|
60
|
+
# HTTP status of the request to the API. 200 in case of success.
|
61
|
+
# @example
|
62
|
+
# client.response_status #=> 200
|
63
|
+
#
|
64
|
+
# @return [nil, Integer]
|
65
|
+
# @see #response_body
|
66
|
+
# @see #response_data
|
67
|
+
# @see #response_headers
|
68
|
+
#
|
10
69
|
class Client
|
11
70
|
|
71
|
+
# Check current API endpoint URL at {https://smspilot.ru/apikey.php#api1}
|
72
|
+
#
|
73
|
+
API_ENDPOINT = "https://smspilot.ru/api.php".freeze
|
74
|
+
|
75
|
+
# Locale influences only the language of API errors
|
76
|
+
#
|
77
|
+
AVAILABLE_LOCALES = [:ru, :en].freeze
|
78
|
+
|
12
79
|
attr_reader :api_key
|
13
80
|
attr_reader :error
|
81
|
+
attr_reader :locale
|
14
82
|
attr_reader :phone
|
15
83
|
attr_reader :response_body
|
16
|
-
attr_reader :response_data
|
17
84
|
attr_reader :response_headers
|
18
85
|
attr_reader :response_status
|
19
|
-
attr_reader :url
|
20
|
-
|
21
86
|
|
22
|
-
def initialize(api_key:)
|
23
|
-
fail TypeError, "API key must be a String, you pass a #{api_key.class} (#{api_key})" unless api_key.is_a? String
|
24
|
-
fail TypeError, "API key cannot be empty" if api_key == ""
|
25
87
|
|
26
|
-
|
88
|
+
# @param api_key [String]
|
89
|
+
# @param locale [Symbol]
|
90
|
+
#
|
91
|
+
# @return [SmsPilot::Client]
|
92
|
+
# @raise [SmsPilot::InvalidAPIkeyError] if you pass anything but a non-empty String
|
93
|
+
# @raise [SmsPilot::InvalidLocaleError] if you pass anything but <tt>:ru</tt> or <tt>:en</tt>
|
94
|
+
#
|
95
|
+
# @see https://smspilot.ru/my-settings.php Get your production API key here
|
96
|
+
# @see https://smspilot.ru/apikey.php Get your development API key here
|
97
|
+
# @note Current development API key is <tt>"XXXXXXXXXXXXYYYYYYYYYYYYZZZZZZZZXXXXXXXXXXXXYYYYYYYYYYYYZZZZZZZZ"</tt>
|
98
|
+
#
|
99
|
+
# @example
|
100
|
+
# client = SmsPilot::Client.new(api_key: ENV["SMS_PILOT_API_KEY"])
|
101
|
+
# client = SmsPilot::Client.new(api_key: ENV["SMS_PILOT_API_KEY"], locale: :en)
|
102
|
+
#
|
103
|
+
def initialize(api_key:, locale: AVAILABLE_LOCALES[0])
|
104
|
+
@api_key = validate_api_key!(api_key)
|
27
105
|
@error = nil
|
106
|
+
@locale = validate_locale!(locale)
|
28
107
|
@response_status = nil
|
29
|
-
@response_headers =
|
108
|
+
@response_headers = {}
|
30
109
|
@response_body = nil
|
31
|
-
@response_data = {}
|
32
|
-
@url = nil
|
33
110
|
end
|
34
111
|
|
35
|
-
def send_sms(phone, text)
|
36
|
-
fail TypeError, "`phone` must be a String, you pass a #{phone.class} (#{phone})" unless phone.is_a? String
|
37
|
-
fail TypeError, "`text` must be a String, you pass a #{ text.class} (#{ text})" unless text.is_a? String
|
38
|
-
fail ArgumentError, "`phone` cannot be empty" if phone == ""
|
39
|
-
fail ArgumentError, "`text` cannot be empty" if text == ""
|
40
|
-
fail ArgumentError, "`phone` must contain digits" if phone.scan(/\d/).none?
|
41
112
|
|
42
|
-
|
43
|
-
@url = build_url(@phone, text)
|
113
|
+
# @!group Main
|
44
114
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
115
|
+
# Send HTTP request to the API to ask them to transmit your SMS
|
116
|
+
#
|
117
|
+
# @return [Boolean] <tt>true</tt> if the SMS has been sent, <tt>false</tt> otherwise
|
118
|
+
#
|
119
|
+
# @param [String] phone The phone to send the SMS to. In free-form, will be sanitized.
|
120
|
+
# @param [String] message The text of your message.
|
121
|
+
#
|
122
|
+
# @raise [SmsPilot::InvalidPhoneError] if you pass anythig but a String with the <tt>phone</tt> argument
|
123
|
+
# @raise [SmsPilot::InvalidMessageError] if you pass anythig but a String with the <tt>message</tt> argument
|
124
|
+
# @raise [SmsPilot::InvalidMessageError] if your message is empty
|
125
|
+
# @raise [SmsPilot::InvalidPhoneError] if your phone is empty
|
126
|
+
# @raise [SmsPilot::InvalidPhoneError] if your phone has no digits
|
127
|
+
# @raise [URI::InvalidURIError] but is almost impossible, because we provide the URL ourselves
|
128
|
+
#
|
129
|
+
# @example
|
130
|
+
# client.send_sms("+7 (902) 123-45-67", "Привет, мир!") # => true
|
131
|
+
#
|
132
|
+
def send_sms(phone, message)
|
133
|
+
validate_phone! phone
|
134
|
+
validate_message! message
|
49
135
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
136
|
+
@phone = normalize_phone(phone)
|
137
|
+
@uri = build_uri(@phone, message)
|
138
|
+
|
139
|
+
response = persist_response_details Net::HTTP.get_response(@uri)
|
54
140
|
|
55
|
-
@
|
141
|
+
@error = "HTTP request failed with code #{response.code}" and return false unless response.is_a?(Net::HTTPSuccess)
|
142
|
+
@error = "#{error_description} (error code: #{error_code})" and return false if rejected?
|
56
143
|
|
57
|
-
|
58
|
-
return true
|
144
|
+
true
|
59
145
|
|
60
146
|
rescue JSON::ParserError => error
|
61
147
|
@error = "API returned invalid JSON. #{error.message}"
|
148
|
+
return false
|
62
149
|
|
63
|
-
rescue
|
64
|
-
|
65
|
-
|
66
|
-
rescue => error
|
150
|
+
rescue SocketError, EOFError, IOError, SystemCallError,
|
151
|
+
Timeout::Error, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
|
152
|
+
Net::ProtocolError, OpenSSL::SSL::SSLError => error
|
67
153
|
@error = error.message
|
154
|
+
return false
|
68
155
|
end
|
69
156
|
|
157
|
+
# @!endgroup
|
158
|
+
|
70
159
|
|
160
|
+
# @!group State accessors
|
161
|
+
|
162
|
+
# Your current balance, remaining after sending that latest SMS.
|
163
|
+
#
|
164
|
+
# @return [nil, Float] Always <tt>nil</tt> before you send SMS and if the SMS was not sent, always Float after successfull SMS transmission.
|
165
|
+
# @example
|
166
|
+
# client.balance #=> 20215.25
|
167
|
+
#
|
71
168
|
def balance
|
72
|
-
|
169
|
+
response_data["balance"]&.to_f if sms_sent?
|
73
170
|
end
|
74
171
|
|
75
172
|
|
76
|
-
#
|
77
|
-
#
|
173
|
+
# SMS broadcast ID (API documentation calls it “server ID” but it makes no sense, as it is clearly the ID of the transmission, not of a server)
|
174
|
+
#
|
175
|
+
# @example
|
176
|
+
# client.broadcast_id #=> 10000
|
177
|
+
#
|
178
|
+
# @return [nil, Integer]
|
179
|
+
#
|
180
|
+
# @see #response_data
|
181
|
+
#
|
182
|
+
def broadcast_id
|
183
|
+
@response_data.dig("send", 0, "server_id")&.to_i if sms_sent?
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
# Numerical code of the error that occured when sending the SMS. In the range from 0 to 715 (which may change).
|
188
|
+
#
|
189
|
+
# @return [nil, Integer] <tt>nil</tt> is returned before sending SMS. Otherwise <tt>Integer</tt>
|
190
|
+
# @example
|
191
|
+
# client.error_code #=> 122
|
192
|
+
# @see #error
|
193
|
+
# @see #error_description
|
194
|
+
# @see https://smspilot.ru/apikey.php#err Error codes at the API documentation website
|
78
195
|
#
|
79
196
|
def error_code
|
80
197
|
@response_data.dig("error", "code")&.to_i if rejected?
|
81
198
|
end
|
82
199
|
|
83
200
|
|
84
|
-
#
|
85
|
-
#
|
201
|
+
# Description of the error that occured when sending the SMS
|
202
|
+
#
|
203
|
+
# @return [nil, String] <tt>nil</tt> is returned before sending SMS. Otherwise <tt>String</tt>
|
204
|
+
# @example
|
205
|
+
# client.error_description #=> "Пользователь временно блокирован (спорная ситуация)"
|
206
|
+
# @see #error
|
207
|
+
# @see #error_code
|
208
|
+
# @see https://smspilot.ru/apikey.php#err Error codes at the API documentation website
|
86
209
|
#
|
87
210
|
def error_description
|
88
|
-
@
|
211
|
+
method_name = (@locale == :ru) ? "description_ru" : "description"
|
212
|
+
@response_data.dig("error", method_name) if rejected?
|
89
213
|
end
|
90
214
|
|
91
215
|
|
92
|
-
#
|
216
|
+
# Did the API reject your request to send that SMS
|
217
|
+
#
|
218
|
+
# @return [Boolean] <tt>false</tt> is returned before sending SMS. Otherwise the <tt>Boolean</tt> corresponds to whether your request to send an SMS was rejected.
|
219
|
+
# @example
|
220
|
+
# client.rejected? #=> false
|
221
|
+
#
|
93
222
|
def rejected?
|
94
223
|
return false if sms_sent?
|
95
|
-
|
224
|
+
response_data["error"].is_a? Hash
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
# Parses <tt>@response_body</tt> and memoizes result in <tt>@response_data</tt>
|
229
|
+
#
|
230
|
+
# @example
|
231
|
+
# {
|
232
|
+
# "balance" => "20006.97",
|
233
|
+
# "cost" => "1.68",
|
234
|
+
# "send" => [
|
235
|
+
# {
|
236
|
+
# "phone" => "79021234567",
|
237
|
+
# "price" => "1.68",
|
238
|
+
# "server_id" => "10000",
|
239
|
+
# "status" => "0"
|
240
|
+
# }
|
241
|
+
# ]
|
242
|
+
# }
|
243
|
+
#
|
244
|
+
# @return [Hash]
|
245
|
+
# @raise [JSON::ParserError] which is rescued in {#send_sms}
|
246
|
+
#
|
247
|
+
# @see #response_body
|
248
|
+
# @see #response_headers
|
249
|
+
# @see #response_status
|
250
|
+
#
|
251
|
+
def response_data
|
252
|
+
return {} unless @response_body
|
253
|
+
@response_data ||= JSON.parse @response_body
|
96
254
|
end
|
97
255
|
|
98
256
|
|
99
|
-
#
|
257
|
+
# Did the API block you
|
100
258
|
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
259
|
+
# Error code | Description
|
260
|
+
# :---|:------------------
|
261
|
+
# 105 | из-за низкого баланса
|
262
|
+
# 106 | за спам/ошибки
|
263
|
+
# 107 | за недостоверные учетные данные / недоступна эл. почта / проблемы с телефоном
|
264
|
+
# 122 | спорная ситуация
|
105
265
|
#
|
106
|
-
#
|
266
|
+
# @return [Boolean] <tt>nil</tt> is returned before sending SMS. Otherwise the <tt>Boolean</tt> corresponds to whether the API has blocked you.
|
267
|
+
# @example
|
268
|
+
# client.sender_blocked? #=> false
|
269
|
+
# @see #error
|
270
|
+
# @see https://smspilot.ru/apikey.php#err Error codes at the API documentation website
|
107
271
|
#
|
108
272
|
def sender_blocked?
|
109
273
|
[105, 106, 107, 122].include? error_code
|
110
274
|
end
|
111
275
|
|
112
276
|
|
113
|
-
#
|
277
|
+
# The cost of the SMS that has just been sent, in RUB
|
278
|
+
#
|
279
|
+
# @return [nil, Float]
|
280
|
+
# @example
|
281
|
+
# client.sms_cost #=> 2.63
|
282
|
+
#
|
114
283
|
def sms_cost
|
115
|
-
|
284
|
+
response_data["cost"]&.to_f if sms_sent?
|
116
285
|
end
|
117
286
|
|
118
287
|
|
119
|
-
#
|
288
|
+
# Has the SMS transmission been a success.
|
289
|
+
#
|
290
|
+
# @return [Boolean] <tt>nil</tt> is returned before sending SMS. Otherwise the <tt>Boolean</tt> corresponds to the result of SMS transmission.
|
291
|
+
# @see #sms_status
|
292
|
+
# @see #rejected?
|
293
|
+
# @see #error
|
294
|
+
#
|
295
|
+
# @example
|
296
|
+
# client.sms_sent? #=> true
|
297
|
+
#
|
120
298
|
def sms_sent?
|
121
|
-
|
299
|
+
response_data["send"] != nil
|
122
300
|
end
|
123
301
|
|
124
302
|
|
125
|
-
#
|
126
|
-
#
|
303
|
+
# SMS delivery status, as returned by the API
|
304
|
+
#
|
305
|
+
# @return [nil, Integer] <tt>nil</tt> is returned before sending SMS or if the request was rejected. Otherwise an <tt>Integer</tt> in the range of [-2..3] is returned.
|
306
|
+
# @see https://smspilot.ru/apikey.php#status List of available statuses at API documentation website
|
307
|
+
#
|
308
|
+
# Code | Name | Final? | Description
|
309
|
+
# ----:|:--------------|:-------|:-------------
|
310
|
+
# -2 | Ошибка | Да | Ошибка, неправильные параметры запроса
|
311
|
+
# -1 | Не доставлено | Да | Сообщение не доставлено (не в сети, заблокирован, не взял трубку), PING — не в сети, HLR — не обслуживается (заблокирован)
|
312
|
+
# 0 | Новое | Нет | Новое сообщение/запрос, ожидает обработки у нас на сервере
|
313
|
+
# 1 | В очереди | Нет | Сообщение или запрос ожидают отправки на сервере оператора
|
314
|
+
# 2 | Доставлено | Да | Доставлено, звонок совершен, PING — в сети, HLR — обслуживается
|
315
|
+
# 3 | Отложено | Нет | Отложенная отправка, отправка сообщения/запроса запланирована на другое время
|
316
|
+
#
|
317
|
+
# @example
|
318
|
+
# client.sms_status #=> 2
|
127
319
|
#
|
128
320
|
def sms_status
|
129
321
|
@response_data.dig("send", 0, "status")&.to_i if sms_sent?
|
130
322
|
end
|
131
323
|
|
132
324
|
|
133
|
-
|
325
|
+
# URL generated by combining <tt>API_ENDPOINT</tt>, your API key, SMS text & phone
|
326
|
+
#
|
327
|
+
# @example
|
328
|
+
# client.url #=> "https://smspilot.ru/api.php?api_key=XXX&format=json&send=TEXT&to=79021234567"
|
329
|
+
#
|
330
|
+
# @return [nil, String]
|
331
|
+
#
|
332
|
+
def url
|
333
|
+
@uri&.to_s
|
334
|
+
end
|
335
|
+
|
336
|
+
# @!endgroup
|
337
|
+
|
134
338
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
339
|
+
# The URI we will send an HTTP request to
|
340
|
+
# @private
|
341
|
+
#
|
342
|
+
# @example
|
343
|
+
# build_uri("79021234567", "Hello, World!")
|
344
|
+
# #=> #<URI::HTTPS https://smspilot.ru/api.php?apikey=XXX…&format=json&send=Hello%2C+World%21&to=79021234567>
|
345
|
+
#
|
346
|
+
# @return [URI]
|
347
|
+
# @raise [URI::InvalidURIError] but is almost impossible, because we provide the URL ourselves
|
348
|
+
#
|
349
|
+
# @see #api_key
|
350
|
+
# @see #phone
|
351
|
+
# @see #validate_phone!
|
352
|
+
# @see #validate_message!
|
353
|
+
#
|
354
|
+
private def build_uri(phone, text)
|
355
|
+
URI.parse(API_ENDPOINT).tap do |uri|
|
356
|
+
uri.query = URI.encode_www_form({ apikey: @api_key, format: :json, send: text, to: phone })
|
357
|
+
end
|
139
358
|
end
|
140
359
|
|
141
|
-
|
360
|
+
|
361
|
+
# Cleans up your phone from anything but digits. Also replaces 8 to 7 if it is the first digit.
|
362
|
+
#
|
363
|
+
# @private
|
364
|
+
# @param [String] phone
|
365
|
+
# @return [String]
|
366
|
+
#
|
367
|
+
# @example
|
368
|
+
# normalize_phone("8 (902) 123-45-67") #=> 79021234567
|
369
|
+
# normalize_phone("+7-902-123-45-67") #=> 79021234567
|
370
|
+
#
|
371
|
+
private def normalize_phone(phone)
|
142
372
|
phone.gsub(/[^0-9]/, '').sub(/^8/, '7').gsub('+7', '8')
|
143
373
|
end
|
144
374
|
|
375
|
+
|
376
|
+
# Saves response details into instance variables
|
377
|
+
# @private
|
378
|
+
#
|
379
|
+
# @return [response]
|
380
|
+
# @raise [TypeError] unless a Net::HTTPResponse passed
|
381
|
+
#
|
382
|
+
private def persist_response_details(response)
|
383
|
+
fail TypeError, "Net::HTTPResponse expected, you pass a #{response.class}" unless response.is_a? Net::HTTPResponse
|
384
|
+
@response_body = response.body
|
385
|
+
@response_status = response.code.to_i
|
386
|
+
@response_headers = response.each_capitalized.to_h
|
387
|
+
response
|
388
|
+
end
|
389
|
+
|
390
|
+
|
391
|
+
# @!group Validations
|
392
|
+
|
393
|
+
# Validates api_key
|
394
|
+
#
|
395
|
+
# @private
|
396
|
+
# @return [String] the original value passed into the method, only if it was valid
|
397
|
+
# @param [String] api_key
|
398
|
+
#
|
399
|
+
# @raise [SmsPilot::InvalidError] if api_key is not a String
|
400
|
+
# @raise [SmsPilot::InvalidError] if api_key is an empty String
|
401
|
+
#
|
402
|
+
private def validate_api_key!(api_key)
|
403
|
+
fail SmsPilot::InvalidAPIkeyError, "API key must be a String, you pass a #{api_key.class} (#{api_key})" unless api_key.is_a? String
|
404
|
+
fail SmsPilot::InvalidAPIkeyError, "API key cannot be empty" if api_key == ""
|
405
|
+
return api_key
|
406
|
+
end
|
407
|
+
|
408
|
+
|
409
|
+
# Validates locale
|
410
|
+
#
|
411
|
+
# @private
|
412
|
+
# @return [Symbol] the original value passed into the method, only if it was valid
|
413
|
+
# @param [Symbol] locale
|
414
|
+
#
|
415
|
+
# @raise [SmsPilot::InvalidError] if locale is not a Symbol
|
416
|
+
# @raise [SmsPilot::InvalidError] if locale is unrecognized
|
417
|
+
#
|
418
|
+
private def validate_locale!(locale)
|
419
|
+
fail SmsPilot::InvalidLocaleError, "locale must be a Symbol" unless locale.is_a? Symbol
|
420
|
+
fail SmsPilot::InvalidLocaleError, "API does not support locale :#{locale}; choose one of #{AVAILABLE_LOCALES.inspect}" unless AVAILABLE_LOCALES.include? locale
|
421
|
+
return locale
|
422
|
+
end
|
423
|
+
|
424
|
+
|
425
|
+
# Validates message
|
426
|
+
# @private
|
427
|
+
#
|
428
|
+
# @param [String] message
|
429
|
+
# @return [String] the original value passed into the method, only if it was valid
|
430
|
+
#
|
431
|
+
# @raise [SmsPilot::InvalidMessageError] if you pass anythig but a String with the <tt>message</tt> argument
|
432
|
+
# @raise [SmsPilot::InvalidMessageError] if your message is empty
|
433
|
+
#
|
434
|
+
private def validate_message!(message)
|
435
|
+
fail SmsPilot::InvalidMessageError, "SMS message must be a String, you pass a #{ message.class} (#{ message})" unless message.is_a? String
|
436
|
+
fail SmsPilot::InvalidMessageError, "SMS message cannot be empty" if message == ""
|
437
|
+
message
|
438
|
+
end
|
439
|
+
|
440
|
+
|
441
|
+
# Validates phone
|
442
|
+
# @private
|
443
|
+
#
|
444
|
+
# @param [String] phone
|
445
|
+
# @return [String] the original value passed into the method, only if it was valid
|
446
|
+
#
|
447
|
+
# @raise [SmsPilot::InvalidPhoneError] if you pass anythig but a String with the <tt>phone</tt> argument
|
448
|
+
# @raise [SmsPilot::InvalidPhoneError] if your phone is empty
|
449
|
+
# @raise [SmsPilot::InvalidPhoneError] if your phone has no digits
|
450
|
+
#
|
451
|
+
private def validate_phone!(phone)
|
452
|
+
fail SmsPilot::InvalidPhoneError, "phone must be a String, you pass a #{phone.class} (#{phone})" unless phone.is_a? String
|
453
|
+
fail SmsPilot::InvalidPhoneError, "phone cannot be empty" if phone == ""
|
454
|
+
fail SmsPilot::InvalidPhoneError, "phone must contain digits" if phone.scan(/\d/).none?
|
455
|
+
phone
|
456
|
+
end
|
457
|
+
|
458
|
+
# @!endgroup
|
459
|
+
|
145
460
|
end
|
146
461
|
end
|
data/lib/sms_pilot/errors.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SmsPilot
|
4
|
-
|
4
|
+
|
5
|
+
class InvalidAPIkeyError < ArgumentError; end
|
6
|
+
class InvalidMessageError < ArgumentError; end
|
7
|
+
class InvalidPhoneError < ArgumentError; end
|
8
|
+
class InvalidLocaleError < ArgumentError; end
|
9
|
+
|
5
10
|
end
|
data/lib/sms_pilot/version.rb
CHANGED
data/sms-pilot-api-v1.gemspec
CHANGED
@@ -3,33 +3,34 @@
|
|
3
3
|
require_relative "lib/sms_pilot/version"
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "sms-pilot-api-v1"
|
6
7
|
spec.authors = ["Sergey Pedan"]
|
7
8
|
spec.summary = "Simple wrapper around SMS pilot API v1"
|
8
9
|
spec.description = "#{spec.summary}. Version 1 because it returns more data within its standard response"
|
9
10
|
spec.email = ["sergey.pedan@gmail.com"]
|
10
|
-
spec.homepage = "https://github.com/sergeypedan
|
11
|
+
spec.homepage = "https://github.com/sergeypedan/#{spec.name}"
|
11
12
|
spec.license = "MIT"
|
12
|
-
spec.name = "sms-pilot-api-v1"
|
13
13
|
spec.version = SmsPilot::VERSION
|
14
14
|
|
15
15
|
spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
|
16
16
|
|
17
17
|
spec.metadata = {
|
18
|
-
|
19
|
-
"documentation_uri" => "
|
20
|
-
|
21
|
-
|
18
|
+
"changelog_uri" => "#{spec.homepage}/blob/master/CHANGELOG.md",
|
19
|
+
"documentation_uri" => "https://rubydoc.info/github/sergeypedan/#{spec.name}/master/",
|
20
|
+
"homepage_uri" => spec.homepage,
|
21
|
+
"source_code_uri" => spec.homepage
|
22
22
|
}
|
23
23
|
|
24
24
|
# Specify which files should be added to the gem when it is released.
|
25
25
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
26
26
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
27
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
27
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features|pkg|doc)/}) }
|
28
28
|
end
|
29
29
|
|
30
|
-
spec.bindir = "
|
30
|
+
spec.bindir = "exe"
|
31
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
32
|
spec.require_paths = ["lib"]
|
32
33
|
|
33
|
-
spec.
|
34
|
+
# spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
34
35
|
|
35
36
|
end
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sms-pilot-api-v1
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergey Pedan
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-05-
|
12
|
-
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: http
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '4'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '4'
|
11
|
+
date: 2021-05-10 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
27
13
|
description: Simple wrapper around SMS pilot API v1. Version 1 because it returns
|
28
14
|
more data within its standard response
|
29
15
|
email:
|
@@ -34,6 +20,7 @@ extra_rdoc_files: []
|
|
34
20
|
files:
|
35
21
|
- ".gitignore"
|
36
22
|
- ".rspec"
|
23
|
+
- ".yardopts"
|
37
24
|
- CHANGELOG.md
|
38
25
|
- Gemfile
|
39
26
|
- Gemfile.lock
|
@@ -45,14 +32,13 @@ files:
|
|
45
32
|
- lib/sms_pilot/client.rb
|
46
33
|
- lib/sms_pilot/errors.rb
|
47
34
|
- lib/sms_pilot/version.rb
|
48
|
-
- sms-pilot-api-v1-0.0.2.gem
|
49
35
|
- sms-pilot-api-v1.gemspec
|
50
36
|
homepage: https://github.com/sergeypedan/sms-pilot-api-v1
|
51
37
|
licenses:
|
52
38
|
- MIT
|
53
39
|
metadata:
|
54
|
-
changelog_uri: https://github.com/sergeypedan/sms-pilot-api-v1/blob/master/
|
55
|
-
documentation_uri: https://
|
40
|
+
changelog_uri: https://github.com/sergeypedan/sms-pilot-api-v1/blob/master/CHANGELOG.md
|
41
|
+
documentation_uri: https://rubydoc.info/github/sergeypedan/sms-pilot-api-v1/master/
|
56
42
|
homepage_uri: https://github.com/sergeypedan/sms-pilot-api-v1
|
57
43
|
source_code_uri: https://github.com/sergeypedan/sms-pilot-api-v1
|
58
44
|
post_install_message:
|
@@ -70,7 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
56
|
- !ruby/object:Gem::Version
|
71
57
|
version: '0'
|
72
58
|
requirements: []
|
73
|
-
rubygems_version: 3.2.
|
59
|
+
rubygems_version: 3.2.8
|
74
60
|
signing_key:
|
75
61
|
specification_version: 4
|
76
62
|
summary: Simple wrapper around SMS pilot API v1
|
data/sms-pilot-api-v1-0.0.2.gem
DELETED
Binary file
|