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 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