saby_vok 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +157 -0
- data/lib/saby_vok/auth.rb +122 -0
- data/lib/saby_vok/client.rb +324 -0
- data/lib/saby_vok/errors.rb +36 -0
- data/lib/saby_vok/http.rb +102 -0
- data/lib/saby_vok/version.rb +5 -0
- data/lib/saby_vok.rb +77 -0
- metadata +98 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e374ddcc4a44ce5055e3c26e18e3d92ca52beaa1756ee4705f913a4ee072f2a5
|
|
4
|
+
data.tar.gz: 5a2ec3eefda6a8c638a580ef30eb5c2d7f3d59f6fd7a6f8e2d81aa398d63ed80
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 46fb949f502e8e50547606e50f52c53845727de8ee5b071314c4502d94b6626713339e82ad4e33aa97410784062dc18c82f6419023553a51fc43e450ab9f55a0
|
|
7
|
+
data.tar.gz: 742120a6bc645d09a0f28b66b2350032571e2b7ce345a18797d33354779195a1726cce27b29279d67e001b42382762ffe0d9943b587ff6f93cbf17bd4120efe8
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Vitalii Dementev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Saby Vok Ruby Client
|
|
2
|
+
|
|
3
|
+
**saby_vok** - Ruby-клиент для API «Всё о компаниях» (СБИС). Библиотека сама выполняет сервисную авторизацию, автоматически обновляет токены при 401/403, проставляет заголовки `X-SBISAccessToken` и `X-SBISSessionId`, а также умеет работать с JSON и бинарными ответами.
|
|
4
|
+
|
|
5
|
+
## Требования
|
|
6
|
+
|
|
7
|
+
- Ruby >= 3.1
|
|
8
|
+
- Действующие `client_id`, `client_secret`, `secret_key` в личном кабинете СБИС
|
|
9
|
+
|
|
10
|
+
## Установка
|
|
11
|
+
|
|
12
|
+
Через Bundler:
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
gem "saby_vok"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
или командой:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bundle add saby_vok
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Без Bundler:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
gem install saby_vok
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
После установки подключите библиотеку:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
require "saby_vok"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Быстрый старт
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
require "saby_vok"
|
|
40
|
+
|
|
41
|
+
client = SabyVok::Client.new(
|
|
42
|
+
ENV.fetch("SABY_CLIENT_ID"),
|
|
43
|
+
ENV.fetch("SABY_CLIENT_SECRET"),
|
|
44
|
+
ENV.fetch("SABY_SECRET_KEY")
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
companies = client.req(inn: "7605016030")
|
|
48
|
+
puts companies.first["company_name"]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Бинарные ответы сохраняйте как есть:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
File.binwrite("logo.png", client.logo(inn: "7605016030"))
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Обработка ошибок
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
begin
|
|
61
|
+
client.req
|
|
62
|
+
rescue SabyVok::ValidationError => e
|
|
63
|
+
warn "wrong params: #{e.message}"
|
|
64
|
+
rescue SabyVok::AuthError => e
|
|
65
|
+
warn "auth failed: #{e.message}"
|
|
66
|
+
rescue SabyVok::HttpError => e
|
|
67
|
+
warn "http #{e.status}: #{e.body}"
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Конфигурация и синглтон
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
SabyVok.configure do |config|
|
|
75
|
+
config.client_id = ENV.fetch("SABY_CLIENT_ID")
|
|
76
|
+
config.client_secret = ENV.fetch("SABY_CLIENT_SECRET")
|
|
77
|
+
config.secret_key = ENV.fetch("SABY_SECRET_KEY")
|
|
78
|
+
config.host = "https://api.sbis.ru/vok/" # опционально
|
|
79
|
+
config.timeout = 10 # секунды
|
|
80
|
+
config.retries = 2
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
client = SabyVok.client
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Нужно обновить настройки - вызовите `SabyVok.reset_client!`, либо создавайте отдельные экземпляры `SabyVok::Client.new(...)` для разных проектов/хостов. Внутри используются мьютексы (`Mutex` и `Mutex_m`), поэтому загрузка токенов и доступ к `SabyVok.client` потокобезопасны.
|
|
87
|
+
|
|
88
|
+
## Rails / Rack проекты
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
# Gemfile
|
|
92
|
+
gem "saby_vok"
|
|
93
|
+
|
|
94
|
+
# config/initializers/saby_vok.rb
|
|
95
|
+
SabyVok.configure do |config|
|
|
96
|
+
creds = Rails.application.credentials.fetch(:saby_vok)
|
|
97
|
+
config.client_id = creds.fetch(:client_id)
|
|
98
|
+
config.client_secret = creds.fetch(:client_secret)
|
|
99
|
+
config.secret_key = creds.fetch(:secret_key)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# anywhere in app
|
|
103
|
+
SabyVok.client.req(inn: params[:inn])
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Поддерживаемые методы
|
|
107
|
+
|
|
108
|
+
JSON:
|
|
109
|
+
|
|
110
|
+
- `req`, `registration_information`, `tenders_info`, `tenders(limit:, page:)`
|
|
111
|
+
- `finance`, `cost_business`, `reliability`, `reliability_blocks`, `market_position`, `creditworthiness`
|
|
112
|
+
- `owners`, `affiliate`, `dirs_history`, `founders_history`
|
|
113
|
+
- `license_stat`, `license_data`, `inspections_stat`, `inspections_data`
|
|
114
|
+
- `statistic_courts`, `courts`, `executive_lists`, `bankruptcy`, `fea`
|
|
115
|
+
- `subscriptions_events`, `subscriptions_contractors`, `subscribe`, `unsubscribe`
|
|
116
|
+
- `contractor_history`, `contacts_official`, `branches`
|
|
117
|
+
- `excerpts_list`, `excerpts_last`, `search`, `client_stat`
|
|
118
|
+
- `trademarks`, `vehicle`, `vehicle_stat`, `pledges`
|
|
119
|
+
|
|
120
|
+
Бинарные:
|
|
121
|
+
|
|
122
|
+
- `logo`, `bankruptcy_file`, `events_file`
|
|
123
|
+
- `sro_file`, `excerpts_file`, `trademarks_image`
|
|
124
|
+
- `pdf_business_report`, `pdf_due_diligence_report`, `pdf_financial_report`, `pdf_signed_excerpt`
|
|
125
|
+
- `xml_egrul_excerpt`, `xml_reporting_excerpt`
|
|
126
|
+
- `pledges_file`, `tenders` документы
|
|
127
|
+
|
|
128
|
+
## Ограничения API
|
|
129
|
+
|
|
130
|
+
- Нужен хотя бы один из параметров: `inn` или `ogrn`.
|
|
131
|
+
- `kpp` разрешён только вместе с `inn`.
|
|
132
|
+
- Массовые запросы: до 20 значений одного типа (ИНН или ОГРН). Пары ИНН+КПП указывайте перед одиночными ИНН.
|
|
133
|
+
- Пагинация: `limit > 0`, `page >= 0`.
|
|
134
|
+
|
|
135
|
+
## Демо-окружение
|
|
136
|
+
|
|
137
|
+
- Базовый URL демо: `https://api.sbis.ru/vok-demo/`.
|
|
138
|
+
- Демо-ИНН: `7605016030`, `7736050003`, `7707049388`, `7814593627`, `7827004484`, `772871281410`, `6382082839`, `7708503727`, `7709464710`.
|
|
139
|
+
- В демо отключены методы, возвращающие файлы.
|
|
140
|
+
|
|
141
|
+
## Разработка
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
bundle install
|
|
145
|
+
rake test
|
|
146
|
+
rubocop
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Пулы и багрепорты приветствуются в [issues](https://github.com/DementevVV/saby_vok/issues).
|
|
150
|
+
|
|
151
|
+
## Лицензия
|
|
152
|
+
|
|
153
|
+
MIT - см. `LICENSE.txt`.
|
|
154
|
+
|
|
155
|
+
## Автор
|
|
156
|
+
|
|
157
|
+
Vitalii Dementev - [@DementevVV](https://github.com/DementevVV)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "mutex_m"
|
|
6
|
+
require "httparty"
|
|
7
|
+
|
|
8
|
+
module SabyVok
|
|
9
|
+
class Auth
|
|
10
|
+
include Mutex_m
|
|
11
|
+
|
|
12
|
+
JSON_HEADERS = { "Content-Type" => "application/json; charset=utf-8" }.freeze
|
|
13
|
+
|
|
14
|
+
# @param credentials [Hash, #to_h] keys :client_id, :client_secret, :secret_key
|
|
15
|
+
# @param oauth_url [String] OAuth endpoint URL
|
|
16
|
+
# @param timeout [Integer] request timeout in seconds
|
|
17
|
+
# @param logger [Logger, nil]
|
|
18
|
+
def initialize(credentials:, oauth_url:, timeout:, logger: nil)
|
|
19
|
+
super() # Mutex_m init
|
|
20
|
+
creds = normalize_credentials(credentials)
|
|
21
|
+
@client_id = creds.fetch(:client_id)
|
|
22
|
+
@client_secret = creds.fetch(:client_secret)
|
|
23
|
+
@secret_key = creds.fetch(:secret_key)
|
|
24
|
+
@oauth_uri = URI(oauth_url)
|
|
25
|
+
@timeout = timeout
|
|
26
|
+
@logger = logger
|
|
27
|
+
@tokens = nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @return [Hash{Symbol=>String}] :access_token and :session_id
|
|
31
|
+
def tokens
|
|
32
|
+
synchronize do
|
|
33
|
+
@tokens ||= obtain_tokens
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @return [Hash{Symbol=>String}] refreshed tokens
|
|
38
|
+
def refresh!
|
|
39
|
+
synchronize do
|
|
40
|
+
@tokens = obtain_tokens
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @return [void]
|
|
45
|
+
def invalidate!
|
|
46
|
+
synchronize do
|
|
47
|
+
@tokens = nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def obtain_tokens
|
|
54
|
+
response = perform_oauth_request
|
|
55
|
+
ensure_success!(response)
|
|
56
|
+
parse_token_payload(response)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def log(message)
|
|
60
|
+
return unless @logger
|
|
61
|
+
|
|
62
|
+
@logger.info("[saby_vok] #{message}")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def perform_oauth_request
|
|
66
|
+
log("Auth: POST #{@oauth_uri}")
|
|
67
|
+
HTTParty.post(@oauth_uri, oauth_request_options)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def ensure_success!(response)
|
|
71
|
+
code = response.code.to_i
|
|
72
|
+
body = response.body
|
|
73
|
+
return if code.between?(200, 299)
|
|
74
|
+
|
|
75
|
+
raise AuthError.new("Auth failed HTTP #{code}", status: code, body: body)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def parse_token_payload(response)
|
|
79
|
+
extract_token_fields(JSON.parse(response.body))
|
|
80
|
+
rescue JSON::ParserError, KeyError => e
|
|
81
|
+
raise AuthError.new(
|
|
82
|
+
"Auth response parsing failed: #{e.message}",
|
|
83
|
+
status: response.code.to_i,
|
|
84
|
+
body: response.body
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def oauth_request_options
|
|
89
|
+
{
|
|
90
|
+
headers: JSON_HEADERS,
|
|
91
|
+
body: oauth_request_body,
|
|
92
|
+
timeout: @timeout
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def oauth_request_body
|
|
97
|
+
credentials_payload.to_json
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def credentials_payload
|
|
101
|
+
{
|
|
102
|
+
app_client_id: @client_id,
|
|
103
|
+
app_secret: @client_secret,
|
|
104
|
+
secret_key: @secret_key
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def extract_token_fields(parsed)
|
|
109
|
+
{
|
|
110
|
+
access_token: parsed.fetch("access_token"),
|
|
111
|
+
session_id: parsed.fetch("sid")
|
|
112
|
+
}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def normalize_credentials(credentials)
|
|
116
|
+
creds_hash = credentials.respond_to?(:to_h) ? credentials.to_h : credentials
|
|
117
|
+
raise ArgumentError, "credentials must be a Hash-like object" unless creds_hash.is_a?(Hash)
|
|
118
|
+
|
|
119
|
+
creds_hash.transform_keys(&:to_sym)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
require_relative "errors"
|
|
5
|
+
require_relative "http"
|
|
6
|
+
require_relative "auth"
|
|
7
|
+
require_relative "version"
|
|
8
|
+
|
|
9
|
+
module SabyVok
|
|
10
|
+
class Client
|
|
11
|
+
DEFAULT_HOST = "https://api.sbis.ru/vok/"
|
|
12
|
+
DEFAULT_OAUTH_URL = "https://api.sbis.ru/oauth/service/"
|
|
13
|
+
DEFAULT_CONFIG = {
|
|
14
|
+
host: DEFAULT_HOST,
|
|
15
|
+
oauth_url: DEFAULT_OAUTH_URL,
|
|
16
|
+
timeout: Http::DEFAULT_TIMEOUT,
|
|
17
|
+
retries: Http::DEFAULT_RETRIES,
|
|
18
|
+
logger: nil
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
# Thin, happy-path client for the VOK API.
|
|
22
|
+
#
|
|
23
|
+
# @example Direct initialization and fetching requisites
|
|
24
|
+
# client = SabyVok::Client.new("id", "secret", "key")
|
|
25
|
+
# data = client.req(inn: "7605016030")
|
|
26
|
+
# puts data.first["company_name"]
|
|
27
|
+
#
|
|
28
|
+
# @example Getting a logo as binary
|
|
29
|
+
# logo_bytes = client.logo(inn: "7605016030")
|
|
30
|
+
# File.binwrite("logo.png", logo_bytes)
|
|
31
|
+
#
|
|
32
|
+
# @overload initialize(client_id, client_secret, secret_key, **options)
|
|
33
|
+
# @param client_id [String]
|
|
34
|
+
# @param client_secret [String]
|
|
35
|
+
# @param secret_key [String]
|
|
36
|
+
# @param options [Hash] optional config (see below)
|
|
37
|
+
# @overload initialize(credentials:, **options)
|
|
38
|
+
# @param credentials [Hash] keys :client_id, :client_secret, :secret_key
|
|
39
|
+
# @param options [Hash]
|
|
40
|
+
# @option options [String] :host (DEFAULT_HOST)
|
|
41
|
+
# @option options [String] :oauth_url (DEFAULT_OAUTH_URL)
|
|
42
|
+
# @option options [Integer] :timeout (Http::DEFAULT_TIMEOUT)
|
|
43
|
+
# @option options [Integer] :retries (Http::DEFAULT_RETRIES)
|
|
44
|
+
# @option options [Logger,nil] :logger
|
|
45
|
+
def initialize(*credential_args, credentials: nil, **options)
|
|
46
|
+
creds = resolve_credentials(credential_args, credentials)
|
|
47
|
+
config = build_config(options)
|
|
48
|
+
|
|
49
|
+
@host = URI(config[:host])
|
|
50
|
+
@oauth_url = config[:oauth_url]
|
|
51
|
+
@logger = config[:logger]
|
|
52
|
+
timeout = config[:timeout]
|
|
53
|
+
retries = config[:retries]
|
|
54
|
+
|
|
55
|
+
@http = Http.new(logger: @logger, timeout: timeout, retries: retries)
|
|
56
|
+
@auth = Auth.new(
|
|
57
|
+
credentials: creds,
|
|
58
|
+
oauth_url: @oauth_url,
|
|
59
|
+
timeout: timeout,
|
|
60
|
+
logger: @logger
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Requisites
|
|
65
|
+
# @return [Array<Hash>, Hash]
|
|
66
|
+
def req(inn: nil, ogrn: nil, kpp: nil)
|
|
67
|
+
ensure_inn_or_ogrn!(inn, ogrn)
|
|
68
|
+
ensure_kpp_valid!(inn, kpp)
|
|
69
|
+
request_json("req", params: { inn: inn, ogrn: ogrn, kpp: kpp })
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @return [String] binary logo data
|
|
73
|
+
def logo(inn: nil, ogrn: nil, kpp: nil)
|
|
74
|
+
ensure_inn_or_ogrn!(inn, ogrn)
|
|
75
|
+
ensure_kpp_valid!(inn, kpp)
|
|
76
|
+
request_binary("logo", params: { inn: inn, ogrn: ogrn, kpp: kpp })
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @return [Array<Hash>, Hash]
|
|
80
|
+
def registration_information(inn: nil, ogrn: nil, kpp: nil)
|
|
81
|
+
ensure_inn_or_ogrn!(inn, ogrn)
|
|
82
|
+
ensure_kpp_valid!(inn, kpp)
|
|
83
|
+
request_json("registration-information", params: { inn: inn, ogrn: ogrn, kpp: kpp })
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Tenders
|
|
87
|
+
# @return [Array<Hash>, Hash]
|
|
88
|
+
def tenders_info(inn: nil, ogrn: nil)
|
|
89
|
+
ensure_inn_or_ogrn!(inn, ogrn)
|
|
90
|
+
request_json("tenders-info", params: { inn: inn, ogrn: ogrn })
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @param limit [Integer]
|
|
94
|
+
# @param page [Integer]
|
|
95
|
+
# @return [Array<Hash>, Hash]
|
|
96
|
+
def tenders(inn: nil, ogrn: nil, limit: 10, page: 0)
|
|
97
|
+
ensure_inn_or_ogrn!(inn, ogrn)
|
|
98
|
+
ensure_pagination!(limit, page)
|
|
99
|
+
request_json("tenders", params: { inn: inn, ogrn: ogrn, limit: limit, page: page })
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Analytics
|
|
103
|
+
def finance(inn: nil, ogrn: nil) = ensure_and_get_json("finance", inn, ogrn)
|
|
104
|
+
def cost_business(inn: nil, ogrn: nil) = ensure_and_get_json("cost-business", inn, ogrn)
|
|
105
|
+
def reliability(inn: nil, ogrn: nil) = ensure_and_get_json("reliability", inn, ogrn)
|
|
106
|
+
def reliability_blocks(inn: nil, ogrn: nil) = ensure_and_get_json("reliability/blocks", inn, ogrn)
|
|
107
|
+
def market_position(inn: nil, ogrn: nil) = ensure_and_get_json("market-position", inn, ogrn)
|
|
108
|
+
def creditworthiness(inn: nil, ogrn: nil) = ensure_and_get_json("creditworthiness", inn, ogrn)
|
|
109
|
+
|
|
110
|
+
# Person / relations
|
|
111
|
+
def owners(inn: nil, ogrn: nil) = ensure_and_get_json("owners", inn, ogrn)
|
|
112
|
+
def affiliate(inn: nil, ogrn: nil) = ensure_and_get_json("affiliate", inn, ogrn)
|
|
113
|
+
def dirs_history(inn: nil, ogrn: nil) = ensure_and_get_json("dirs-history", inn, ogrn)
|
|
114
|
+
def founders_history(inn: nil, ogrn: nil) = ensure_and_get_json("founders-history", inn, ogrn)
|
|
115
|
+
|
|
116
|
+
# Licenses / inspections
|
|
117
|
+
def license_stat(inn: nil, ogrn: nil) = ensure_and_get_json("license/stat", inn, ogrn)
|
|
118
|
+
def license_data(inn: nil, ogrn: nil) = ensure_and_get_json("license/data", inn, ogrn)
|
|
119
|
+
def inspections_stat(inn: nil, ogrn: nil) = ensure_and_get_json("inspections/stat", inn, ogrn)
|
|
120
|
+
def inspections_data(inn: nil, ogrn: nil) = ensure_and_get_json("inspections/data", inn, ogrn)
|
|
121
|
+
|
|
122
|
+
# SRO
|
|
123
|
+
def sro(inn: nil, ogrn: nil) = ensure_and_get_json("sro", inn, ogrn)
|
|
124
|
+
def sro_file(inn: nil, ogrn: nil) = ensure_and_get_binary("sro/file", inn, ogrn)
|
|
125
|
+
|
|
126
|
+
# Courts / bankruptcy
|
|
127
|
+
def statistic_courts(inn: nil, ogrn: nil) = ensure_and_get_json("statistic-courts", inn, ogrn)
|
|
128
|
+
def courts(inn: nil, ogrn: nil) = ensure_and_get_json("courts", inn, ogrn)
|
|
129
|
+
def executive_lists(inn: nil, ogrn: nil) = ensure_and_get_json("executive-lists", inn, ogrn)
|
|
130
|
+
def bankruptcy(inn: nil, ogrn: nil) = ensure_and_get_json("bankruptcy", inn, ogrn)
|
|
131
|
+
def bankruptcy_file(inn: nil, ogrn: nil) = ensure_and_get_binary("bankruptcy/file", inn, ogrn)
|
|
132
|
+
|
|
133
|
+
# External economic activity
|
|
134
|
+
def fea(inn: nil, ogrn: nil) = ensure_and_get_json("fea", inn, ogrn)
|
|
135
|
+
|
|
136
|
+
# Events / subscriptions
|
|
137
|
+
# @return [Array<Hash>, Hash]
|
|
138
|
+
def events(inn: nil, ogrn: nil) = ensure_and_get_json("events", inn, ogrn)
|
|
139
|
+
# @return [String] binary file data
|
|
140
|
+
def events_file(inn: nil, ogrn: nil) = ensure_and_get_binary("events/file", inn, ogrn)
|
|
141
|
+
# @return [Array<Hash>, Hash]
|
|
142
|
+
def subscriptions_events(inn: nil, ogrn: nil) = ensure_and_get_json("subscriptions/events", inn, ogrn)
|
|
143
|
+
# @return [Array<Hash>, Hash]
|
|
144
|
+
def subscriptions_contractors(inn: nil, ogrn: nil) = ensure_and_get_json("subscriptions/contractors", inn, ogrn)
|
|
145
|
+
|
|
146
|
+
# @return [Array<Hash>, Hash]
|
|
147
|
+
def subscribe(inn: nil, ogrn: nil, **params)
|
|
148
|
+
ensure_inn_or_ogrn!(inn, ogrn)
|
|
149
|
+
request_json("subscriptions/subscribe", params: { inn: inn, ogrn: ogrn }.merge(params), method: :post)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# @return [Array<Hash>, Hash]
|
|
153
|
+
def unsubscribe(inn: nil, ogrn: nil, **params)
|
|
154
|
+
ensure_inn_or_ogrn!(inn, ogrn)
|
|
155
|
+
request_json("subscriptions/unsubscribe", params: { inn: inn, ogrn: ogrn }.merge(params), method: :post)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# History / contacts / branches
|
|
159
|
+
def contractor_history(inn: nil, ogrn: nil) = ensure_and_get_json("contractor-history", inn, ogrn)
|
|
160
|
+
def contacts_official(inn: nil, ogrn: nil) = ensure_and_get_json("contacts-official", inn, ogrn)
|
|
161
|
+
def branches(inn: nil, ogrn: nil) = ensure_and_get_json("branches", inn, ogrn)
|
|
162
|
+
|
|
163
|
+
# Excerpts
|
|
164
|
+
# @return [Array<Hash>, Hash]
|
|
165
|
+
def excerpts_list(inn: nil, ogrn: nil) = ensure_and_get_json("excerpts/list", inn, ogrn)
|
|
166
|
+
# @return [Array<Hash>, Hash]
|
|
167
|
+
def excerpts_last(inn: nil, ogrn: nil) = ensure_and_get_json("excerpts/last", inn, ogrn)
|
|
168
|
+
# @return [String] binary file data
|
|
169
|
+
def excerpts_file(inn: nil, ogrn: nil) = ensure_and_get_binary("excerpts/file", inn, ogrn)
|
|
170
|
+
|
|
171
|
+
# Search / stats
|
|
172
|
+
# @return [Array<Hash>, Hash]
|
|
173
|
+
def search(query:, **params)
|
|
174
|
+
raise ValidationError, "query is required" if query.nil? || query.to_s.strip.empty?
|
|
175
|
+
|
|
176
|
+
request_json("search", params: { query: query }.merge(params))
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# @return [Array<Hash>, Hash]
|
|
180
|
+
def client_stat(**params)
|
|
181
|
+
request_json("client/stat", params: params)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Trademarks / vehicles / pledges
|
|
185
|
+
# @return [Array<Hash>, Hash]
|
|
186
|
+
def trademarks(inn: nil, ogrn: nil) = ensure_and_get_json("trademarks", inn, ogrn)
|
|
187
|
+
# @return [String] binary image data
|
|
188
|
+
def trademarks_image(inn: nil, ogrn: nil) = ensure_and_get_binary("trademarks/image", inn, ogrn)
|
|
189
|
+
# @return [Array<Hash>, Hash]
|
|
190
|
+
def vehicle(inn: nil, ogrn: nil) = ensure_and_get_json("vehicle", inn, ogrn)
|
|
191
|
+
# @return [Array<Hash>, Hash]
|
|
192
|
+
def vehicle_stat(inn: nil, ogrn: nil) = ensure_and_get_json("vehicle/stat", inn, ogrn)
|
|
193
|
+
# @return [Array<Hash>, Hash]
|
|
194
|
+
def pledges(inn: nil, ogrn: nil) = ensure_and_get_json("pledges", inn, ogrn)
|
|
195
|
+
# @return [String] binary file data
|
|
196
|
+
def pledges_file(inn: nil, ogrn: nil) = ensure_and_get_binary("pledges/file", inn, ogrn)
|
|
197
|
+
|
|
198
|
+
# Reports
|
|
199
|
+
# @return [String] PDF binary
|
|
200
|
+
def pdf_business_report(inn: nil, ogrn: nil) = ensure_and_get_binary("pdf/business-report", inn, ogrn)
|
|
201
|
+
# @return [String] PDF binary
|
|
202
|
+
def pdf_due_diligence_report(inn: nil, ogrn: nil) = ensure_and_get_binary("pdf/due-diligence-report", inn, ogrn)
|
|
203
|
+
# @return [String] PDF binary
|
|
204
|
+
def pdf_financial_report(inn: nil, ogrn: nil) = ensure_and_get_binary("pdf/financial-report", inn, ogrn)
|
|
205
|
+
# @return [String] PDF binary
|
|
206
|
+
def pdf_signed_excerpt(inn: nil, ogrn: nil) = ensure_and_get_binary("pdf/signed-excerpt", inn, ogrn)
|
|
207
|
+
# @return [String] XML binary
|
|
208
|
+
def xml_egrul_excerpt(inn: nil, ogrn: nil) = ensure_and_get_binary("xml/egrul-excerpt", inn, ogrn)
|
|
209
|
+
# @return [String] XML binary
|
|
210
|
+
def xml_reporting_excerpt(inn: nil, ogrn: nil) = ensure_and_get_binary("xml/reporting-excerpt", inn, ogrn)
|
|
211
|
+
|
|
212
|
+
private
|
|
213
|
+
|
|
214
|
+
# @return [Array<Hash>, Hash]
|
|
215
|
+
def ensure_and_get_json(path, inn, ogrn)
|
|
216
|
+
ensure_inn_or_ogrn!(inn, ogrn)
|
|
217
|
+
request_json(path, params: { inn: inn, ogrn: ogrn })
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# @return [String]
|
|
221
|
+
def ensure_and_get_binary(path, inn, ogrn)
|
|
222
|
+
ensure_inn_or_ogrn!(inn, ogrn)
|
|
223
|
+
request_binary(path, params: { inn: inn, ogrn: ogrn })
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def build_uri(path)
|
|
227
|
+
URI.join(@host.to_s.end_with?("/") ? @host.to_s : "#{@host}/", path)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def request_json(path, params:, method: :get)
|
|
231
|
+
perform_request(path, params: params, method: method, expect_binary: false)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def request_binary(path, params:, method: :get)
|
|
235
|
+
perform_request(path, params: params, method: method, expect_binary: true)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def perform_request(path, params:, method:, expect_binary:)
|
|
239
|
+
attempts = 0
|
|
240
|
+
begin
|
|
241
|
+
attempts += 1
|
|
242
|
+
tokens = @auth.tokens
|
|
243
|
+
headers = default_headers(tokens)
|
|
244
|
+
@http.request(
|
|
245
|
+
method: method,
|
|
246
|
+
uri: build_uri(path),
|
|
247
|
+
headers: headers,
|
|
248
|
+
params: compact_params(params),
|
|
249
|
+
expect_binary: expect_binary
|
|
250
|
+
)
|
|
251
|
+
rescue HttpError => e
|
|
252
|
+
if [ 401, 403 ].include?(e.status.to_i) && attempts < 2
|
|
253
|
+
@auth.refresh!
|
|
254
|
+
retry
|
|
255
|
+
end
|
|
256
|
+
raise
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def resolve_credentials(credential_args, credentials)
|
|
261
|
+
return normalize_credentials(credentials) if credentials
|
|
262
|
+
|
|
263
|
+
unless credential_args.size == 3
|
|
264
|
+
raise ArgumentError, "provide client_id, client_secret, secret_key or pass credentials: { ... }"
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
{
|
|
268
|
+
client_id: credential_args[0],
|
|
269
|
+
client_secret: credential_args[1],
|
|
270
|
+
secret_key: credential_args[2]
|
|
271
|
+
}
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def build_config(options)
|
|
275
|
+
symbolized = symbolize_keys(options)
|
|
276
|
+
unknown = symbolized.keys - DEFAULT_CONFIG.keys
|
|
277
|
+
raise ArgumentError, "unknown option(s): #{unknown.join(', ')}" if unknown.any?
|
|
278
|
+
|
|
279
|
+
DEFAULT_CONFIG.merge(symbolized)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def normalize_credentials(values)
|
|
283
|
+
return symbolize_keys(values.to_h) if values.respond_to?(:to_h)
|
|
284
|
+
|
|
285
|
+
raise ArgumentError, "credentials must respond to #to_h"
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def symbolize_keys(hash)
|
|
289
|
+
hash.transform_keys(&:to_sym)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def ensure_inn_or_ogrn!(inn, ogrn)
|
|
293
|
+
return if (inn && !inn.to_s.empty?) || (ogrn && !ogrn.to_s.empty?)
|
|
294
|
+
|
|
295
|
+
raise ValidationError, "either inn or ogrn is required"
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def ensure_kpp_valid!(inn, kpp)
|
|
299
|
+
return unless kpp
|
|
300
|
+
|
|
301
|
+
raise ValidationError, "kpp requires inn" unless inn
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def ensure_pagination!(limit, page)
|
|
305
|
+
raise ValidationError, "limit must be positive" unless limit.to_i.positive?
|
|
306
|
+
raise ValidationError, "page must be non-negative" if page.to_i.negative?
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def compact_params(params)
|
|
310
|
+
params.each_with_object({}) do |(k, v), acc|
|
|
311
|
+
acc[k] = v unless v.nil?
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def default_headers(tokens)
|
|
316
|
+
{
|
|
317
|
+
"User-Agent" => "SabyVok Ruby Client / #{VERSION}",
|
|
318
|
+
"X-SBISAccessToken" => tokens[:access_token],
|
|
319
|
+
"X-SBISSessionId" => tokens[:session_id],
|
|
320
|
+
"Content-Type" => "application/json; charset=utf-8"
|
|
321
|
+
}
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SabyVok
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
class ValidationError < Error; end
|
|
7
|
+
|
|
8
|
+
class AuthError < Error
|
|
9
|
+
attr_reader :status, :body
|
|
10
|
+
|
|
11
|
+
def initialize(message, status:, body: nil)
|
|
12
|
+
super(message)
|
|
13
|
+
@status = status
|
|
14
|
+
@body = body
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class HttpError < Error
|
|
19
|
+
attr_reader :status, :body
|
|
20
|
+
|
|
21
|
+
def initialize(message, status:, body:)
|
|
22
|
+
super(message)
|
|
23
|
+
@status = status
|
|
24
|
+
@body = body
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class ParsingError < Error
|
|
29
|
+
attr_reader :body
|
|
30
|
+
|
|
31
|
+
def initialize(message, body:)
|
|
32
|
+
super(message)
|
|
33
|
+
@body = body
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "httparty"
|
|
4
|
+
require "json"
|
|
5
|
+
require "timeout"
|
|
6
|
+
|
|
7
|
+
module SabyVok
|
|
8
|
+
class Http
|
|
9
|
+
DEFAULT_TIMEOUT = 15
|
|
10
|
+
DEFAULT_RETRIES = 2
|
|
11
|
+
RETRYABLE_CODES = [ 429, 502, 503, 504 ].freeze
|
|
12
|
+
|
|
13
|
+
# @param logger [Logger, nil]
|
|
14
|
+
# @param timeout [Integer]
|
|
15
|
+
# @param retries [Integer]
|
|
16
|
+
def initialize(logger: nil, timeout: DEFAULT_TIMEOUT, retries: DEFAULT_RETRIES)
|
|
17
|
+
@logger = logger
|
|
18
|
+
@timeout = timeout
|
|
19
|
+
@retries = retries
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @param method [Symbol, String] :get or :post
|
|
23
|
+
# @param uri [URI::HTTP]
|
|
24
|
+
# @param headers [Hash]
|
|
25
|
+
# @param params [Hash]
|
|
26
|
+
# @param expect_binary [Boolean]
|
|
27
|
+
# @return [Hash, Array, String]
|
|
28
|
+
# @raise [HttpError, ParsingError]
|
|
29
|
+
def request(method:, uri:, headers:, params:, expect_binary: false)
|
|
30
|
+
raise ArgumentError, "uri must be URI" unless uri.is_a?(URI)
|
|
31
|
+
|
|
32
|
+
attempts = 0
|
|
33
|
+
loop do
|
|
34
|
+
attempts += 1
|
|
35
|
+
response = perform_request(method, uri, headers, params)
|
|
36
|
+
if retryable_response?(response) && attempts <= @retries + 1
|
|
37
|
+
backoff(attempts)
|
|
38
|
+
next
|
|
39
|
+
end
|
|
40
|
+
return parse_response(response, expect_binary: expect_binary)
|
|
41
|
+
rescue Timeout::Error => e
|
|
42
|
+
if attempts <= @retries + 1
|
|
43
|
+
backoff(attempts)
|
|
44
|
+
next
|
|
45
|
+
end
|
|
46
|
+
raise HttpError.new("request timed out: #{e.class}", status: 504, body: nil)
|
|
47
|
+
rescue SocketError, Errno::ECONNREFUSED => e
|
|
48
|
+
raise HttpError.new(
|
|
49
|
+
"connection failed: #{e.class} #{e.message}",
|
|
50
|
+
status: 503,
|
|
51
|
+
body: nil
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def perform_request(method, uri, headers, params)
|
|
59
|
+
options = {
|
|
60
|
+
headers: headers,
|
|
61
|
+
timeout: @timeout
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if method.to_s.downcase.to_sym == :post
|
|
65
|
+
options[:headers] = headers.merge("Content-Type" => "application/json; charset=utf-8")
|
|
66
|
+
options[:body] = params.to_json
|
|
67
|
+
else
|
|
68
|
+
options[:query] = params.compact
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
HTTParty.send(method, uri, options)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def retryable_response?(response)
|
|
75
|
+
RETRYABLE_CODES.include?(response.code.to_i)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def backoff(attempts)
|
|
79
|
+
delay = 0.5 * (2**(attempts - 1))
|
|
80
|
+
log("retrying in #{delay.round(2)}s (attempt #{attempts})")
|
|
81
|
+
sleep(delay)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def parse_response(response, expect_binary:)
|
|
85
|
+
code = response.code.to_i
|
|
86
|
+
body = response.body
|
|
87
|
+
return body if expect_binary && code.between?(200, 299)
|
|
88
|
+
|
|
89
|
+
return response.parsed_response if code.between?(200, 299)
|
|
90
|
+
|
|
91
|
+
raise HttpError.new("HTTP #{code}", status: code, body: body)
|
|
92
|
+
rescue JSON::ParserError => e
|
|
93
|
+
raise ParsingError.new("failed to parse JSON: #{e.message}", body: response.body)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def log(message)
|
|
97
|
+
return unless @logger
|
|
98
|
+
|
|
99
|
+
@logger.info("[saby_vok] #{message}")
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
data/lib/saby_vok.rb
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thread"
|
|
4
|
+
require_relative "saby_vok/version"
|
|
5
|
+
require_relative "saby_vok/errors"
|
|
6
|
+
require_relative "saby_vok/http"
|
|
7
|
+
require_relative "saby_vok/auth"
|
|
8
|
+
require_relative "saby_vok/client"
|
|
9
|
+
|
|
10
|
+
module SabyVok
|
|
11
|
+
# @example Configure once and reuse the client
|
|
12
|
+
# SabyVok.configure do |config|
|
|
13
|
+
# config.client_id = ENV.fetch("SABY_CLIENT_ID")
|
|
14
|
+
# config.client_secret = ENV.fetch("SABY_CLIENT_SECRET")
|
|
15
|
+
# config.secret_key = ENV.fetch("SABY_SECRET_KEY")
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# requisites = SabyVok.client.req(inn: "7605016030")
|
|
19
|
+
# puts requisites.first["company_name"]
|
|
20
|
+
class Config
|
|
21
|
+
attr_accessor :client_id, :client_secret, :secret_key, :host, :oauth_url, :timeout, :retries, :logger
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
attr_writer :client
|
|
26
|
+
|
|
27
|
+
# @return [SabyVok::Client, nil]
|
|
28
|
+
def client
|
|
29
|
+
mutex.synchronize do
|
|
30
|
+
@client ||= build_client_from_config
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @yield [Config]
|
|
35
|
+
# @return [void]
|
|
36
|
+
def configure
|
|
37
|
+
mutex.synchronize do
|
|
38
|
+
yield(config)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Drops the memoized client so the next call to {#client} rebuilds it with updated config.
|
|
43
|
+
#
|
|
44
|
+
# @return [void]
|
|
45
|
+
def reset_client!
|
|
46
|
+
mutex.synchronize do
|
|
47
|
+
@client = nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @return [Config]
|
|
52
|
+
def config
|
|
53
|
+
@config ||= Config.new
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def mutex
|
|
59
|
+
@mutex ||= Mutex.new
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def build_client_from_config
|
|
63
|
+
return nil unless config.client_id && config.client_secret && config.secret_key
|
|
64
|
+
|
|
65
|
+
Client.new(
|
|
66
|
+
config.client_id,
|
|
67
|
+
config.client_secret,
|
|
68
|
+
config.secret_key,
|
|
69
|
+
host: config.host,
|
|
70
|
+
oauth_url: config.oauth_url,
|
|
71
|
+
timeout: config.timeout,
|
|
72
|
+
retries: config.retries,
|
|
73
|
+
logger: config.logger
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: saby_vok
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Vitalii Dementev
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: httparty
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: mutex_m
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: ostruct
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
description: Thin Ruby wrapper around the Saby Vok API with easy auth and binary/JSON
|
|
55
|
+
helpers.
|
|
56
|
+
email:
|
|
57
|
+
- v@dementev.dev
|
|
58
|
+
executables: []
|
|
59
|
+
extensions: []
|
|
60
|
+
extra_rdoc_files: []
|
|
61
|
+
files:
|
|
62
|
+
- CHANGELOG.md
|
|
63
|
+
- LICENSE.txt
|
|
64
|
+
- README.md
|
|
65
|
+
- lib/saby_vok.rb
|
|
66
|
+
- lib/saby_vok/auth.rb
|
|
67
|
+
- lib/saby_vok/client.rb
|
|
68
|
+
- lib/saby_vok/errors.rb
|
|
69
|
+
- lib/saby_vok/http.rb
|
|
70
|
+
- lib/saby_vok/version.rb
|
|
71
|
+
homepage: https://dementevvv.github.io/saby_vok/
|
|
72
|
+
licenses:
|
|
73
|
+
- MIT
|
|
74
|
+
metadata:
|
|
75
|
+
homepage_uri: https://dementevvv.github.io/saby_vok/
|
|
76
|
+
documentation_uri: https://dementevvv.github.io/saby_vok/
|
|
77
|
+
source_code_uri: https://github.com/DementevVV/saby_vok
|
|
78
|
+
changelog_uri: https://github.com/DementevVV/saby_vok/blob/master/CHANGELOG.md
|
|
79
|
+
bug_tracker_uri: https://github.com/DementevVV/saby_vok/issues
|
|
80
|
+
rubygems_mfa_required: 'true'
|
|
81
|
+
rdoc_options: []
|
|
82
|
+
require_paths:
|
|
83
|
+
- lib
|
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '3.1'
|
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - ">="
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '0'
|
|
94
|
+
requirements: []
|
|
95
|
+
rubygems_version: 3.7.2
|
|
96
|
+
specification_version: 4
|
|
97
|
+
summary: Ruby client for Saby Vok API
|
|
98
|
+
test_files: []
|