max_api_client 0.1.2 → 0.2.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 +4 -4
- data/CHANGELOG.md +30 -2
- data/README.md +149 -18
- data/certs/russian_trusted_root_ca.pem +33 -0
- data/lib/max_api_client/api.rb +57 -6
- data/lib/max_api_client/attachments.rb +69 -1
- data/lib/max_api_client/certificate_store.rb +19 -0
- data/lib/max_api_client/client.rb +14 -7
- data/lib/max_api_client/polling.rb +7 -5
- data/lib/max_api_client/raw_api.rb +65 -9
- data/lib/max_api_client/version.rb +1 -1
- data/lib/max_api_client.rb +2 -0
- data/sig/max_api_client.rbs +49 -7
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7d6578ded53eb4717c32d40728c217f163db0e8149bc813b084c6920692201dd
|
|
4
|
+
data.tar.gz: 338a01c773c1be6502cd37161bf1b11011a08717fd006055db99e9ff7466716c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a656768c33a27bf0c4d6a61b8eb8c6ef807b2177472c19c99c70ed29ec4f4d9a4ee9dfaec2a8db4d330582040d79b8500d511bcb3dd8e55fd178f98058f8fb09
|
|
7
|
+
data.tar.gz: 99f540522f43226232847ee792d1950643c7ff56464b693813b60d7a3da6cd07fa155c62978c95630cf8f7da78385ffbe5c6eb48b79392b5ca7e395362901751
|
data/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,35 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
## [0.2.0] - 2026-10-03
|
|
4
|
+
|
|
5
|
+
Synced with Max Bot API schema 0.0.33.
|
|
6
|
+
|
|
7
|
+
- Added `edit_my_commands` (`PATCH /me/commands`); `set_my_commands` and `delete_my_commands` now use it instead of `PATCH /me`.
|
|
8
|
+
- Added `add_chat_admins` and `remove_chat_admin`.
|
|
9
|
+
- Added comment methods: `get_comments`, `get_comment`, `send_comment`, `edit_comment`, `delete_comment` (`api.raw.comments`).
|
|
10
|
+
- Added `get_video_info` (`GET /videos/{videoToken}`).
|
|
11
|
+
- Added `ContactAttachment`, `InlineKeyboardAttachment` and `Button` builders; attachment objects now serialize to JSON without an explicit `to_h`.
|
|
12
|
+
- Added `limit` to `poll_updates`; an empty `types` list is no longer sent.
|
|
13
|
+
- Fixed `remove_chat_member`: `user_id` and `block` are sent as query parameters, as the schema requires.
|
|
14
|
+
- Fixed `get_message`, which raised `ArgumentError`.
|
|
15
|
+
- Fixed `answer_on_callback`: `disable_link_preview` is sent as a query parameter.
|
|
16
|
+
- `false` query values are now sent as `false` instead of being dropped.
|
|
17
|
+
- Sending a message is retried at most 3 times on `attachment.not.ready`, with exponential backoff.
|
|
18
|
+
- `delete_message` no longer accepts extra options (they raised `ArgumentError`).
|
|
19
|
+
- Deprecated `edit_my_info`, `get_all_chats`, `get_chat_by_link` and `add_chat_members`: their endpoints are removed from the API or absent from the schema.
|
|
20
|
+
|
|
21
|
+
## [0.1.3] - 2026-07-16
|
|
22
|
+
|
|
23
|
+
- Switched the default API endpoint to `https://platform-api2.max.ru`.
|
|
24
|
+
- Added the Russian Trusted Root CA to the TLS trust store, with optional `ca_file` override.
|
|
25
|
+
- Added an explicit `verify_ssl: false` option for environments that must temporarily ignore certificate errors.
|
|
26
|
+
- Documented the new endpoint and TLS configuration options.
|
|
27
|
+
- Updated the TypeScript reference client link.
|
|
28
|
+
|
|
29
|
+
## [0.1.2] - 2026-03-27
|
|
30
|
+
|
|
31
|
+
- Configured RubyGems trusted publishing through GitHub Actions.
|
|
32
|
+
- Added release tag validation and updated the Ruby CI matrix.
|
|
5
33
|
|
|
6
34
|
## [0.1.0] - 2026-02-12
|
|
7
35
|
|
data/README.md
CHANGED
|
@@ -24,19 +24,95 @@ gem "max_api_client", git: "git@github.com:Ziaw/max_api_client.git"
|
|
|
24
24
|
bundle install
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
## Адрес API и сертификат Минцифры
|
|
28
|
+
|
|
29
|
+
По умолчанию клиент использует актуальный адрес `https://platform-api2.max.ru`.
|
|
30
|
+
В gem включён корневой сертификат `Russian Trusted Root CA`, поэтому TLS-цепочка
|
|
31
|
+
MAX проверяется вместе с обычным системным хранилищем доверенных сертификатов.
|
|
32
|
+
По умолчанию проверка TLS включена.
|
|
33
|
+
|
|
34
|
+
Актуальные сертификаты и инструкции по их системной установке опубликованы
|
|
35
|
+
на Госуслугах: <https://www.gosuslugi.ru/crt>.
|
|
36
|
+
|
|
37
|
+
Обычное подключение не требует дополнительных настроек:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
api = MaxApiClient::Api.new(
|
|
41
|
+
token: ENV.fetch("MAX_BOT_TOKEN")
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Доступные параметры подключения:
|
|
46
|
+
|
|
47
|
+
- `base_url` — адрес MAX API, по умолчанию `https://platform-api2.max.ru`;
|
|
48
|
+
- `ca_file` — путь к дополнительному доверенному CA-файлу;
|
|
49
|
+
- `verify_ssl` — проверять сертификат сервера, по умолчанию `true`;
|
|
50
|
+
- `open_timeout` — таймаут установления соединения;
|
|
51
|
+
- `read_timeout` — таймаут чтения ответа.
|
|
52
|
+
|
|
53
|
+
Эти параметры поддерживают как `MaxApiClient::Api`, так и
|
|
54
|
+
`MaxApiClient::Client`.
|
|
55
|
+
|
|
56
|
+
### Подключение собственного сертификата
|
|
57
|
+
|
|
58
|
+
Передайте путь к актуальному сертификату Минцифры в формате PEM:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
api = MaxApiClient::Api.new(
|
|
62
|
+
token: ENV.fetch("MAX_BOT_TOKEN"),
|
|
63
|
+
ca_file: "/etc/ssl/certs/russian_trusted_root_ca.pem"
|
|
64
|
+
)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Чтобы использовать только системное хранилище сертификатов, передайте
|
|
68
|
+
`ca_file: nil`.
|
|
69
|
+
|
|
70
|
+
### Игнорирование неверного сертификата
|
|
71
|
+
|
|
72
|
+
Если окружение временно не может проверить сертификат, отключите проверку
|
|
73
|
+
параметром `verify_ssl: false`:
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
api = MaxApiClient::Api.new(
|
|
77
|
+
token: ENV.fetch("MAX_BOT_TOKEN"),
|
|
78
|
+
verify_ssl: false
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`verify_ssl: false` принимает любой серверный сертификат и делает соединение
|
|
83
|
+
уязвимым для перехвата. Используйте эту настройку только как временный обходной
|
|
84
|
+
вариант. Для постоянной настройки передайте актуальный сертификат через
|
|
85
|
+
`ca_file` или установите его в системное хранилище.
|
|
86
|
+
|
|
87
|
+
Полный пример настройки:
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
api = MaxApiClient::Api.new(
|
|
91
|
+
token: ENV.fetch("MAX_BOT_TOKEN"),
|
|
92
|
+
base_url: "https://platform-api2.max.ru",
|
|
93
|
+
ca_file: MaxApiClient::Client::DEFAULT_CA_FILE,
|
|
94
|
+
verify_ssl: true,
|
|
95
|
+
open_timeout: 10,
|
|
96
|
+
read_timeout: 30
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
27
100
|
## Справочник API
|
|
28
101
|
|
|
29
102
|
### Методы бота
|
|
30
103
|
|
|
31
|
-
Методы
|
|
104
|
+
Методы доступные через `MaxApiClient::Api`, порт официального клиента <https://github.com/max-messenger/max-bot-api-client-ts>, названия сохранены, poller собственный:
|
|
32
105
|
|
|
33
106
|
- `get_my_info`
|
|
34
|
-
- `
|
|
107
|
+
- `edit_my_commands(commands)`
|
|
35
108
|
- `set_my_commands(commands)`
|
|
36
109
|
- `delete_my_commands`
|
|
37
110
|
|
|
38
|
-
|
|
39
|
-
|
|
111
|
+
Клиент соответствует схеме Bot API MAX версии 0.0.33
|
|
112
|
+
(<https://github.com/max-messenger/api-schema>).
|
|
113
|
+
|
|
114
|
+
`set_my_commands(commands)` это синоним `edit_my_commands(commands)`.
|
|
115
|
+
Он принимает массив команд и отправляет его в поле `commands`.
|
|
40
116
|
|
|
41
117
|
Ожидается массив хэшей с данными команды, например:
|
|
42
118
|
|
|
@@ -52,27 +128,28 @@ api.set_my_commands([
|
|
|
52
128
|
Соответствующие HTTP-маршруты:
|
|
53
129
|
|
|
54
130
|
- `GET /me`
|
|
55
|
-
- `PATCH /me`
|
|
131
|
+
- `PATCH /me/commands`
|
|
56
132
|
|
|
57
133
|
Типовые сценарии:
|
|
58
134
|
|
|
59
135
|
- получить текущий профиль бота;
|
|
60
|
-
- обновить имя, описание, аватар и команды бота;
|
|
61
136
|
- опубликовать или очистить подсказки команд для пользователей.
|
|
62
137
|
|
|
138
|
+
Устаревший метод `edit_my_info(**extra)` (`PATCH /me`) оставлен для совместимости
|
|
139
|
+
и выводит предупреждение: этого маршрута нет в актуальной схеме API.
|
|
140
|
+
|
|
63
141
|
### Методы чатов
|
|
64
142
|
|
|
65
143
|
Методы Ruby, доступные через `MaxApiClient::Api`:
|
|
66
144
|
|
|
67
|
-
- `get_all_chats(**extra)`
|
|
68
145
|
- `get_chat(chat_id)`
|
|
69
|
-
- `get_chat_by_link(chat_link)`
|
|
70
146
|
- `edit_chat_info(chat_id, **extra)`
|
|
71
147
|
- `get_chat_membership(chat_id)`
|
|
72
148
|
- `get_chat_admins(chat_id)`
|
|
73
|
-
- `
|
|
149
|
+
- `add_chat_admins(chat_id, admins)`
|
|
150
|
+
- `remove_chat_admin(chat_id, user_id)`
|
|
74
151
|
- `get_chat_members(chat_id, **extra)`
|
|
75
|
-
- `remove_chat_member(chat_id, user_id)`
|
|
152
|
+
- `remove_chat_member(chat_id, user_id, block: nil)`
|
|
76
153
|
- `get_pinned_message(chat_id)`
|
|
77
154
|
- `pin_message(chat_id, message_id, **extra)`
|
|
78
155
|
- `unpin_message(chat_id)`
|
|
@@ -81,13 +158,12 @@ api.set_my_commands([
|
|
|
81
158
|
|
|
82
159
|
Соответствующие HTTP-маршруты:
|
|
83
160
|
|
|
84
|
-
- `GET /chats`
|
|
85
161
|
- `GET /chats/{chat_id}`
|
|
86
|
-
- `GET /chats/{chat_link}`
|
|
87
162
|
- `PATCH /chats/{chat_id}`
|
|
88
163
|
- `GET /chats/{chat_id}/members/me`
|
|
89
164
|
- `GET /chats/{chat_id}/members/admins`
|
|
90
|
-
- `POST /chats/{chat_id}/members`
|
|
165
|
+
- `POST /chats/{chat_id}/members/admins`
|
|
166
|
+
- `DELETE /chats/{chat_id}/members/admins/{user_id}`
|
|
91
167
|
- `GET /chats/{chat_id}/members`
|
|
92
168
|
- `DELETE /chats/{chat_id}/members`
|
|
93
169
|
- `GET /chats/{chat_id}/pin`
|
|
@@ -98,14 +174,29 @@ api.set_my_commands([
|
|
|
98
174
|
|
|
99
175
|
Типовые сценарии:
|
|
100
176
|
|
|
101
|
-
- получить
|
|
102
|
-
- найти чат по идентификатору или публичной ссылке;
|
|
177
|
+
- получить чат по идентификатору;
|
|
103
178
|
- изменить заголовок, иконку и метаданные чата;
|
|
104
179
|
- управлять участниками и администраторами;
|
|
105
180
|
- читать, устанавливать и снимать закреплённые сообщения;
|
|
106
181
|
- отправлять статус набора текста и другие действия отправителя;
|
|
107
182
|
- выходить из чата.
|
|
108
183
|
|
|
184
|
+
Пример назначения администратора:
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
api.add_chat_admins(chat_id, [
|
|
188
|
+
{ user_id: 42, permissions: %w[write pin_message] }
|
|
189
|
+
])
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Устаревшие методы оставлены для совместимости и выводят предупреждение:
|
|
193
|
+
|
|
194
|
+
- `get_all_chats(**extra)` — `GET /chats` не поддерживается с июня 2026;
|
|
195
|
+
список чатов нужно вести самостоятельно по событиям `bot_added` и `bot_started`;
|
|
196
|
+
- `get_chat_by_link(chat_link)` — поиска по ссылке нет в актуальной схеме;
|
|
197
|
+
- `add_chat_members(chat_id, user_ids)` — `POST /chats/{chat_id}/members`
|
|
198
|
+
удалён из API 30 сентября 2026.
|
|
199
|
+
|
|
109
200
|
### Методы сообщений
|
|
110
201
|
|
|
111
202
|
Методы Ruby, доступные через `MaxApiClient::Api`:
|
|
@@ -115,7 +206,8 @@ api.set_my_commands([
|
|
|
115
206
|
- `get_messages(chat_id, **extra)`
|
|
116
207
|
- `get_message(message_id)`
|
|
117
208
|
- `edit_message(message_id, **extra)`
|
|
118
|
-
- `delete_message(message_id
|
|
209
|
+
- `delete_message(message_id)`
|
|
210
|
+
- `get_video_info(video_token)`
|
|
119
211
|
- `answer_on_callback(callback_id, **extra)`
|
|
120
212
|
|
|
121
213
|
Соответствующие HTTP-маршруты:
|
|
@@ -125,6 +217,7 @@ api.set_my_commands([
|
|
|
125
217
|
- `GET /messages/{message_id}`
|
|
126
218
|
- `PUT /messages`
|
|
127
219
|
- `DELETE /messages`
|
|
220
|
+
- `GET /videos/{video_token}`
|
|
128
221
|
- `POST /answers`
|
|
129
222
|
|
|
130
223
|
Поддерживаемые возможности:
|
|
@@ -133,7 +226,25 @@ api.set_my_commands([
|
|
|
133
226
|
- дополнительный payload для форматирования, reply-ссылок и вложений;
|
|
134
227
|
- редактирование и удаление сообщений;
|
|
135
228
|
- ответы на callback-кнопки;
|
|
136
|
-
- автоматический повтор
|
|
229
|
+
- автоматический повтор запроса (до 3 попыток), если вложение после загрузки ещё не готово.
|
|
230
|
+
|
|
231
|
+
### Методы комментариев
|
|
232
|
+
|
|
233
|
+
Методы Ruby, доступные через `MaxApiClient::Api`:
|
|
234
|
+
|
|
235
|
+
- `get_comments(message_id, **extra)`
|
|
236
|
+
- `get_comment(message_id, comment_id)`
|
|
237
|
+
- `send_comment(message_id, text, **extra)`
|
|
238
|
+
- `edit_comment(message_id, comment_id, **extra)`
|
|
239
|
+
- `delete_comment(message_id, comment_id)`
|
|
240
|
+
|
|
241
|
+
Соответствующие HTTP-маршруты:
|
|
242
|
+
|
|
243
|
+
- `GET /messages/{message_id}/comments`
|
|
244
|
+
- `GET /messages/{message_id}/comments/{comment_id}`
|
|
245
|
+
- `POST /messages/{message_id}/comments`
|
|
246
|
+
- `PUT /messages/{message_id}/comments`
|
|
247
|
+
- `DELETE /messages/{message_id}/comments`
|
|
137
248
|
|
|
138
249
|
### Методы подписок
|
|
139
250
|
|
|
@@ -142,7 +253,7 @@ api.set_my_commands([
|
|
|
142
253
|
- `get_subscriptions`
|
|
143
254
|
- `subscribe(url, update_types: nil, secret: nil)`
|
|
144
255
|
- `unsubscribe(url)`
|
|
145
|
-
- `poll_updates(types = [], marker: nil, timeout: 20, retry_interval: 5, read_timeout: nil, &block)`
|
|
256
|
+
- `poll_updates(types = [], marker: nil, limit: nil, timeout: 20, retry_interval: 5, read_timeout: nil, &block)`
|
|
146
257
|
|
|
147
258
|
Соответствующие HTTP-маршруты:
|
|
148
259
|
|
|
@@ -197,6 +308,25 @@ end
|
|
|
197
308
|
- `StickerAttachment`
|
|
198
309
|
- `LocationAttachment`
|
|
199
310
|
- `ShareAttachment`
|
|
311
|
+
- `ContactAttachment`
|
|
312
|
+
- `InlineKeyboardAttachment`
|
|
313
|
+
|
|
314
|
+
Объекты вложений можно передавать в `attachments:` напрямую. Кнопки клавиатуры
|
|
315
|
+
собираются через `MaxApiClient::Button`:
|
|
316
|
+
|
|
317
|
+
```ruby
|
|
318
|
+
keyboard = MaxApiClient::InlineKeyboardAttachment.new(
|
|
319
|
+
buttons: [[
|
|
320
|
+
MaxApiClient::Button.callback("Да", "yes"),
|
|
321
|
+
MaxApiClient::Button.link("Сайт", "https://max.ru")
|
|
322
|
+
]]
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
api.send_message_to_chat(chat_id, "Выберите", attachments: [keyboard])
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Доступные кнопки: `callback`, `link`, `message`, `request_contact`,
|
|
329
|
+
`request_geo_location`, `open_app`, `clipboard`.
|
|
200
330
|
|
|
201
331
|
### Доступ к Raw API
|
|
202
332
|
|
|
@@ -265,6 +395,7 @@ bin/console
|
|
|
265
395
|
## Источники
|
|
266
396
|
|
|
267
397
|
- Официальная документация Max Bot API: <https://dev.max.ru/>
|
|
398
|
+
- OpenAPI-схема Max Bot API: <https://github.com/max-messenger/api-schema>
|
|
268
399
|
- TypeScript reference client: <https://github.com/max-messenger/max-bot-api-client-ts>
|
|
269
400
|
|
|
270
401
|
## Лицензия
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIFwjCCA6qgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwcDELMAkGA1UEBhMCUlUx
|
|
3
|
+
PzA9BgNVBAoMNlRoZSBNaW5pc3RyeSBvZiBEaWdpdGFsIERldmVsb3BtZW50IGFu
|
|
4
|
+
ZCBDb21tdW5pY2F0aW9uczEgMB4GA1UEAwwXUnVzc2lhbiBUcnVzdGVkIFJvb3Qg
|
|
5
|
+
Q0EwHhcNMjIwMzAxMjEwNDE1WhcNMzIwMjI3MjEwNDE1WjBwMQswCQYDVQQGEwJS
|
|
6
|
+
VTE/MD0GA1UECgw2VGhlIE1pbmlzdHJ5IG9mIERpZ2l0YWwgRGV2ZWxvcG1lbnQg
|
|
7
|
+
YW5kIENvbW11bmljYXRpb25zMSAwHgYDVQQDDBdSdXNzaWFuIFRydXN0ZWQgUm9v
|
|
8
|
+
dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMfFOZ8pUAL3+r2n
|
|
9
|
+
qqE0Zp52selXsKGFYoG0GM5bwz1bSFtCt+AZQMhkWQheI3poZAToYJu69pHLKS6Q
|
|
10
|
+
XBiwBC1cvzYmUYKMYZC7jE5YhEU2bSL0mX7NaMxMDmH2/NwuOVRj8OImVa5s1F4U
|
|
11
|
+
zn4Kv3PFlDBjjSjXKVY9kmjUBsXQrIHeaqmUIsPIlNWUnimXS0I0abExqkbdrXbX
|
|
12
|
+
YwCOXhOO2pDUx3ckmJlCMUGacUTnylyQW2VsJIyIGA8V0xzdaeUXg0VZ6ZmNUr5Y
|
|
13
|
+
Ber/EAOLPb8NYpsAhJe2mXjMB/J9HNsoFMBFJ0lLOT/+dQvjbdRZoOT8eqJpWnVD
|
|
14
|
+
U+QL/qEZnz57N88OWM3rabJkRNdU/Z7x5SFIM9FrqtN8xewsiBWBI0K6XFuOBOTD
|
|
15
|
+
4V08o4TzJ8+Ccq5XlCUW2L48pZNCYuBDfBh7FxkB7qDgGDiaftEkZZfApRg2E+M9
|
|
16
|
+
G8wkNKTPLDc4wH0FDTijhgxR3Y4PiS1HL2Zhw7bD3CbslmEGgfnnZojNkJtcLeBH
|
|
17
|
+
BLa52/dSwNU4WWLubaYSiAmA9IUMX1/RpfpxOxd4Ykmhz97oFbUaDJFipIggx5sX
|
|
18
|
+
ePAlkTdWnv+RWBxlJwMQ25oEHmRguNYf4Zr/Rxr9cS93Y+mdXIZaBEE0KS2iLRqa
|
|
19
|
+
OiWBki9IMQU4phqPOBAaG7A+eP8PAgMBAAGjZjBkMB0GA1UdDgQWBBTh0YHlzlpf
|
|
20
|
+
BKrS6badZrHF+qwshzAfBgNVHSMEGDAWgBTh0YHlzlpfBKrS6badZrHF+qwshzAS
|
|
21
|
+
BgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF
|
|
22
|
+
AAOCAgEAALIY1wkilt/urfEVM5vKzr6utOeDWCUczmWX/RX4ljpRdgF+5fAIS4vH
|
|
23
|
+
tmXkqpSCOVeWUrJV9QvZn6L227ZwuE15cWi8DCDal3Ue90WgAJJZMfTshN4OI8cq
|
|
24
|
+
W9E4EG9wglbEtMnObHlms8F3CHmrw3k6KmUkWGoa+/ENmcVl68u/cMRl1JbW2bM+
|
|
25
|
+
/3A+SAg2c6iPDlehczKx2oa95QW0SkPPWGuNA/CE8CpyANIhu9XFrj3RQ3EqeRcS
|
|
26
|
+
AQQod1RNuHpfETLU/A2gMmvn/w/sx7TB3W5BPs6rprOA37tutPq9u6FTZOcG1Oqj
|
|
27
|
+
C/B7yTqgI7rbyvox7DEXoX7rIiEqyNNUguTk/u3SZ4VXE2kmxdmSh3TQvybfbnXV
|
|
28
|
+
4JbCZVaqiZraqc7oZMnRoWrXRG3ztbnbes/9qhRGI7PqXqeKJBztxRTEVj8ONs1d
|
|
29
|
+
WN5szTwaPIvhkhO3CO5ErU2rVdUr89wKpNXbBODFKRtgxUT70YpmJ46VVaqdAhOZ
|
|
30
|
+
D9EUUn4YaeLaS8AjSF/h7UkjOibNc4qVDiPP+rkehFWM66PVnP1Msh93tc+taIfC
|
|
31
|
+
EYVMxjh8zNbFuoc7fzvvrFILLe7ifvEIUqSVIC/AzplM/Jxw7buXFeGP1qVCBEHq
|
|
32
|
+
391d/9RAfaZ12zkwFsl+IKwE/OZxW8AHa9i1p4GO0YSNuczzEm4=
|
|
33
|
+
-----END CERTIFICATE-----
|
data/lib/max_api_client/api.rb
CHANGED
|
@@ -2,18 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
module MaxApiClient
|
|
4
4
|
# High-level convenience wrapper over grouped Max Bot API endpoints.
|
|
5
|
+
# rubocop:disable Metrics/ClassLength
|
|
5
6
|
class Api
|
|
6
7
|
attr_reader :raw, :upload, :client
|
|
7
8
|
|
|
8
9
|
# rubocop:disable Metrics/ParameterLists
|
|
9
10
|
def initialize(token:, base_url: Client::DEFAULT_BASE_URL, adapter: nil, open_timeout: nil, read_timeout: nil,
|
|
10
|
-
logger: nil)
|
|
11
|
+
ca_file: Client::DEFAULT_CA_FILE, verify_ssl: true, logger: nil)
|
|
11
12
|
@client = Client.new(
|
|
12
13
|
token:,
|
|
13
14
|
base_url:,
|
|
14
15
|
adapter:,
|
|
15
16
|
open_timeout:,
|
|
16
17
|
read_timeout:,
|
|
18
|
+
ca_file:,
|
|
19
|
+
verify_ssl:,
|
|
17
20
|
logger:
|
|
18
21
|
)
|
|
19
22
|
@raw = RawApi.new(client)
|
|
@@ -28,20 +31,26 @@ module MaxApiClient
|
|
|
28
31
|
# rubocop:enable Naming/AccessorMethodName
|
|
29
32
|
|
|
30
33
|
def edit_my_info(**extra)
|
|
34
|
+
warn_deprecated(:edit_my_info, "PATCH /me is absent from the current API schema, use edit_my_commands")
|
|
31
35
|
raw.bots.edit_my_info(**extra)
|
|
32
36
|
end
|
|
33
37
|
|
|
38
|
+
def edit_my_commands(commands)
|
|
39
|
+
raw.bots.edit_my_commands(commands:)
|
|
40
|
+
end
|
|
41
|
+
|
|
34
42
|
# rubocop:disable Naming/AccessorMethodName
|
|
35
43
|
def set_my_commands(commands)
|
|
36
|
-
|
|
44
|
+
edit_my_commands(commands)
|
|
37
45
|
end
|
|
38
46
|
# rubocop:enable Naming/AccessorMethodName
|
|
39
47
|
|
|
40
48
|
def delete_my_commands
|
|
41
|
-
|
|
49
|
+
edit_my_commands([])
|
|
42
50
|
end
|
|
43
51
|
|
|
44
52
|
def get_all_chats(**extra)
|
|
53
|
+
warn_deprecated(:get_all_chats, "GET /chats is not supported by the API since June 2026")
|
|
45
54
|
raw.chats.get_all(**extra)
|
|
46
55
|
end
|
|
47
56
|
|
|
@@ -50,6 +59,7 @@ module MaxApiClient
|
|
|
50
59
|
end
|
|
51
60
|
|
|
52
61
|
def get_chat_by_link(chat_link)
|
|
62
|
+
warn_deprecated(:get_chat_by_link, "lookup by link is absent from the current API schema")
|
|
53
63
|
raw.chats.get_by_link(chat_link:)
|
|
54
64
|
end
|
|
55
65
|
|
|
@@ -65,7 +75,16 @@ module MaxApiClient
|
|
|
65
75
|
raw.chats.get_chat_admins(chat_id:)
|
|
66
76
|
end
|
|
67
77
|
|
|
78
|
+
def add_chat_admins(chat_id, admins)
|
|
79
|
+
raw.chats.add_chat_admins(chat_id:, admins:)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def remove_chat_admin(chat_id, user_id)
|
|
83
|
+
raw.chats.remove_chat_admin(chat_id:, user_id:)
|
|
84
|
+
end
|
|
85
|
+
|
|
68
86
|
def add_chat_members(chat_id, user_ids)
|
|
87
|
+
warn_deprecated(:add_chat_members, "POST /chats/{chat_id}/members was removed from the API on 2026-09-30")
|
|
69
88
|
raw.chats.add_chat_members(chat_id:, user_ids:)
|
|
70
89
|
end
|
|
71
90
|
|
|
@@ -117,14 +136,38 @@ module MaxApiClient
|
|
|
117
136
|
raw.messages.edit(message_id:, **extra)
|
|
118
137
|
end
|
|
119
138
|
|
|
120
|
-
def delete_message(message_id
|
|
121
|
-
raw.messages.delete(message_id
|
|
139
|
+
def delete_message(message_id)
|
|
140
|
+
raw.messages.delete(message_id:)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def get_video_info(video_token)
|
|
144
|
+
raw.messages.get_video_info(video_token:)
|
|
122
145
|
end
|
|
123
146
|
|
|
124
147
|
def answer_on_callback(callback_id, **extra)
|
|
125
148
|
raw.messages.answer_on_callback(callback_id:, **extra)
|
|
126
149
|
end
|
|
127
150
|
|
|
151
|
+
def get_comments(message_id, **extra)
|
|
152
|
+
raw.comments.get(message_id:, **csv_query(extra, :comment_ids))
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def get_comment(message_id, comment_id)
|
|
156
|
+
raw.comments.get_by_id(message_id:, comment_id:)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def send_comment(message_id, text, **extra)
|
|
160
|
+
message_from(raw.comments.send(message_id:, text:, **extra))
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def edit_comment(message_id, comment_id, **extra)
|
|
164
|
+
raw.comments.edit(message_id:, comment_id:, **extra)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def delete_comment(message_id, comment_id)
|
|
168
|
+
raw.comments.delete(message_id:, comment_id:)
|
|
169
|
+
end
|
|
170
|
+
|
|
128
171
|
# rubocop:disable Naming/AccessorMethodName
|
|
129
172
|
def get_subscriptions
|
|
130
173
|
raw.subscriptions.get_subscriptions
|
|
@@ -139,12 +182,14 @@ module MaxApiClient
|
|
|
139
182
|
raw.subscriptions.unsubscribe(url:)
|
|
140
183
|
end
|
|
141
184
|
|
|
142
|
-
|
|
185
|
+
# rubocop:disable Metrics/ParameterLists
|
|
186
|
+
def poll_updates(types = [], marker: nil, limit: nil, timeout: Polling::DEFAULT_TIMEOUT,
|
|
143
187
|
retry_interval: Polling::DEFAULT_RETRY_INTERVAL, read_timeout: nil, &block)
|
|
144
188
|
poller = Polling.new(
|
|
145
189
|
self,
|
|
146
190
|
types:,
|
|
147
191
|
marker:,
|
|
192
|
+
limit:,
|
|
148
193
|
timeout:,
|
|
149
194
|
retry_interval:,
|
|
150
195
|
read_timeout:
|
|
@@ -154,6 +199,7 @@ module MaxApiClient
|
|
|
154
199
|
|
|
155
200
|
poller.each(&block)
|
|
156
201
|
end
|
|
202
|
+
# rubocop:enable Metrics/ParameterLists
|
|
157
203
|
|
|
158
204
|
def upload_image(options)
|
|
159
205
|
data = upload.image(**options)
|
|
@@ -187,8 +233,13 @@ module MaxApiClient
|
|
|
187
233
|
query.merge(key => normalize_types(query[key]))
|
|
188
234
|
end
|
|
189
235
|
|
|
236
|
+
def warn_deprecated(method_name, reason)
|
|
237
|
+
warn("[max_api_client] #{method_name} is deprecated: #{reason}", uplevel: 2)
|
|
238
|
+
end
|
|
239
|
+
|
|
190
240
|
def message_from(response)
|
|
191
241
|
response.fetch("message") { response.fetch(:message) }
|
|
192
242
|
end
|
|
193
243
|
end
|
|
244
|
+
# rubocop:enable Metrics/ClassLength
|
|
194
245
|
end
|
|
@@ -6,6 +6,10 @@ module MaxApiClient
|
|
|
6
6
|
def to_h
|
|
7
7
|
raise NotImplementedError, "#{self.class} must implement #to_h"
|
|
8
8
|
end
|
|
9
|
+
|
|
10
|
+
def to_json(*args)
|
|
11
|
+
to_h.to_json(*args)
|
|
12
|
+
end
|
|
9
13
|
end
|
|
10
14
|
|
|
11
15
|
# Shared attachment implementation for upload-backed media objects.
|
|
@@ -105,7 +109,71 @@ module MaxApiClient
|
|
|
105
109
|
end
|
|
106
110
|
|
|
107
111
|
def to_h
|
|
108
|
-
{ type: "share", payload: { url:, token: } }
|
|
112
|
+
{ type: "share", payload: { url:, token: }.compact }
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Attachment wrapper for contact cards.
|
|
117
|
+
class ContactAttachment < Attachment
|
|
118
|
+
attr_reader :name, :contact_id, :vcf_info, :vcf_phone
|
|
119
|
+
|
|
120
|
+
def initialize(name: nil, contact_id: nil, vcf_info: nil, vcf_phone: nil)
|
|
121
|
+
super()
|
|
122
|
+
@name = name
|
|
123
|
+
@contact_id = contact_id
|
|
124
|
+
@vcf_info = vcf_info
|
|
125
|
+
@vcf_phone = vcf_phone
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def to_h
|
|
129
|
+
{ type: "contact", payload: { name:, contact_id:, vcf_info:, vcf_phone: }.compact }
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Attachment wrapper for an inline keyboard: an array of button rows.
|
|
134
|
+
class InlineKeyboardAttachment < Attachment
|
|
135
|
+
attr_reader :buttons
|
|
136
|
+
|
|
137
|
+
def initialize(buttons:)
|
|
138
|
+
super()
|
|
139
|
+
@buttons = buttons
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def to_h
|
|
143
|
+
{ type: "inline_keyboard", payload: { buttons: } }
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Builders for inline keyboard buttons.
|
|
148
|
+
module Button
|
|
149
|
+
module_function
|
|
150
|
+
|
|
151
|
+
def callback(text, payload)
|
|
152
|
+
{ type: "callback", text:, payload: }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def link(text, url)
|
|
156
|
+
{ type: "link", text:, url: }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def message(text)
|
|
160
|
+
{ type: "message", text: }
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def request_contact(text)
|
|
164
|
+
{ type: "request_contact", text: }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def request_geo_location(text, quick: nil)
|
|
168
|
+
{ type: "request_geo_location", text:, quick: }.compact
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def open_app(text, web_app, payload: nil, contact_id: nil)
|
|
172
|
+
{ type: "open_app", text:, web_app:, payload:, contact_id: }.compact
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def clipboard(text, payload)
|
|
176
|
+
{ type: "clipboard", text:, payload: }
|
|
109
177
|
end
|
|
110
178
|
end
|
|
111
179
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MaxApiClient
|
|
4
|
+
# Builds a TLS trust store from the system defaults and an optional CA file.
|
|
5
|
+
class CertificateStore
|
|
6
|
+
def self.configure(http, ca_file:, verify_ssl:)
|
|
7
|
+
http.use_ssl = true
|
|
8
|
+
http.verify_mode = verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
|
9
|
+
http.cert_store = build(ca_file:) if verify_ssl
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.build(ca_file:)
|
|
13
|
+
OpenSSL::X509::Store.new.tap do |store|
|
|
14
|
+
store.set_default_paths
|
|
15
|
+
store.add_file(ca_file) if ca_file
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
module MaxApiClient
|
|
4
4
|
# HTTP transport client responsible for authenticated API requests.
|
|
5
5
|
class Client
|
|
6
|
-
DEFAULT_BASE_URL = "https://platform-
|
|
6
|
+
DEFAULT_BASE_URL = "https://platform-api2.max.ru"
|
|
7
|
+
DEFAULT_CA_FILE = File.expand_path("../../certs/russian_trusted_root_ca.pem", __dir__)
|
|
7
8
|
REQUEST_CLASSES = {
|
|
8
9
|
get: Net::HTTP::Get,
|
|
9
10
|
post: Net::HTTP::Post,
|
|
@@ -12,15 +13,18 @@ module MaxApiClient
|
|
|
12
13
|
delete: Net::HTTP::Delete
|
|
13
14
|
}.freeze
|
|
14
15
|
|
|
15
|
-
attr_reader :token, :base_url
|
|
16
|
+
attr_reader :token, :base_url, :ca_file, :verify_ssl
|
|
16
17
|
|
|
17
18
|
# rubocop:disable Metrics/ParameterLists
|
|
18
|
-
def initialize(token:, base_url: DEFAULT_BASE_URL, adapter: nil, open_timeout: nil, read_timeout: nil,
|
|
19
|
+
def initialize(token:, base_url: DEFAULT_BASE_URL, adapter: nil, open_timeout: nil, read_timeout: nil,
|
|
20
|
+
ca_file: DEFAULT_CA_FILE, verify_ssl: true, logger: nil)
|
|
19
21
|
@token = token
|
|
20
22
|
@base_url = base_url
|
|
21
23
|
@adapter = adapter
|
|
22
24
|
@open_timeout = open_timeout
|
|
23
25
|
@read_timeout = read_timeout
|
|
26
|
+
@ca_file = ca_file
|
|
27
|
+
@verify_ssl = verify_ssl
|
|
24
28
|
@logger = logger || MaxApiClient.logger
|
|
25
29
|
end
|
|
26
30
|
# rubocop:enable Metrics/ParameterLists
|
|
@@ -82,7 +86,7 @@ module MaxApiClient
|
|
|
82
86
|
|
|
83
87
|
params = URI.decode_www_form(String(uri.query))
|
|
84
88
|
query.to_h.each do |key, value|
|
|
85
|
-
next if value.nil?
|
|
89
|
+
next if value.nil?
|
|
86
90
|
|
|
87
91
|
params << [key.to_s, value.to_s]
|
|
88
92
|
end
|
|
@@ -122,10 +126,13 @@ module MaxApiClient
|
|
|
122
126
|
end
|
|
123
127
|
|
|
124
128
|
def configured_http(uri, open_timeout:, read_timeout:)
|
|
129
|
+
open_timeout ||= @open_timeout
|
|
130
|
+
read_timeout ||= @read_timeout
|
|
131
|
+
|
|
125
132
|
Net::HTTP.new(uri.host, uri.port).tap do |http|
|
|
126
|
-
http
|
|
127
|
-
http.open_timeout = open_timeout
|
|
128
|
-
http.read_timeout = read_timeout
|
|
133
|
+
CertificateStore.configure(http, ca_file:, verify_ssl:) if uri.scheme == "https"
|
|
134
|
+
http.open_timeout = open_timeout if open_timeout
|
|
135
|
+
http.read_timeout = read_timeout if read_timeout
|
|
129
136
|
end
|
|
130
137
|
end
|
|
131
138
|
|
|
@@ -16,11 +16,12 @@ module MaxApiClient
|
|
|
16
16
|
].freeze
|
|
17
17
|
|
|
18
18
|
# rubocop:disable Metrics/ParameterLists
|
|
19
|
-
def initialize(api, types: [], marker: nil,
|
|
20
|
-
read_timeout: nil)
|
|
19
|
+
def initialize(api, types: [], marker: nil, limit: nil, timeout: DEFAULT_TIMEOUT,
|
|
20
|
+
retry_interval: DEFAULT_RETRY_INTERVAL, read_timeout: nil)
|
|
21
21
|
@api = api
|
|
22
22
|
@types = types
|
|
23
23
|
@marker = marker
|
|
24
|
+
@limit = limit
|
|
24
25
|
@timeout = timeout
|
|
25
26
|
@retry_interval = retry_interval
|
|
26
27
|
@read_timeout = read_timeout || (timeout.to_i + READ_TIMEOUT_PADDING)
|
|
@@ -63,12 +64,13 @@ module MaxApiClient
|
|
|
63
64
|
|
|
64
65
|
private
|
|
65
66
|
|
|
66
|
-
attr_reader :api, :types, :marker, :timeout, :retry_interval, :read_timeout
|
|
67
|
+
attr_reader :api, :types, :marker, :limit, :timeout, :retry_interval, :read_timeout
|
|
67
68
|
|
|
68
69
|
def fetch_updates
|
|
69
70
|
api.raw.subscriptions.get_updates(
|
|
70
71
|
types: normalize_types(types),
|
|
71
72
|
marker:,
|
|
73
|
+
limit:,
|
|
72
74
|
timeout:,
|
|
73
75
|
read_timeout:
|
|
74
76
|
)
|
|
@@ -87,9 +89,9 @@ module MaxApiClient
|
|
|
87
89
|
end
|
|
88
90
|
|
|
89
91
|
def normalize_types(value)
|
|
90
|
-
|
|
92
|
+
value = value.join(",") if value.is_a?(Array)
|
|
91
93
|
|
|
92
|
-
value
|
|
94
|
+
value unless value.to_s.empty?
|
|
93
95
|
end
|
|
94
96
|
end
|
|
95
97
|
end
|
|
@@ -17,6 +17,10 @@ module MaxApiClient
|
|
|
17
17
|
@messages ||= build_api(MessagesApi)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
def comments
|
|
21
|
+
@comments ||= build_api(CommentsApi)
|
|
22
|
+
end
|
|
23
|
+
|
|
20
24
|
def subscriptions
|
|
21
25
|
@subscriptions ||= build_api(SubscriptionsApi)
|
|
22
26
|
end
|
|
@@ -40,13 +44,19 @@ module MaxApiClient
|
|
|
40
44
|
end
|
|
41
45
|
# rubocop:enable Naming/AccessorMethodName
|
|
42
46
|
|
|
47
|
+
# PATCH /me is absent from the current API schema; prefer #edit_my_commands.
|
|
43
48
|
def edit_my_info(**extra)
|
|
44
49
|
patch("me", body: extra)
|
|
45
50
|
end
|
|
51
|
+
|
|
52
|
+
def edit_my_commands(commands:)
|
|
53
|
+
patch("me/commands", body: { commands: })
|
|
54
|
+
end
|
|
46
55
|
end
|
|
47
56
|
|
|
48
57
|
# Raw chat management endpoints.
|
|
49
58
|
class ChatsApi < BaseApi
|
|
59
|
+
# GET /chats is no longer supported by the API since June 2026.
|
|
50
60
|
def get_all(**extra)
|
|
51
61
|
get("chats", query: extra)
|
|
52
62
|
end
|
|
@@ -55,6 +65,7 @@ module MaxApiClient
|
|
|
55
65
|
get("chats/{chat_id}", path_params: { chat_id: })
|
|
56
66
|
end
|
|
57
67
|
|
|
68
|
+
# Lookup by public link is absent from the current API schema.
|
|
58
69
|
def get_by_link(chat_link:)
|
|
59
70
|
get("chats/{chat_link}", path_params: { chat_link: })
|
|
60
71
|
end
|
|
@@ -71,6 +82,15 @@ module MaxApiClient
|
|
|
71
82
|
get("chats/{chat_id}/members/admins", path_params: { chat_id: })
|
|
72
83
|
end
|
|
73
84
|
|
|
85
|
+
def add_chat_admins(chat_id:, admins:)
|
|
86
|
+
post("chats/{chat_id}/members/admins", path_params: { chat_id: }, body: { admins: })
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def remove_chat_admin(chat_id:, user_id:)
|
|
90
|
+
delete("chats/{chat_id}/members/admins/{user_id}", path_params: { chat_id:, user_id: })
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# POST /chats/{chat_id}/members was removed from the API on 2026-09-30.
|
|
74
94
|
def add_chat_members(chat_id:, user_ids:)
|
|
75
95
|
post("chats/{chat_id}/members", path_params: { chat_id: }, body: { user_ids: })
|
|
76
96
|
end
|
|
@@ -80,7 +100,7 @@ module MaxApiClient
|
|
|
80
100
|
end
|
|
81
101
|
|
|
82
102
|
def remove_chat_member(chat_id:, user_id:, block: nil)
|
|
83
|
-
delete("chats/{chat_id}/members", path_params: { chat_id: },
|
|
103
|
+
delete("chats/{chat_id}/members", path_params: { chat_id: }, query: compact_nil(user_id:, block:))
|
|
84
104
|
end
|
|
85
105
|
|
|
86
106
|
def get_pinned_message(chat_id:)
|
|
@@ -108,22 +128,30 @@ module MaxApiClient
|
|
|
108
128
|
class MessagesApi < BaseApi
|
|
109
129
|
ATTACHMENT_NOT_READY_CODE = "attachment.not.ready"
|
|
110
130
|
ATTACHMENT_NOT_READY_DELAY = 1
|
|
131
|
+
ATTACHMENT_NOT_READY_RETRIES = 3
|
|
111
132
|
|
|
112
133
|
def get(**query)
|
|
113
134
|
super("messages", query:)
|
|
114
135
|
end
|
|
115
136
|
|
|
116
137
|
def get_by_id(message_id:)
|
|
117
|
-
get
|
|
138
|
+
call_api(:get, "messages/{message_id}", path_params: { message_id: })
|
|
118
139
|
end
|
|
119
140
|
|
|
120
141
|
def send(chat_id: nil, user_id: nil, disable_link_preview: nil, **body)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
142
|
+
attempt = 0
|
|
143
|
+
|
|
144
|
+
begin
|
|
145
|
+
post("messages", query: compact_nil(chat_id:, user_id:, disable_link_preview:), body:)
|
|
146
|
+
rescue ApiError => e
|
|
147
|
+
raise unless e.code == ATTACHMENT_NOT_READY_CODE
|
|
124
148
|
|
|
125
|
-
|
|
126
|
-
|
|
149
|
+
attempt += 1
|
|
150
|
+
raise if attempt >= ATTACHMENT_NOT_READY_RETRIES
|
|
151
|
+
|
|
152
|
+
sleep(ATTACHMENT_NOT_READY_DELAY * (2**(attempt - 1)))
|
|
153
|
+
retry
|
|
154
|
+
end
|
|
127
155
|
end
|
|
128
156
|
|
|
129
157
|
def edit(message_id:, **body)
|
|
@@ -134,8 +162,36 @@ module MaxApiClient
|
|
|
134
162
|
super("messages", query: { message_id: })
|
|
135
163
|
end
|
|
136
164
|
|
|
137
|
-
def
|
|
138
|
-
|
|
165
|
+
def get_video_info(video_token:)
|
|
166
|
+
call_api(:get, "videos/{video_token}", path_params: { video_token: })
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def answer_on_callback(callback_id:, disable_link_preview: nil, **body)
|
|
170
|
+
post("answers", query: compact_nil(callback_id:, disable_link_preview:), body:)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Raw endpoints for comments under channel posts.
|
|
175
|
+
class CommentsApi < BaseApi
|
|
176
|
+
def get(message_id:, **query)
|
|
177
|
+
super("messages/{message_id}/comments", path_params: { message_id: }, query:)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def get_by_id(message_id:, comment_id:)
|
|
181
|
+
call_api(:get, "messages/{message_id}/comments/{comment_id}", path_params: { message_id:, comment_id: })
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def send(message_id:, disable_link_preview: nil, **body)
|
|
185
|
+
post("messages/{message_id}/comments", path_params: { message_id: },
|
|
186
|
+
query: compact_nil(disable_link_preview:), body:)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def edit(message_id:, comment_id:, **body)
|
|
190
|
+
put("messages/{message_id}/comments", path_params: { message_id: }, query: { comment_id: }, body:)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def delete(message_id:, comment_id:)
|
|
194
|
+
super("messages/{message_id}/comments", path_params: { message_id: }, query: { comment_id: })
|
|
139
195
|
end
|
|
140
196
|
end
|
|
141
197
|
|
data/lib/max_api_client.rb
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "net/http"
|
|
5
|
+
require "openssl"
|
|
5
6
|
require "uri"
|
|
6
7
|
require "securerandom"
|
|
7
8
|
|
|
8
9
|
require_relative "max_api_client/version"
|
|
9
10
|
require_relative "max_api_client/error"
|
|
11
|
+
require_relative "max_api_client/certificate_store"
|
|
10
12
|
require_relative "max_api_client/client"
|
|
11
13
|
require_relative "max_api_client/base_api"
|
|
12
14
|
require_relative "max_api_client/raw_api"
|
data/sig/max_api_client.rbs
CHANGED
|
@@ -17,9 +17,12 @@ module MaxApiClient
|
|
|
17
17
|
|
|
18
18
|
class Client
|
|
19
19
|
DEFAULT_BASE_URL: String
|
|
20
|
+
DEFAULT_CA_FILE: String
|
|
20
21
|
attr_reader token: String
|
|
21
22
|
attr_reader base_url: String
|
|
22
|
-
|
|
23
|
+
attr_reader ca_file: String?
|
|
24
|
+
attr_reader verify_ssl: bool
|
|
25
|
+
def initialize: (token: String, ?base_url: String, ?adapter: untyped, ?open_timeout: Numeric?, ?read_timeout: Numeric?, ?ca_file: String?, ?verify_ssl: bool, ?logger: untyped) -> void
|
|
23
26
|
def call: (method: Symbol | String, ?path: String?, ?query: untyped, ?body: untyped, ?path_params: untyped, ?headers: untyped, ?url: String?, ?raw_body: untyped, ?parse_json: bool) -> Hash[Symbol, untyped]
|
|
24
27
|
end
|
|
25
28
|
|
|
@@ -33,6 +36,7 @@ module MaxApiClient
|
|
|
33
36
|
def bots: () -> BotsApi
|
|
34
37
|
def chats: () -> ChatsApi
|
|
35
38
|
def messages: () -> MessagesApi
|
|
39
|
+
def comments: () -> CommentsApi
|
|
36
40
|
def subscriptions: () -> SubscriptionsApi
|
|
37
41
|
def uploads: () -> UploadsApi
|
|
38
42
|
end
|
|
@@ -40,6 +44,7 @@ module MaxApiClient
|
|
|
40
44
|
class BotsApi
|
|
41
45
|
def get_my_info: () -> untyped
|
|
42
46
|
def edit_my_info: (untyped extra) -> untyped
|
|
47
|
+
def edit_my_commands: (commands: Array[bot_command]) -> untyped
|
|
43
48
|
end
|
|
44
49
|
|
|
45
50
|
class ChatsApi
|
|
@@ -49,6 +54,8 @@ module MaxApiClient
|
|
|
49
54
|
def edit: (chat_id: Integer, **untyped extra) -> untyped
|
|
50
55
|
def get_chat_membership: (chat_id: Integer) -> untyped
|
|
51
56
|
def get_chat_admins: (chat_id: Integer) -> untyped
|
|
57
|
+
def add_chat_admins: (chat_id: Integer, admins: Array[Hash[Symbol | String, untyped]]) -> untyped
|
|
58
|
+
def remove_chat_admin: (chat_id: Integer, user_id: Integer) -> untyped
|
|
52
59
|
def add_chat_members: (chat_id: Integer, user_ids: Array[Integer]) -> untyped
|
|
53
60
|
def get_chat_members: (chat_id: Integer, **untyped query) -> untyped
|
|
54
61
|
def remove_chat_member: (chat_id: Integer, user_id: Integer, ?block: bool?) -> untyped
|
|
@@ -65,7 +72,16 @@ module MaxApiClient
|
|
|
65
72
|
def send: (?chat_id: Integer?, ?user_id: Integer?, ?disable_link_preview: bool?, **untyped body) -> untyped
|
|
66
73
|
def edit: (message_id: String, **untyped body) -> untyped
|
|
67
74
|
def delete: (message_id: String) -> untyped
|
|
68
|
-
def
|
|
75
|
+
def get_video_info: (video_token: String) -> untyped
|
|
76
|
+
def answer_on_callback: (callback_id: String, ?disable_link_preview: bool?, **untyped body) -> untyped
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class CommentsApi
|
|
80
|
+
def get: (message_id: String, **untyped query) -> untyped
|
|
81
|
+
def get_by_id: (message_id: String, comment_id: String) -> untyped
|
|
82
|
+
def send: (message_id: String, ?disable_link_preview: bool?, **untyped body) -> untyped
|
|
83
|
+
def edit: (message_id: String, comment_id: String, **untyped body) -> untyped
|
|
84
|
+
def delete: (message_id: String, comment_id: String) -> untyped
|
|
69
85
|
end
|
|
70
86
|
|
|
71
87
|
class SubscriptionsApi
|
|
@@ -81,6 +97,7 @@ module MaxApiClient
|
|
|
81
97
|
|
|
82
98
|
class Attachment
|
|
83
99
|
def to_h: () -> Hash[Symbol, untyped]
|
|
100
|
+
def to_json: (*untyped args) -> String
|
|
84
101
|
end
|
|
85
102
|
|
|
86
103
|
class ImageAttachment < Attachment
|
|
@@ -104,6 +121,22 @@ module MaxApiClient
|
|
|
104
121
|
class ShareAttachment < Attachment
|
|
105
122
|
end
|
|
106
123
|
|
|
124
|
+
class ContactAttachment < Attachment
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
class InlineKeyboardAttachment < Attachment
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
module Button
|
|
131
|
+
def self.callback: (String text, String payload) -> Hash[Symbol, untyped]
|
|
132
|
+
def self.link: (String text, String url) -> Hash[Symbol, untyped]
|
|
133
|
+
def self.message: (String text) -> Hash[Symbol, untyped]
|
|
134
|
+
def self.request_contact: (String text) -> Hash[Symbol, untyped]
|
|
135
|
+
def self.request_geo_location: (String text, ?quick: bool?) -> Hash[Symbol, untyped]
|
|
136
|
+
def self.open_app: (String text, String web_app, ?payload: String?, ?contact_id: Integer?) -> Hash[Symbol, untyped]
|
|
137
|
+
def self.clipboard: (String text, String payload) -> Hash[Symbol, untyped]
|
|
138
|
+
end
|
|
139
|
+
|
|
107
140
|
class Upload
|
|
108
141
|
def initialize: (Api) -> void
|
|
109
142
|
end
|
|
@@ -112,7 +145,7 @@ module MaxApiClient
|
|
|
112
145
|
DEFAULT_TIMEOUT: Integer
|
|
113
146
|
DEFAULT_RETRY_INTERVAL: Integer
|
|
114
147
|
READ_TIMEOUT_PADDING: Integer
|
|
115
|
-
def initialize: (Api api, ?types: Array[String] | String, ?marker: Integer?, ?timeout: Integer, ?retry_interval: Integer, ?read_timeout: Numeric?) -> void
|
|
148
|
+
def initialize: (Api api, ?types: Array[String] | String, ?marker: Integer?, ?limit: Integer?, ?timeout: Integer, ?retry_interval: Integer, ?read_timeout: Numeric?) -> void
|
|
116
149
|
def each: () { (untyped update) -> void } -> self
|
|
117
150
|
def each: () -> Enumerator[untyped, self]
|
|
118
151
|
def stop: () -> bool
|
|
@@ -123,9 +156,10 @@ module MaxApiClient
|
|
|
123
156
|
attr_reader raw: RawApi
|
|
124
157
|
attr_reader upload: Upload
|
|
125
158
|
attr_reader client: Client
|
|
126
|
-
def initialize: (token: String, ?base_url: String, ?adapter: untyped, ?open_timeout: Numeric?, ?read_timeout: Numeric?, ?logger: untyped) -> void
|
|
159
|
+
def initialize: (token: String, ?base_url: String, ?adapter: untyped, ?open_timeout: Numeric?, ?read_timeout: Numeric?, ?ca_file: String?, ?verify_ssl: bool, ?logger: untyped) -> void
|
|
127
160
|
def get_my_info: () -> untyped
|
|
128
161
|
def edit_my_info: (untyped extra) -> untyped
|
|
162
|
+
def edit_my_commands: (Array[bot_command] commands) -> untyped
|
|
129
163
|
def set_my_commands: (Array[bot_command] commands) -> untyped
|
|
130
164
|
def delete_my_commands: () -> untyped
|
|
131
165
|
def get_all_chats: (?untyped extra) -> untyped
|
|
@@ -134,6 +168,8 @@ module MaxApiClient
|
|
|
134
168
|
def edit_chat_info: (Integer chat_id, untyped extra) -> untyped
|
|
135
169
|
def get_chat_membership: (Integer chat_id) -> untyped
|
|
136
170
|
def get_chat_admins: (Integer chat_id) -> untyped
|
|
171
|
+
def add_chat_admins: (Integer chat_id, Array[Hash[Symbol | String, untyped]] admins) -> untyped
|
|
172
|
+
def remove_chat_admin: (Integer chat_id, Integer user_id) -> untyped
|
|
137
173
|
def add_chat_members: (Integer chat_id, Array[Integer] user_ids) -> untyped
|
|
138
174
|
def get_chat_members: (Integer chat_id, ?untyped extra) -> untyped
|
|
139
175
|
def remove_chat_member: (Integer chat_id, Integer user_id, ?block: bool?) -> untyped
|
|
@@ -147,13 +183,19 @@ module MaxApiClient
|
|
|
147
183
|
def get_messages: (Integer chat_id, ?untyped extra) -> untyped
|
|
148
184
|
def get_message: (String message_id) -> untyped
|
|
149
185
|
def edit_message: (String message_id, ?untyped extra) -> untyped
|
|
150
|
-
def delete_message: (String message_id
|
|
186
|
+
def delete_message: (String message_id) -> untyped
|
|
187
|
+
def get_video_info: (String video_token) -> untyped
|
|
188
|
+
def get_comments: (String message_id, ?untyped extra) -> untyped
|
|
189
|
+
def get_comment: (String message_id, String comment_id) -> untyped
|
|
190
|
+
def send_comment: (String message_id, String text, ?untyped extra) -> untyped
|
|
191
|
+
def edit_comment: (String message_id, String comment_id, ?untyped extra) -> untyped
|
|
192
|
+
def delete_comment: (String message_id, String comment_id) -> untyped
|
|
151
193
|
def answer_on_callback: (String callback_id, ?untyped extra) -> untyped
|
|
152
194
|
def get_subscriptions: () -> untyped
|
|
153
195
|
def subscribe: (String url, ?update_types: Array[String] | String?, ?secret: String?) -> untyped
|
|
154
196
|
def unsubscribe: (String url) -> untyped
|
|
155
|
-
def poll_updates: (?Array[String] | String types, ?marker: Integer?, ?timeout: Integer, ?retry_interval: Integer, ?read_timeout: Numeric?) { (untyped update) -> void } -> Polling
|
|
156
|
-
def poll_updates: (?Array[String] | String types, ?marker: Integer?, ?timeout: Integer, ?retry_interval: Integer, ?read_timeout: Numeric?) -> Enumerator[untyped, Polling]
|
|
197
|
+
def poll_updates: (?Array[String] | String types, ?marker: Integer?, ?limit: Integer?, ?timeout: Integer, ?retry_interval: Integer, ?read_timeout: Numeric?) { (untyped update) -> void } -> Polling
|
|
198
|
+
def poll_updates: (?Array[String] | String types, ?marker: Integer?, ?limit: Integer?, ?timeout: Integer, ?retry_interval: Integer, ?read_timeout: Numeric?) -> Enumerator[untyped, Polling]
|
|
157
199
|
def upload_image: (Hash[Symbol, untyped] options) -> ImageAttachment
|
|
158
200
|
def upload_video: (Hash[Symbol, untyped] options) -> VideoAttachment
|
|
159
201
|
def upload_audio: (Hash[Symbol, untyped] options) -> AudioAttachment
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: max_api_client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexandr Zimin
|
|
@@ -23,10 +23,12 @@ files:
|
|
|
23
23
|
- LICENSE.txt
|
|
24
24
|
- README.md
|
|
25
25
|
- Rakefile
|
|
26
|
+
- certs/russian_trusted_root_ca.pem
|
|
26
27
|
- lib/max_api_client.rb
|
|
27
28
|
- lib/max_api_client/api.rb
|
|
28
29
|
- lib/max_api_client/attachments.rb
|
|
29
30
|
- lib/max_api_client/base_api.rb
|
|
31
|
+
- lib/max_api_client/certificate_store.rb
|
|
30
32
|
- lib/max_api_client/client.rb
|
|
31
33
|
- lib/max_api_client/error.rb
|
|
32
34
|
- lib/max_api_client/polling.rb
|