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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9fde75c48daf67e3c6daac640e467b02b31601c5abef3e92ed3fd14f83509d28
4
- data.tar.gz: 53f4dd194924f9853a98fcc60789bd7678320bedb73d7a0dbe35bd2bb1196817
3
+ metadata.gz: 7d6578ded53eb4717c32d40728c217f163db0e8149bc813b084c6920692201dd
4
+ data.tar.gz: 338a01c773c1be6502cd37161bf1b11011a08717fd006055db99e9ff7466716c
5
5
  SHA512:
6
- metadata.gz: 0bd30a89346e414d46dace23dfe18745a7e54ecec52024a3f48d7ba12e97b8e5df09eca5cad6be05e4ac0488c99b679f1671d85d132d09c7e9eb0b10197729cd
7
- data.tar.gz: b9bedc2313023176b3b92e21c34c97e1d012f13afb1c8dfaa322c4e8c9125f61be75c33476b84e0dcd3ea6cf80a9c5ce197c6b809ff1a5d1d218df858f2af1e9
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
- - `edit_my_info(**extra)`
107
+ - `edit_my_commands(commands)`
108
108
  - `set_my_commands(commands)`
109
109
  - `delete_my_commands`
110
110
 
111
- `set_my_commands(commands)` это короткий хелпер над `edit_my_info(commands: ...)`.
112
- Он принимает массив команд и отправляет его в поле `commands` профиля бота.
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
- - `add_chat_members(chat_id, user_ids)`
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, **extra)`
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
  ## Лицензия
@@ -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
- edit_my_info(commands:)
44
+ edit_my_commands(commands)
39
45
  end
40
46
  # rubocop:enable Naming/AccessorMethodName
41
47
 
42
48
  def delete_my_commands
43
- edit_my_info(commands: [])
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, **extra)
123
- raw.messages.delete(message_id:, **extra)
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
- def poll_updates(types = [], marker: nil, timeout: Polling::DEFAULT_TIMEOUT,
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
@@ -86,7 +86,7 @@ module MaxApiClient
86
86
 
87
87
  params = URI.decode_www_form(String(uri.query))
88
88
  query.to_h.each do |key, value|
89
- next if value.nil? || value == false
89
+ next if value.nil?
90
90
 
91
91
  params << [key.to_s, value.to_s]
92
92
  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, timeout: DEFAULT_TIMEOUT, retry_interval: DEFAULT_RETRY_INTERVAL,
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
- return value.join(",") if value.is_a?(Array)
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: }, body: compact_nil(user_id:, block:))
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("messages/{message_id}", path_params: { message_id: })
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
- post("messages", query: compact_nil(chat_id:, user_id:, disable_link_preview:), body:)
122
- rescue ApiError => e
123
- raise unless e.code == ATTACHMENT_NOT_READY_CODE
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
- sleep(ATTACHMENT_NOT_READY_DELAY)
126
- send(chat_id:, user_id:, disable_link_preview:, **body)
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 answer_on_callback(callback_id:, **body)
138
- post("answers", query: { callback_id: }, body:)
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
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Root namespace for gem version information.
4
4
  module MaxApiClient
5
- VERSION = "0.1.3"
5
+ VERSION = "0.2.0"
6
6
  end
@@ -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 answer_on_callback: (callback_id: String, **untyped body) -> untyped
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, ?untyped extra) -> untyped
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
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.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexandr Zimin