max_api_client 0.1.3 → 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 +18 -0
- data/README.md +75 -17
- data/lib/max_api_client/api.rb +54 -5
- data/lib/max_api_client/attachments.rb +69 -1
- data/lib/max_api_client/client.rb +1 -1
- 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/sig/max_api_client.rbs +44 -5
- metadata +1 -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,5 +1,23 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
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
|
+
|
|
3
21
|
## [0.1.3] - 2026-07-16
|
|
4
22
|
|
|
5
23
|
- Switched the default API endpoint to `https://platform-api2.max.ru`.
|
data/README.md
CHANGED
|
@@ -104,12 +104,15 @@ api = MaxApiClient::Api.new(
|
|
|
104
104
|
Методы доступные через `MaxApiClient::Api`, порт официального клиента <https://github.com/max-messenger/max-bot-api-client-ts>, названия сохранены, poller собственный:
|
|
105
105
|
|
|
106
106
|
- `get_my_info`
|
|
107
|
-
- `
|
|
107
|
+
- `edit_my_commands(commands)`
|
|
108
108
|
- `set_my_commands(commands)`
|
|
109
109
|
- `delete_my_commands`
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
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`.
|
|
113
116
|
|
|
114
117
|
Ожидается массив хэшей с данными команды, например:
|
|
115
118
|
|
|
@@ -125,27 +128,28 @@ api.set_my_commands([
|
|
|
125
128
|
Соответствующие HTTP-маршруты:
|
|
126
129
|
|
|
127
130
|
- `GET /me`
|
|
128
|
-
- `PATCH /me`
|
|
131
|
+
- `PATCH /me/commands`
|
|
129
132
|
|
|
130
133
|
Типовые сценарии:
|
|
131
134
|
|
|
132
135
|
- получить текущий профиль бота;
|
|
133
|
-
- обновить имя, описание, аватар и команды бота;
|
|
134
136
|
- опубликовать или очистить подсказки команд для пользователей.
|
|
135
137
|
|
|
138
|
+
Устаревший метод `edit_my_info(**extra)` (`PATCH /me`) оставлен для совместимости
|
|
139
|
+
и выводит предупреждение: этого маршрута нет в актуальной схеме API.
|
|
140
|
+
|
|
136
141
|
### Методы чатов
|
|
137
142
|
|
|
138
143
|
Методы Ruby, доступные через `MaxApiClient::Api`:
|
|
139
144
|
|
|
140
|
-
- `get_all_chats(**extra)`
|
|
141
145
|
- `get_chat(chat_id)`
|
|
142
|
-
- `get_chat_by_link(chat_link)`
|
|
143
146
|
- `edit_chat_info(chat_id, **extra)`
|
|
144
147
|
- `get_chat_membership(chat_id)`
|
|
145
148
|
- `get_chat_admins(chat_id)`
|
|
146
|
-
- `
|
|
149
|
+
- `add_chat_admins(chat_id, admins)`
|
|
150
|
+
- `remove_chat_admin(chat_id, user_id)`
|
|
147
151
|
- `get_chat_members(chat_id, **extra)`
|
|
148
|
-
- `remove_chat_member(chat_id, user_id)`
|
|
152
|
+
- `remove_chat_member(chat_id, user_id, block: nil)`
|
|
149
153
|
- `get_pinned_message(chat_id)`
|
|
150
154
|
- `pin_message(chat_id, message_id, **extra)`
|
|
151
155
|
- `unpin_message(chat_id)`
|
|
@@ -154,13 +158,12 @@ api.set_my_commands([
|
|
|
154
158
|
|
|
155
159
|
Соответствующие HTTP-маршруты:
|
|
156
160
|
|
|
157
|
-
- `GET /chats`
|
|
158
161
|
- `GET /chats/{chat_id}`
|
|
159
|
-
- `GET /chats/{chat_link}`
|
|
160
162
|
- `PATCH /chats/{chat_id}`
|
|
161
163
|
- `GET /chats/{chat_id}/members/me`
|
|
162
164
|
- `GET /chats/{chat_id}/members/admins`
|
|
163
|
-
- `POST /chats/{chat_id}/members`
|
|
165
|
+
- `POST /chats/{chat_id}/members/admins`
|
|
166
|
+
- `DELETE /chats/{chat_id}/members/admins/{user_id}`
|
|
164
167
|
- `GET /chats/{chat_id}/members`
|
|
165
168
|
- `DELETE /chats/{chat_id}/members`
|
|
166
169
|
- `GET /chats/{chat_id}/pin`
|
|
@@ -171,14 +174,29 @@ api.set_my_commands([
|
|
|
171
174
|
|
|
172
175
|
Типовые сценарии:
|
|
173
176
|
|
|
174
|
-
- получить
|
|
175
|
-
- найти чат по идентификатору или публичной ссылке;
|
|
177
|
+
- получить чат по идентификатору;
|
|
176
178
|
- изменить заголовок, иконку и метаданные чата;
|
|
177
179
|
- управлять участниками и администраторами;
|
|
178
180
|
- читать, устанавливать и снимать закреплённые сообщения;
|
|
179
181
|
- отправлять статус набора текста и другие действия отправителя;
|
|
180
182
|
- выходить из чата.
|
|
181
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
|
+
|
|
182
200
|
### Методы сообщений
|
|
183
201
|
|
|
184
202
|
Методы Ruby, доступные через `MaxApiClient::Api`:
|
|
@@ -188,7 +206,8 @@ api.set_my_commands([
|
|
|
188
206
|
- `get_messages(chat_id, **extra)`
|
|
189
207
|
- `get_message(message_id)`
|
|
190
208
|
- `edit_message(message_id, **extra)`
|
|
191
|
-
- `delete_message(message_id
|
|
209
|
+
- `delete_message(message_id)`
|
|
210
|
+
- `get_video_info(video_token)`
|
|
192
211
|
- `answer_on_callback(callback_id, **extra)`
|
|
193
212
|
|
|
194
213
|
Соответствующие HTTP-маршруты:
|
|
@@ -198,6 +217,7 @@ api.set_my_commands([
|
|
|
198
217
|
- `GET /messages/{message_id}`
|
|
199
218
|
- `PUT /messages`
|
|
200
219
|
- `DELETE /messages`
|
|
220
|
+
- `GET /videos/{video_token}`
|
|
201
221
|
- `POST /answers`
|
|
202
222
|
|
|
203
223
|
Поддерживаемые возможности:
|
|
@@ -206,7 +226,25 @@ api.set_my_commands([
|
|
|
206
226
|
- дополнительный payload для форматирования, reply-ссылок и вложений;
|
|
207
227
|
- редактирование и удаление сообщений;
|
|
208
228
|
- ответы на callback-кнопки;
|
|
209
|
-
- автоматический повтор
|
|
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`
|
|
210
248
|
|
|
211
249
|
### Методы подписок
|
|
212
250
|
|
|
@@ -215,7 +253,7 @@ api.set_my_commands([
|
|
|
215
253
|
- `get_subscriptions`
|
|
216
254
|
- `subscribe(url, update_types: nil, secret: nil)`
|
|
217
255
|
- `unsubscribe(url)`
|
|
218
|
-
- `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)`
|
|
219
257
|
|
|
220
258
|
Соответствующие HTTP-маршруты:
|
|
221
259
|
|
|
@@ -270,6 +308,25 @@ end
|
|
|
270
308
|
- `StickerAttachment`
|
|
271
309
|
- `LocationAttachment`
|
|
272
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`.
|
|
273
330
|
|
|
274
331
|
### Доступ к Raw API
|
|
275
332
|
|
|
@@ -338,6 +395,7 @@ bin/console
|
|
|
338
395
|
## Источники
|
|
339
396
|
|
|
340
397
|
- Официальная документация Max Bot API: <https://dev.max.ru/>
|
|
398
|
+
- OpenAPI-схема Max Bot API: <https://github.com/max-messenger/api-schema>
|
|
341
399
|
- TypeScript reference client: <https://github.com/max-messenger/max-bot-api-client-ts>
|
|
342
400
|
|
|
343
401
|
## Лицензия
|
data/lib/max_api_client/api.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
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
|
|
|
@@ -30,20 +31,26 @@ module MaxApiClient
|
|
|
30
31
|
# rubocop:enable Naming/AccessorMethodName
|
|
31
32
|
|
|
32
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")
|
|
33
35
|
raw.bots.edit_my_info(**extra)
|
|
34
36
|
end
|
|
35
37
|
|
|
38
|
+
def edit_my_commands(commands)
|
|
39
|
+
raw.bots.edit_my_commands(commands:)
|
|
40
|
+
end
|
|
41
|
+
|
|
36
42
|
# rubocop:disable Naming/AccessorMethodName
|
|
37
43
|
def set_my_commands(commands)
|
|
38
|
-
|
|
44
|
+
edit_my_commands(commands)
|
|
39
45
|
end
|
|
40
46
|
# rubocop:enable Naming/AccessorMethodName
|
|
41
47
|
|
|
42
48
|
def delete_my_commands
|
|
43
|
-
|
|
49
|
+
edit_my_commands([])
|
|
44
50
|
end
|
|
45
51
|
|
|
46
52
|
def get_all_chats(**extra)
|
|
53
|
+
warn_deprecated(:get_all_chats, "GET /chats is not supported by the API since June 2026")
|
|
47
54
|
raw.chats.get_all(**extra)
|
|
48
55
|
end
|
|
49
56
|
|
|
@@ -52,6 +59,7 @@ module MaxApiClient
|
|
|
52
59
|
end
|
|
53
60
|
|
|
54
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")
|
|
55
63
|
raw.chats.get_by_link(chat_link:)
|
|
56
64
|
end
|
|
57
65
|
|
|
@@ -67,7 +75,16 @@ module MaxApiClient
|
|
|
67
75
|
raw.chats.get_chat_admins(chat_id:)
|
|
68
76
|
end
|
|
69
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
|
+
|
|
70
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")
|
|
71
88
|
raw.chats.add_chat_members(chat_id:, user_ids:)
|
|
72
89
|
end
|
|
73
90
|
|
|
@@ -119,14 +136,38 @@ module MaxApiClient
|
|
|
119
136
|
raw.messages.edit(message_id:, **extra)
|
|
120
137
|
end
|
|
121
138
|
|
|
122
|
-
def delete_message(message_id
|
|
123
|
-
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:)
|
|
124
145
|
end
|
|
125
146
|
|
|
126
147
|
def answer_on_callback(callback_id, **extra)
|
|
127
148
|
raw.messages.answer_on_callback(callback_id:, **extra)
|
|
128
149
|
end
|
|
129
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
|
+
|
|
130
171
|
# rubocop:disable Naming/AccessorMethodName
|
|
131
172
|
def get_subscriptions
|
|
132
173
|
raw.subscriptions.get_subscriptions
|
|
@@ -141,12 +182,14 @@ module MaxApiClient
|
|
|
141
182
|
raw.subscriptions.unsubscribe(url:)
|
|
142
183
|
end
|
|
143
184
|
|
|
144
|
-
|
|
185
|
+
# rubocop:disable Metrics/ParameterLists
|
|
186
|
+
def poll_updates(types = [], marker: nil, limit: nil, timeout: Polling::DEFAULT_TIMEOUT,
|
|
145
187
|
retry_interval: Polling::DEFAULT_RETRY_INTERVAL, read_timeout: nil, &block)
|
|
146
188
|
poller = Polling.new(
|
|
147
189
|
self,
|
|
148
190
|
types:,
|
|
149
191
|
marker:,
|
|
192
|
+
limit:,
|
|
150
193
|
timeout:,
|
|
151
194
|
retry_interval:,
|
|
152
195
|
read_timeout:
|
|
@@ -156,6 +199,7 @@ module MaxApiClient
|
|
|
156
199
|
|
|
157
200
|
poller.each(&block)
|
|
158
201
|
end
|
|
202
|
+
# rubocop:enable Metrics/ParameterLists
|
|
159
203
|
|
|
160
204
|
def upload_image(options)
|
|
161
205
|
data = upload.image(**options)
|
|
@@ -189,8 +233,13 @@ module MaxApiClient
|
|
|
189
233
|
query.merge(key => normalize_types(query[key]))
|
|
190
234
|
end
|
|
191
235
|
|
|
236
|
+
def warn_deprecated(method_name, reason)
|
|
237
|
+
warn("[max_api_client] #{method_name} is deprecated: #{reason}", uplevel: 2)
|
|
238
|
+
end
|
|
239
|
+
|
|
192
240
|
def message_from(response)
|
|
193
241
|
response.fetch("message") { response.fetch(:message) }
|
|
194
242
|
end
|
|
195
243
|
end
|
|
244
|
+
# rubocop:enable Metrics/ClassLength
|
|
196
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
|
|
@@ -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/sig/max_api_client.rbs
CHANGED
|
@@ -36,6 +36,7 @@ module MaxApiClient
|
|
|
36
36
|
def bots: () -> BotsApi
|
|
37
37
|
def chats: () -> ChatsApi
|
|
38
38
|
def messages: () -> MessagesApi
|
|
39
|
+
def comments: () -> CommentsApi
|
|
39
40
|
def subscriptions: () -> SubscriptionsApi
|
|
40
41
|
def uploads: () -> UploadsApi
|
|
41
42
|
end
|
|
@@ -43,6 +44,7 @@ module MaxApiClient
|
|
|
43
44
|
class BotsApi
|
|
44
45
|
def get_my_info: () -> untyped
|
|
45
46
|
def edit_my_info: (untyped extra) -> untyped
|
|
47
|
+
def edit_my_commands: (commands: Array[bot_command]) -> untyped
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
class ChatsApi
|
|
@@ -52,6 +54,8 @@ module MaxApiClient
|
|
|
52
54
|
def edit: (chat_id: Integer, **untyped extra) -> untyped
|
|
53
55
|
def get_chat_membership: (chat_id: Integer) -> untyped
|
|
54
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
|
|
55
59
|
def add_chat_members: (chat_id: Integer, user_ids: Array[Integer]) -> untyped
|
|
56
60
|
def get_chat_members: (chat_id: Integer, **untyped query) -> untyped
|
|
57
61
|
def remove_chat_member: (chat_id: Integer, user_id: Integer, ?block: bool?) -> untyped
|
|
@@ -68,7 +72,16 @@ module MaxApiClient
|
|
|
68
72
|
def send: (?chat_id: Integer?, ?user_id: Integer?, ?disable_link_preview: bool?, **untyped body) -> untyped
|
|
69
73
|
def edit: (message_id: String, **untyped body) -> untyped
|
|
70
74
|
def delete: (message_id: String) -> untyped
|
|
71
|
-
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
|
|
72
85
|
end
|
|
73
86
|
|
|
74
87
|
class SubscriptionsApi
|
|
@@ -84,6 +97,7 @@ module MaxApiClient
|
|
|
84
97
|
|
|
85
98
|
class Attachment
|
|
86
99
|
def to_h: () -> Hash[Symbol, untyped]
|
|
100
|
+
def to_json: (*untyped args) -> String
|
|
87
101
|
end
|
|
88
102
|
|
|
89
103
|
class ImageAttachment < Attachment
|
|
@@ -107,6 +121,22 @@ module MaxApiClient
|
|
|
107
121
|
class ShareAttachment < Attachment
|
|
108
122
|
end
|
|
109
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
|
+
|
|
110
140
|
class Upload
|
|
111
141
|
def initialize: (Api) -> void
|
|
112
142
|
end
|
|
@@ -115,7 +145,7 @@ module MaxApiClient
|
|
|
115
145
|
DEFAULT_TIMEOUT: Integer
|
|
116
146
|
DEFAULT_RETRY_INTERVAL: Integer
|
|
117
147
|
READ_TIMEOUT_PADDING: Integer
|
|
118
|
-
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
|
|
119
149
|
def each: () { (untyped update) -> void } -> self
|
|
120
150
|
def each: () -> Enumerator[untyped, self]
|
|
121
151
|
def stop: () -> bool
|
|
@@ -129,6 +159,7 @@ module MaxApiClient
|
|
|
129
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
|
|
130
160
|
def get_my_info: () -> untyped
|
|
131
161
|
def edit_my_info: (untyped extra) -> untyped
|
|
162
|
+
def edit_my_commands: (Array[bot_command] commands) -> untyped
|
|
132
163
|
def set_my_commands: (Array[bot_command] commands) -> untyped
|
|
133
164
|
def delete_my_commands: () -> untyped
|
|
134
165
|
def get_all_chats: (?untyped extra) -> untyped
|
|
@@ -137,6 +168,8 @@ module MaxApiClient
|
|
|
137
168
|
def edit_chat_info: (Integer chat_id, untyped extra) -> untyped
|
|
138
169
|
def get_chat_membership: (Integer chat_id) -> untyped
|
|
139
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
|
|
140
173
|
def add_chat_members: (Integer chat_id, Array[Integer] user_ids) -> untyped
|
|
141
174
|
def get_chat_members: (Integer chat_id, ?untyped extra) -> untyped
|
|
142
175
|
def remove_chat_member: (Integer chat_id, Integer user_id, ?block: bool?) -> untyped
|
|
@@ -150,13 +183,19 @@ module MaxApiClient
|
|
|
150
183
|
def get_messages: (Integer chat_id, ?untyped extra) -> untyped
|
|
151
184
|
def get_message: (String message_id) -> untyped
|
|
152
185
|
def edit_message: (String message_id, ?untyped extra) -> untyped
|
|
153
|
-
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
|
|
154
193
|
def answer_on_callback: (String callback_id, ?untyped extra) -> untyped
|
|
155
194
|
def get_subscriptions: () -> untyped
|
|
156
195
|
def subscribe: (String url, ?update_types: Array[String] | String?, ?secret: String?) -> untyped
|
|
157
196
|
def unsubscribe: (String url) -> untyped
|
|
158
|
-
def poll_updates: (?Array[String] | String types, ?marker: Integer?, ?timeout: Integer, ?retry_interval: Integer, ?read_timeout: Numeric?) { (untyped update) -> void } -> Polling
|
|
159
|
-
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]
|
|
160
199
|
def upload_image: (Hash[Symbol, untyped] options) -> ImageAttachment
|
|
161
200
|
def upload_video: (Hash[Symbol, untyped] options) -> VideoAttachment
|
|
162
201
|
def upload_audio: (Hash[Symbol, untyped] options) -> AudioAttachment
|