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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c7152ee4bb497bc839d159e8cdc7bb9b9b5735179a8c5ced0176924acd53727
4
- data.tar.gz: 9dfa498a10e6b68c6115ed42ff4a6b678a18b4b31ad2532f1092095d9efe9a60
3
+ metadata.gz: fb76209647bdda0c204159de74aeb9afd98cb01b6d8979ae9857407e76ff61b1
4
+ data.tar.gz: 6bcea2c04668198bcbf07babcfec333e90e1cb920a06eb9cb371d2a291f1ca66
5
5
  SHA512:
6
- metadata.gz: fb942ccb1bfab86c399c1c0567885d8ca0b43ca54f3e832d6316d6ad2838beb7d1ade363f537247aff90aa8090886a78f2ac0187c9e82c077ade0944bb4e5497
7
- data.tar.gz: 6ca91958e3d50371b7a48c7679b6a6285f6ff9d8389e2e2a0605e02f6eea85359e2b873366351d762fe48bad1bf288c3f9d94244fa8c0ac5a09a1c85230ae5de
6
+ metadata.gz: b21cecdb44421ffe495e0963b9928dedab5e8740680f94937b85eceb1de256bdef23d967e8012e46c124f3a24f27f3fb8eedd541391964ec6dcdd83c21af7300
7
+ data.tar.gz: a693db49eac9e534a6be8c3132804376783a42c56ae532782cd6459f9c516c6f4610ff234d4a9cb522fe30019c64724c042a649e43a475c6c10bbe7e65baf27e
data/.gitignore CHANGED
@@ -1,3 +1,5 @@
1
+ .DS_Storage
2
+ .rspec_status
1
3
  /.bundle/
2
4
  /.yardoc
3
5
  /_yardoc/
@@ -6,6 +8,3 @@
6
8
  /pkg/
7
9
  /spec/reports/
8
10
  /tmp/
9
-
10
- # rspec failure tracking
11
- .rspec_status
data/.yardopts ADDED
@@ -0,0 +1,8 @@
1
+ --private
2
+ --protected
3
+ -m markdown
4
+ -M redcarpet
5
+ -
6
+ lib/**/*.rb
7
+ CHANGELOG.md
8
+ README.md
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
- ## [Unreleased]
1
+ # Changelog
2
2
 
3
- ## [0.1.0] - 2021-05-06
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
@@ -5,5 +5,6 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in gemspec
6
6
  gemspec
7
7
 
8
+ gem "pry"
8
9
  gem "rake", "~> 13.0"
9
10
  gem "rspec", "~> 3.0"
data/Gemfile.lock CHANGED
@@ -1,32 +1,17 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sms-pilot-api-v1 (0.1.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
- addressable (2.7.0)
11
- public_suffix (>= 2.0.2, < 5.0)
9
+ coderay (1.1.3)
12
10
  diff-lcs (1.4.4)
13
- domain_name (0.5.20190701)
14
- unf (>= 0.0.5, < 1.0.0)
15
- ffi (1.15.0)
16
- ffi-compiler (1.0.1)
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.11
40
+ 2.2.17
data/README.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # SmsPilot API v1 client
2
2
 
3
- Обёртка отправки GET-запроса на API endpoint сервиса SMS Pilot (API v1) для удобства доступа к ошибкам, статусам, цене SMS и т. п.
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&amp;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
- ## Usage
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
- client = SmsPilot::Client.new(api_key: "XXXXXXXXXXXXYYYYYYYYYYYYZZZZZZZZXXXXXXXXXXXXYYYYYYYYYYYYZZZZZZZZ")
32
- #<SmsPilot::Client:0x00007fb1c602d490 @api_key="XXXXX...", @error=nil, @response_status=nil, @response_headers=nil, @response_body=nil, @response_data={}, @url=nil>
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 # => nil
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", "Привет, мир!") # => true
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
- - [web version](https://smspilot.ru/apikey.php) — см. вкладку PHP, в остальных ничего нет
149
+ - [Web version](https://smspilot.ru/apikey.php) — см. вкладку PHP, в остальных ничего нет
124
150
  - [PDF version](https://smspilot.ru/download/SMSPilotRu-HTTP-v1.9.19.pdf) — тут намного подробнее
125
- - [Коды ошибок](https://smspilot.ru/apikey.php#err)
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
- # require "pry"
12
- # Pry.start
13
-
14
- require "irb"
15
- IRB.start(__FILE__)
11
+ require "pry"
12
+ Pry.start
data/lib/sms_pilot.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Module that for now encompasses only API client
4
+ #
5
+ module SmsPilot
6
+ end
7
+
3
8
  require_relative "sms_pilot/version"
4
9
  require_relative "sms_pilot/errors"
5
10
  require_relative "sms_pilot/client"
6
-
@@ -1,146 +1,461 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "http"
3
+ require "json"
4
+ require "net/https"
4
5
  require "uri"
5
6
 
6
7
  module SmsPilot
7
8
 
8
- API_ENDPOINT = "https://smspilot.ru/api.php".freeze
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
- @api_key = api_key
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 = nil
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
- @phone = normalize_phone(phone)
43
- @url = build_url(@phone, text)
113
+ # @!group Main
44
114
 
45
- response = HTTP.timeout(connect: 15, read: 30).accept(:json).get(@url)
46
- @response_status = response.status.code
47
- @response_headers = response.headers.to_h
48
- @response_body = response.body.to_s
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
- unless response.status.success?
51
- @error = "HTTP request failed with code #{response.status.code}"
52
- return false
53
- end
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
- @response_data = JSON.parse @response_body
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
- return @error = "#{error_description} (код ошибки: #{error_code})" if rejected?
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 HTTP::Error => error
64
- @error = error.message
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
- @response_data["balance"]&.to_f if sms_sent?
169
+ response_data["balance"]&.to_f if sms_sent?
73
170
  end
74
171
 
75
172
 
76
- # Коды ошибок: https://smspilot.ru/apikey.php#err
77
- # Расшифровка ошибки пишется в @error
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
- # Коды ошибок: https://smspilot.ru/apikey.php#err
85
- # Расшифровка ошибки пишется в @error
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
- @response_data.dig("error", "description_ru") if rejected?
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
- # HTTP запрос удался, но API отказался отправлять SMS
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
- @response_data["error"].is_a? Hash
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
- # API сообщает, что мы заблокированы
257
+ # Did the API block you
100
258
  #
101
- # 105 из-за низкого баланса
102
- # 106 за спам/ошибки
103
- # 107 за недостоверные учетные данные / недоступна эл. почта / проблемы с телефоном
104
- # 122 спорная ситуация
259
+ # Error code | Description
260
+ # :---|:------------------
261
+ # 105 | из-за низкого баланса
262
+ # 106 | за спам/ошибки
263
+ # 107 | за недостоверные учетные данные / недоступна эл. почта / проблемы с телефоном
264
+ # 122 | спорная ситуация
105
265
  #
106
- # Расшифровка ошибки пишется в @error
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
- # Цена отправленной только что SMS
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
- @response_data["cost"] if sms_sent?
284
+ response_data["cost"]&.to_f if sms_sent?
116
285
  end
117
286
 
118
287
 
119
- # API успешно отправил SMS
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
- @response_data["send"] != nil
299
+ response_data["send"] != nil
122
300
  end
123
301
 
124
302
 
125
- # Статус доставки SMS
126
- # https://smspilot.ru/apikey.php#status
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
- private
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
- def build_url(phone, text)
136
- URI.parse(API_ENDPOINT).tap do |url|
137
- url.query = URI.encode_www_form({ apikey: @api_key, format: :json, send: text, to: phone })
138
- end.to_s
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
- def normalize_phone(phone)
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
@@ -1,5 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SmsPilot
4
- class Error < StandardError; end
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
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SmsPilot
4
- VERSION = "0.0.3"
4
+
5
+ # Gem version
6
+ VERSION = "0.0.8"
7
+
5
8
  end
@@ -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/sms-pilot-api-v1"
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
- "changelog_uri" => "#{spec.homepage}/blob/master/Changelog.md",
19
- "documentation_uri" => "#{spec.homepage}#usage",
20
- "homepage_uri" => spec.homepage,
21
- "source_code_uri" => spec.homepage
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 = "bin"
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.add_runtime_dependency "http", "~> 4"
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.3
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergey Pedan
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-06 00:00:00.000000000 Z
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/Changelog.md
55
- documentation_uri: https://github.com/sergeypedan/sms-pilot-api-v1#usage
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.5
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
Binary file