sms-pilot-api-v1 0.0.3 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/sms-pilot-api-v1.svg)](https://badge.fury.io/rb/sms-pilot-api-v1)
|
4
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/42765c3098d5f531a3f7/maintainability)](https://codeclimate.com/github/sergeypedan/sms-pilot-api-v1/maintainability)
|
5
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/42765c3098d5f531a3f7/test_coverage)](https://codeclimate.com/github/sergeypedan/sms-pilot-api-v1/test_coverage)
|
6
|
+
[![Inch CI documentation](https://inch-ci.org/github/sergeypedan/sms-pilot-api-v1.svg?branch=master&style=flat)](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
|