maxapi-lite 0.2.0__tar.gz → 0.3.0__tar.gz

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.
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0
4
+
5
+ - Новый API-домен `platform-api2.max.ru`; параметры `base_url`, `ssl_context`, `connector`, `timeout` и `transfer_timeout` в `Bot` и `MaxApiClient`.
6
+ - Отдельная сессия без Authorization для upload/download, общий настроенный TLS и явное владение connector.
7
+ - Исправлен token из `/uploads` для video/audio; добавлены audio mapping и проверка размера файла до сетевого запроса.
8
+ - `download_file`: потоковая запись, проверка размера, докачка через Range/If-Range и атомарная замена готового файла.
9
+ - `get_video`, `extract_attachments`, фильтр `types` и согласованный HTTP timeout для `get_updates`.
10
+ - `FileTooLarge`, `AttachmentNotReady`, `RateLimited`; строковый код MAX доступен в `MaxApiError.api_code`, числовой HTTP status остаётся в `.code`.
11
+ - Опциональное ожидание обработки вложений через `wait_attachments=True` с общим сроком 60 секунд. Сетевые ошибки отправки и 429 автоматически не повторяются.
12
+ - Поле `type` во всех моделях кнопок, `build_keyboard`, `send_callback(message=...)`; `callback_id` передаётся в query.
13
+ - CI проверяет aiohttp 3.9.5 дополнительно к 3.8.4/3.8.6.
14
+
15
+ ## 0.2.0
16
+
17
+ Первый публичный релиз в виде отдельного пакета (ранее код жил внутри MaxGiveawayBot).
18
+
19
+ - `Bot`: `get_me`, `get_updates`, `send_message`, `edit_message`, `delete_message`, `get_message`
20
+ - закрепление: `pin_chat_message`, `pin_from_response`, `unpin_chat_message`
21
+ - чаты: `get_chat_by_id`, `get_chat_member`, `get_chat_members_batch`, `get_list_admin_chat`
22
+ - callback: `send_callback`
23
+ - медиа: `upload_file`, `create_media_attachment`, `send_message_with_media`, `edit_message_with_media`
24
+ - утилиты: `extract_message_id`, `build_link_button_attachment`
@@ -0,0 +1,299 @@
1
+ Metadata-Version: 2.5
2
+ Name: maxapi-lite
3
+ Version: 0.3.0
4
+ Summary: Lightweight async client for the MAX messenger Bot API, compatible with aiohttp 3.8
5
+ Project-URL: Homepage, https://github.com/kiriyk/maxapi-lite
6
+ Project-URL: Source, https://github.com/kiriyk/maxapi-lite
7
+ Project-URL: Issues, https://github.com/kiriyk/maxapi-lite/issues
8
+ Project-URL: Changelog, https://github.com/kiriyk/maxapi-lite/blob/main/CHANGELOG.md
9
+ Author: Kirill
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: aiohttp,asyncio,bot,bot-api,max,max.ru,messenger
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Framework :: AsyncIO
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Natural Language :: Russian
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Topic :: Communications :: Chat
26
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
+ Classifier: Typing :: Typed
28
+ Requires-Python: >=3.9
29
+ Requires-Dist: aiohttp>=3.8
30
+ Requires-Dist: pydantic>=2
31
+ Provides-Extra: test
32
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'test'
33
+ Requires-Dist: pytest>=7; extra == 'test'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # maxapi-lite
37
+
38
+ [![PyPI](https://img.shields.io/pypi/v/maxapi-lite.svg)](https://pypi.org/project/maxapi-lite/)
39
+ [![Python](https://img.shields.io/pypi/pyversions/maxapi-lite.svg)](https://pypi.org/project/maxapi-lite/)
40
+ [![CI](https://github.com/kiriyk/maxapi-lite/actions/workflows/ci.yml/badge.svg)](https://github.com/kiriyk/maxapi-lite/actions/workflows/ci.yml)
41
+
42
+ Легковесный асинхронный клиент [MAX Bot API](https://dev.max.ru/docs-api), совместимый с `aiohttp 3.8.x`.
43
+
44
+ Пакет нужен, когда в проекте уже зафиксирован старый `aiohttp` — например, рядом живёт
45
+ `aiogram 2.x` (`aiohttp<3.9`), а официальный клиент `maxapi` требует `aiohttp>=3.12`.
46
+ Зависимости — только `aiohttp>=3.8` и `pydantic>=2`.
47
+
48
+ *Lightweight async client for the MAX messenger Bot API that works with aiohttp 3.8,
49
+ so it can share an environment with aiogram 2.x.*
50
+
51
+ ## Установка
52
+
53
+ ```bash
54
+ pip install maxapi-lite
55
+ ```
56
+
57
+ ## Быстрый старт
58
+
59
+ ```python
60
+ import asyncio
61
+ from maxapi_lite import Bot, ParseMode
62
+
63
+
64
+ async def main():
65
+ bot = Bot(token="YOUR_MAX_TOKEN", parse_mode=ParseMode.HTML) # или переменная окружения MAX_BOT_TOKEN
66
+
67
+ # Кнопка-ссылка
68
+ kb_attachment = bot.build_link_button_attachment(
69
+ text="Открыть MiniApp",
70
+ url="https://example.com/app?startapp=123",
71
+ )
72
+
73
+ # Отправка фото + кнопки
74
+ result = await bot.send_message_with_media(
75
+ chat_id=-1, # Демонстрационный ID — замените на ID своего чата
76
+ text="<b>Розыгрыш</b>",
77
+ media_type="photo",
78
+ file_path="banner.jpg", # или media_url="https://..."
79
+ attachments=[kb_attachment],
80
+ )
81
+
82
+ # Извлечение mid и закрепление
83
+ mid = bot.extract_message_id(result)
84
+ if mid:
85
+ await bot.pin_chat_message(chat_id=-1, message_id=mid)
86
+
87
+ await bot.close_session()
88
+
89
+
90
+ asyncio.run(main())
91
+ ```
92
+
93
+ ### Long polling
94
+
95
+ ```python
96
+ marker = None
97
+ while True:
98
+ data = await bot.get_updates(timeout=30, marker=marker)
99
+ for update in data.get("updates", []):
100
+ ...
101
+ marker = data.get("marker", marker)
102
+ ```
103
+
104
+ Можно передать `types=["message_created", "message_callback"]`. Библиотека оставляет
105
+ HTTP-таймауту запас не менее 10 секунд относительно `timeout` поллинга; явно
106
+ отключённые ограничения HTTP не включаются обратно. Допустимый `timeout` — 0–90,
107
+ `limit` — 1–1000. MAX рекомендует использовать Webhook для production: Long Polling
108
+ ограничен скоростью и сроком хранения событий ([документация](https://dev.max.ru/docs-api/methods/GET/updates)).
109
+
110
+ ## Подключение и TLS
111
+
112
+ По умолчанию запросы идут на `https://platform-api2.max.ru`. Для другого сервера
113
+ передайте `base_url`. Настройки доступны как в `Bot`, так и в `MaxApiClient`:
114
+
115
+ ```python
116
+ import ssl
117
+ import aiohttp
118
+ from maxapi_lite import Bot
119
+
120
+ tls = ssl.create_default_context()
121
+ # При необходимости добавьте доверенный сертификат в окружении приложения:
122
+ # tls.load_verify_locations(cafile="/path/to/trusted-ca.pem")
123
+
124
+ bot = Bot(
125
+ ssl_context=tls, # токен берётся из MAX_BOT_TOKEN
126
+ timeout=aiohttp.ClientTimeout(total=60),
127
+ transfer_timeout=aiohttp.ClientTimeout(
128
+ total=None, connect=30, sock_connect=30, sock_read=120,
129
+ ),
130
+ )
131
+ ```
132
+
133
+ API по умолчанию использует `total=300`. У upload/download нет общего ограничения
134
+ времени, но по умолчанию установлены таймауты соединения 30 с и чтения 120 с.
135
+ Вместо `ssl_context` можно передать `connector=aiohttp.TCPConnector(ssl=tls)`;
136
+ одновременно эти параметры передавать нельзя. Внешний connector закрывает
137
+ вызывающий код. Собственные сессии и connector библиотека закрывает через
138
+ `await bot.close_session()`; вызывайте его в `finally`.
139
+
140
+ ## Входящие файлы и видео
141
+
142
+ Извлекайте URL из входящего события и передавайте его в `download_file`:
143
+
144
+ ```python
145
+ from maxapi_lite import Bot
146
+
147
+ async def save_incoming_video(bot: Bot, update: dict, destination: str) -> int:
148
+ for attachment in bot.extract_attachments(update):
149
+ if attachment.get("type") == "video":
150
+ url = (attachment.get("payload") or {}).get("url")
151
+ if url:
152
+ return await bot.download_file(url, destination)
153
+ return 0
154
+ ```
155
+
156
+ `download_file(url, dest_path, *, chunk_size=1<<20, max_retries=3)` возвращает число
157
+ сохранённых байтов. Скачивание идёт чанками через отдельную сессию **без токена
158
+ бота**, включая переходы по редиректам. `max_retries=3` — до трёх повторов после
159
+ первой попытки. При обрыве выполняется докачка через Range/If-Range, если сервер
160
+ предоставил сильный ETag или Last-Modified; без валидатора скачивание начинается
161
+ заново. Ответ 200 вместо 206 также перезаписывает временный файл с начала.
162
+ Размер сверяется с Content-Length/Content-Range, когда они доступны.
163
+
164
+ Существующий файл назначения заменяется атомарно после успешного скачивания.
165
+ При ошибке или отмене временный файл удаляется; между вызовами докачка не
166
+ сохраняется. Родительский каталог назначения должен существовать.
167
+
168
+ Для скачивания по готовому `payload.url` токен видео не нужен.
169
+ `await bot.get_video(video_token)` отдельно получает метаданные через API;
170
+ `urls` в ответе может быть `None`.
171
+
172
+ По наблюдениям, приведённым в задании ClipWizard: входящее `video` перекодируется
173
+ MAX и URL живёт около 24 часов; `file` сохраняет исходные байты и URL живёт около
174
+ 30 дней; изображение может возвращаться в webp. Это наблюдения, а не гарантии
175
+ SDK: скачивайте входящие файлы своевременно. Ошибка HTTP 403 возвращается без
176
+ автоматических повторов; приложение может запросить свежую ссылку через MAX API,
177
+ если такая возможность доступна для вложения.
178
+
179
+ ## Загрузка и отправка результата
180
+
181
+ ```python
182
+ await bot.send_message_with_media(
183
+ chat_id=-1, # замените на свой ID
184
+ media_type="video",
185
+ file_path="result.mp4",
186
+ text="Видео готово",
187
+ wait_attachments=True,
188
+ attachment_wait_timeout=60,
189
+ )
190
+ ```
191
+
192
+ Файл передаётся потоком в multipart-поле `data`. Для video/audio библиотека
193
+ сохраняет токен из ответа `/uploads`, если сервер заливки не вернул собственный.
194
+ Токен вложения используется при отправке сообщения; передавать его вручную при
195
+ загрузке локального файла не требуется.
196
+
197
+ При `wait_attachments=True` отправка или редактирование повторяется только после
198
+ ошибки `attachment.not.ready`. Паузы растут 1, 2, 4, 8, 16 с, затем остаются 16 с;
199
+ запросы и паузы ограничены общим бюджетом `attachment_wait_timeout` (60 с по
200
+ умолчанию). Эти параметры работают у `send_message`, `edit_message` и обоих
201
+ методов `*_with_media`. По умолчанию ожидание выключено. Истечение бюджета после
202
+ определённого ответа not.ready даёт `AttachmentNotReady`; таймаут запроса с
203
+ неизвестным результатом остаётся `asyncio.TimeoutError`.
204
+
205
+ Размер локального файла проверяется до запроса `/uploads`:
206
+
207
+ | Тип | Максимум в байтах |
208
+ | --- | ---: |
209
+ | image | 50 000 000 |
210
+ | video | 250 000 000 |
211
+ | audio | 256 000 000 |
212
+ | file | 4 000 000 000 |
213
+
214
+ Используются консервативные десятичные МБ/ГБ. При превышении возникает
215
+ `FileTooLarge` с полями `size` и `limit`. MAX также ограничивает изображения
216
+ 7680×7680 px и аудио 60 минутами; поддерживает MP4/MOV/MKV/WEBM для video.
217
+ Проверка содержимого, разрешения и длительности выполняется сервером; локально
218
+ библиотека не декодирует медиа ([лимиты MAX](https://dev.max.ru/docs-api/methods/POST/uploads)).
219
+ Resumable upload пока не реализован; после обрыва заливку нужно запускать заново.
220
+
221
+ ## Клавиатуры и callback
222
+
223
+ ```python
224
+ from maxapi_lite import CallbackButton, LinkButton
225
+
226
+ keyboard = bot.build_keyboard([
227
+ [CallbackButton(text="Обработать", payload="process")],
228
+ [LinkButton(text="Открыть сайт", url="https://example.com")],
229
+ ])
230
+ await bot.send_message(chat_id=-1, text="Выберите действие", attachments=[keyboard])
231
+ await bot.send_callback(
232
+ callback_id="CALLBACK_ID_FROM_UPDATE",
233
+ notification="Готово",
234
+ message={"text": "Обработка запущена", "attachments": []},
235
+ )
236
+ ```
237
+
238
+ Модели кнопок сериализуются с обязательным полем `type`. `build_keyboard`
239
+ принимает ряды моделей или словарей и возвращает одно вложение inline_keyboard.
240
+ `send_callback` может отправлять уведомление, обновлённое сообщение или оба.
241
+
242
+ ## Ошибки и повторы
243
+
244
+ `MaxApiError.code` — числовой HTTP status, `MaxApiError.api_code` — строковый код
245
+ ошибки MAX, если он есть. `FileTooLarge`, `AttachmentNotReady`, `RateLimited`
246
+ наследуют `MaxApiError`; `InvalidToken` сохраняет прежнее поведение.
247
+
248
+ При HTTP 429 возникает `RateLimited`; поле `retry_after` содержит задержку в
249
+ секундах из Retry-After либо `None`. 429 автоматически не повторяется. MAX
250
+ указывает общий предел 30 rps, а для `/answers` — не более двух ответов в секунду
251
+ в один диалог/чат/канал; очередь и регулирование частоты остаются в приложении
252
+ ([uploads](https://dev.max.ru/docs-api/methods/POST/uploads),
253
+ [answers](https://dev.max.ru/docs-api/methods/POST/answers)).
254
+
255
+ Сетевые ошибки отправки сообщений автоматически не повторяются: после обрыва
256
+ сервер уже мог принять сообщение, и повтор способен создать дубликат.
257
+
258
+ ## Методы `Bot`
259
+
260
+ | Группа | Методы |
261
+ | --- | --- |
262
+ | Бот и обновления | `get_me`, `get_updates`, `extract_attachments` |
263
+ | Сообщения | `send_message`, `edit_message`, `delete_message`, `get_message` |
264
+ | Медиа | `upload_file`, `download_file`, `get_video`, `create_media_attachment`, `send_message_with_media`, `edit_message_with_media` |
265
+ | Закрепление | `pin_chat_message`, `pin_from_response`, `unpin_chat_message` |
266
+ | Чаты | `get_chat_by_id`, `get_chat_member`, `get_chat_members_batch`, `get_list_admin_chat` |
267
+ | Callback | `send_callback` |
268
+ | Утилиты | `extract_message_id`, `build_keyboard`, `build_link_button_attachment` |
269
+
270
+ Методы возвращают «сырые» `dict` из ответа API. Ошибки API поднимаются как `MaxApiError`
271
+ (с полем `code`), ошибка авторизации — как `InvalidToken`.
272
+
273
+ ## Поддерживаемые `media_type`
274
+
275
+ - `photo`, `animation`, `image` → загрузка как `image`
276
+ - `video`
277
+ - `audio`
278
+ - `document`, `file`
279
+
280
+ ## Примечания
281
+
282
+ - `chat_id` / `user_id` передаются query-параметрами, как требует MAX API.
283
+ - `edit_message(..., attachments=[])` явно очищает вложения; `attachments=None` оставляет их как есть.
284
+ - Формат (`parse_mode`) применяется к редактированию только если передан `text`.
285
+ - Для upload используется multipart-поле `data`.
286
+
287
+ ## Разработка
288
+
289
+ ```bash
290
+ pip install -e ".[test]"
291
+ pytest
292
+ ```
293
+
294
+ Релиз: поднять `__version__` в `src/maxapi_lite/__init__.py`, дополнить `CHANGELOG.md`,
295
+ создать GitHub Release с тегом `vX.Y.Z` — workflow `publish.yml` соберёт пакет и опубликует его на PyPI.
296
+
297
+ ## Лицензия
298
+
299
+ MIT
@@ -0,0 +1,264 @@
1
+ # maxapi-lite
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/maxapi-lite.svg)](https://pypi.org/project/maxapi-lite/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/maxapi-lite.svg)](https://pypi.org/project/maxapi-lite/)
5
+ [![CI](https://github.com/kiriyk/maxapi-lite/actions/workflows/ci.yml/badge.svg)](https://github.com/kiriyk/maxapi-lite/actions/workflows/ci.yml)
6
+
7
+ Легковесный асинхронный клиент [MAX Bot API](https://dev.max.ru/docs-api), совместимый с `aiohttp 3.8.x`.
8
+
9
+ Пакет нужен, когда в проекте уже зафиксирован старый `aiohttp` — например, рядом живёт
10
+ `aiogram 2.x` (`aiohttp<3.9`), а официальный клиент `maxapi` требует `aiohttp>=3.12`.
11
+ Зависимости — только `aiohttp>=3.8` и `pydantic>=2`.
12
+
13
+ *Lightweight async client for the MAX messenger Bot API that works with aiohttp 3.8,
14
+ so it can share an environment with aiogram 2.x.*
15
+
16
+ ## Установка
17
+
18
+ ```bash
19
+ pip install maxapi-lite
20
+ ```
21
+
22
+ ## Быстрый старт
23
+
24
+ ```python
25
+ import asyncio
26
+ from maxapi_lite import Bot, ParseMode
27
+
28
+
29
+ async def main():
30
+ bot = Bot(token="YOUR_MAX_TOKEN", parse_mode=ParseMode.HTML) # или переменная окружения MAX_BOT_TOKEN
31
+
32
+ # Кнопка-ссылка
33
+ kb_attachment = bot.build_link_button_attachment(
34
+ text="Открыть MiniApp",
35
+ url="https://example.com/app?startapp=123",
36
+ )
37
+
38
+ # Отправка фото + кнопки
39
+ result = await bot.send_message_with_media(
40
+ chat_id=-1, # Демонстрационный ID — замените на ID своего чата
41
+ text="<b>Розыгрыш</b>",
42
+ media_type="photo",
43
+ file_path="banner.jpg", # или media_url="https://..."
44
+ attachments=[kb_attachment],
45
+ )
46
+
47
+ # Извлечение mid и закрепление
48
+ mid = bot.extract_message_id(result)
49
+ if mid:
50
+ await bot.pin_chat_message(chat_id=-1, message_id=mid)
51
+
52
+ await bot.close_session()
53
+
54
+
55
+ asyncio.run(main())
56
+ ```
57
+
58
+ ### Long polling
59
+
60
+ ```python
61
+ marker = None
62
+ while True:
63
+ data = await bot.get_updates(timeout=30, marker=marker)
64
+ for update in data.get("updates", []):
65
+ ...
66
+ marker = data.get("marker", marker)
67
+ ```
68
+
69
+ Можно передать `types=["message_created", "message_callback"]`. Библиотека оставляет
70
+ HTTP-таймауту запас не менее 10 секунд относительно `timeout` поллинга; явно
71
+ отключённые ограничения HTTP не включаются обратно. Допустимый `timeout` — 0–90,
72
+ `limit` — 1–1000. MAX рекомендует использовать Webhook для production: Long Polling
73
+ ограничен скоростью и сроком хранения событий ([документация](https://dev.max.ru/docs-api/methods/GET/updates)).
74
+
75
+ ## Подключение и TLS
76
+
77
+ По умолчанию запросы идут на `https://platform-api2.max.ru`. Для другого сервера
78
+ передайте `base_url`. Настройки доступны как в `Bot`, так и в `MaxApiClient`:
79
+
80
+ ```python
81
+ import ssl
82
+ import aiohttp
83
+ from maxapi_lite import Bot
84
+
85
+ tls = ssl.create_default_context()
86
+ # При необходимости добавьте доверенный сертификат в окружении приложения:
87
+ # tls.load_verify_locations(cafile="/path/to/trusted-ca.pem")
88
+
89
+ bot = Bot(
90
+ ssl_context=tls, # токен берётся из MAX_BOT_TOKEN
91
+ timeout=aiohttp.ClientTimeout(total=60),
92
+ transfer_timeout=aiohttp.ClientTimeout(
93
+ total=None, connect=30, sock_connect=30, sock_read=120,
94
+ ),
95
+ )
96
+ ```
97
+
98
+ API по умолчанию использует `total=300`. У upload/download нет общего ограничения
99
+ времени, но по умолчанию установлены таймауты соединения 30 с и чтения 120 с.
100
+ Вместо `ssl_context` можно передать `connector=aiohttp.TCPConnector(ssl=tls)`;
101
+ одновременно эти параметры передавать нельзя. Внешний connector закрывает
102
+ вызывающий код. Собственные сессии и connector библиотека закрывает через
103
+ `await bot.close_session()`; вызывайте его в `finally`.
104
+
105
+ ## Входящие файлы и видео
106
+
107
+ Извлекайте URL из входящего события и передавайте его в `download_file`:
108
+
109
+ ```python
110
+ from maxapi_lite import Bot
111
+
112
+ async def save_incoming_video(bot: Bot, update: dict, destination: str) -> int:
113
+ for attachment in bot.extract_attachments(update):
114
+ if attachment.get("type") == "video":
115
+ url = (attachment.get("payload") or {}).get("url")
116
+ if url:
117
+ return await bot.download_file(url, destination)
118
+ return 0
119
+ ```
120
+
121
+ `download_file(url, dest_path, *, chunk_size=1<<20, max_retries=3)` возвращает число
122
+ сохранённых байтов. Скачивание идёт чанками через отдельную сессию **без токена
123
+ бота**, включая переходы по редиректам. `max_retries=3` — до трёх повторов после
124
+ первой попытки. При обрыве выполняется докачка через Range/If-Range, если сервер
125
+ предоставил сильный ETag или Last-Modified; без валидатора скачивание начинается
126
+ заново. Ответ 200 вместо 206 также перезаписывает временный файл с начала.
127
+ Размер сверяется с Content-Length/Content-Range, когда они доступны.
128
+
129
+ Существующий файл назначения заменяется атомарно после успешного скачивания.
130
+ При ошибке или отмене временный файл удаляется; между вызовами докачка не
131
+ сохраняется. Родительский каталог назначения должен существовать.
132
+
133
+ Для скачивания по готовому `payload.url` токен видео не нужен.
134
+ `await bot.get_video(video_token)` отдельно получает метаданные через API;
135
+ `urls` в ответе может быть `None`.
136
+
137
+ По наблюдениям, приведённым в задании ClipWizard: входящее `video` перекодируется
138
+ MAX и URL живёт около 24 часов; `file` сохраняет исходные байты и URL живёт около
139
+ 30 дней; изображение может возвращаться в webp. Это наблюдения, а не гарантии
140
+ SDK: скачивайте входящие файлы своевременно. Ошибка HTTP 403 возвращается без
141
+ автоматических повторов; приложение может запросить свежую ссылку через MAX API,
142
+ если такая возможность доступна для вложения.
143
+
144
+ ## Загрузка и отправка результата
145
+
146
+ ```python
147
+ await bot.send_message_with_media(
148
+ chat_id=-1, # замените на свой ID
149
+ media_type="video",
150
+ file_path="result.mp4",
151
+ text="Видео готово",
152
+ wait_attachments=True,
153
+ attachment_wait_timeout=60,
154
+ )
155
+ ```
156
+
157
+ Файл передаётся потоком в multipart-поле `data`. Для video/audio библиотека
158
+ сохраняет токен из ответа `/uploads`, если сервер заливки не вернул собственный.
159
+ Токен вложения используется при отправке сообщения; передавать его вручную при
160
+ загрузке локального файла не требуется.
161
+
162
+ При `wait_attachments=True` отправка или редактирование повторяется только после
163
+ ошибки `attachment.not.ready`. Паузы растут 1, 2, 4, 8, 16 с, затем остаются 16 с;
164
+ запросы и паузы ограничены общим бюджетом `attachment_wait_timeout` (60 с по
165
+ умолчанию). Эти параметры работают у `send_message`, `edit_message` и обоих
166
+ методов `*_with_media`. По умолчанию ожидание выключено. Истечение бюджета после
167
+ определённого ответа not.ready даёт `AttachmentNotReady`; таймаут запроса с
168
+ неизвестным результатом остаётся `asyncio.TimeoutError`.
169
+
170
+ Размер локального файла проверяется до запроса `/uploads`:
171
+
172
+ | Тип | Максимум в байтах |
173
+ | --- | ---: |
174
+ | image | 50 000 000 |
175
+ | video | 250 000 000 |
176
+ | audio | 256 000 000 |
177
+ | file | 4 000 000 000 |
178
+
179
+ Используются консервативные десятичные МБ/ГБ. При превышении возникает
180
+ `FileTooLarge` с полями `size` и `limit`. MAX также ограничивает изображения
181
+ 7680×7680 px и аудио 60 минутами; поддерживает MP4/MOV/MKV/WEBM для video.
182
+ Проверка содержимого, разрешения и длительности выполняется сервером; локально
183
+ библиотека не декодирует медиа ([лимиты MAX](https://dev.max.ru/docs-api/methods/POST/uploads)).
184
+ Resumable upload пока не реализован; после обрыва заливку нужно запускать заново.
185
+
186
+ ## Клавиатуры и callback
187
+
188
+ ```python
189
+ from maxapi_lite import CallbackButton, LinkButton
190
+
191
+ keyboard = bot.build_keyboard([
192
+ [CallbackButton(text="Обработать", payload="process")],
193
+ [LinkButton(text="Открыть сайт", url="https://example.com")],
194
+ ])
195
+ await bot.send_message(chat_id=-1, text="Выберите действие", attachments=[keyboard])
196
+ await bot.send_callback(
197
+ callback_id="CALLBACK_ID_FROM_UPDATE",
198
+ notification="Готово",
199
+ message={"text": "Обработка запущена", "attachments": []},
200
+ )
201
+ ```
202
+
203
+ Модели кнопок сериализуются с обязательным полем `type`. `build_keyboard`
204
+ принимает ряды моделей или словарей и возвращает одно вложение inline_keyboard.
205
+ `send_callback` может отправлять уведомление, обновлённое сообщение или оба.
206
+
207
+ ## Ошибки и повторы
208
+
209
+ `MaxApiError.code` — числовой HTTP status, `MaxApiError.api_code` — строковый код
210
+ ошибки MAX, если он есть. `FileTooLarge`, `AttachmentNotReady`, `RateLimited`
211
+ наследуют `MaxApiError`; `InvalidToken` сохраняет прежнее поведение.
212
+
213
+ При HTTP 429 возникает `RateLimited`; поле `retry_after` содержит задержку в
214
+ секундах из Retry-After либо `None`. 429 автоматически не повторяется. MAX
215
+ указывает общий предел 30 rps, а для `/answers` — не более двух ответов в секунду
216
+ в один диалог/чат/канал; очередь и регулирование частоты остаются в приложении
217
+ ([uploads](https://dev.max.ru/docs-api/methods/POST/uploads),
218
+ [answers](https://dev.max.ru/docs-api/methods/POST/answers)).
219
+
220
+ Сетевые ошибки отправки сообщений автоматически не повторяются: после обрыва
221
+ сервер уже мог принять сообщение, и повтор способен создать дубликат.
222
+
223
+ ## Методы `Bot`
224
+
225
+ | Группа | Методы |
226
+ | --- | --- |
227
+ | Бот и обновления | `get_me`, `get_updates`, `extract_attachments` |
228
+ | Сообщения | `send_message`, `edit_message`, `delete_message`, `get_message` |
229
+ | Медиа | `upload_file`, `download_file`, `get_video`, `create_media_attachment`, `send_message_with_media`, `edit_message_with_media` |
230
+ | Закрепление | `pin_chat_message`, `pin_from_response`, `unpin_chat_message` |
231
+ | Чаты | `get_chat_by_id`, `get_chat_member`, `get_chat_members_batch`, `get_list_admin_chat` |
232
+ | Callback | `send_callback` |
233
+ | Утилиты | `extract_message_id`, `build_keyboard`, `build_link_button_attachment` |
234
+
235
+ Методы возвращают «сырые» `dict` из ответа API. Ошибки API поднимаются как `MaxApiError`
236
+ (с полем `code`), ошибка авторизации — как `InvalidToken`.
237
+
238
+ ## Поддерживаемые `media_type`
239
+
240
+ - `photo`, `animation`, `image` → загрузка как `image`
241
+ - `video`
242
+ - `audio`
243
+ - `document`, `file`
244
+
245
+ ## Примечания
246
+
247
+ - `chat_id` / `user_id` передаются query-параметрами, как требует MAX API.
248
+ - `edit_message(..., attachments=[])` явно очищает вложения; `attachments=None` оставляет их как есть.
249
+ - Формат (`parse_mode`) применяется к редактированию только если передан `text`.
250
+ - Для upload используется multipart-поле `data`.
251
+
252
+ ## Разработка
253
+
254
+ ```bash
255
+ pip install -e ".[test]"
256
+ pytest
257
+ ```
258
+
259
+ Релиз: поднять `__version__` в `src/maxapi_lite/__init__.py`, дополнить `CHANGELOG.md`,
260
+ создать GitHub Release с тегом `vX.Y.Z` — workflow `publish.yml` соберёт пакет и опубликует его на PyPI.
261
+
262
+ ## Лицензия
263
+
264
+ MIT
@@ -7,7 +7,7 @@ maxapi_lite - легковесный асинхронный клиент MAX Bot
7
7
 
8
8
  from .bot import Bot
9
9
  from .enums import ChatPermission, ParseMode
10
- from .exceptions import InvalidToken, MaxApiError
10
+ from .exceptions import AttachmentNotReady, FileTooLarge, InvalidToken, MaxApiError, RateLimited
11
11
  from .types import (
12
12
  Attachment,
13
13
  ButtonsPayload,
@@ -19,7 +19,7 @@ from .types import (
19
19
  RequestGeoLocationButton,
20
20
  )
21
21
 
22
- __version__ = "0.2.0"
22
+ __version__ = "0.3.0"
23
23
 
24
24
  __all__ = [
25
25
  # Main class
@@ -39,4 +39,7 @@ __all__ = [
39
39
  # Exceptions
40
40
  "MaxApiError",
41
41
  "InvalidToken",
42
+ "FileTooLarge",
43
+ "AttachmentNotReady",
44
+ "RateLimited",
42
45
  ]