aiogram-max 1.1.1__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.
- aiogram_max-1.1.1/.github/workflows/ci.yml +43 -0
- aiogram_max-1.1.1/.github/workflows/publish.yml +55 -0
- aiogram_max-1.1.1/.gitignore +11 -0
- aiogram_max-1.1.1/CONTRIBUTING.md +48 -0
- aiogram_max-1.1.1/LICENSE +21 -0
- aiogram_max-1.1.1/PKG-INFO +256 -0
- aiogram_max-1.1.1/README.md +224 -0
- aiogram_max-1.1.1/docs/method-coverage.md +165 -0
- aiogram_max-1.1.1/examples/demo_bot.py +65 -0
- aiogram_max-1.1.1/pyproject.toml +75 -0
- aiogram_max-1.1.1/scripts/capture_updates.py +61 -0
- aiogram_max-1.1.1/scripts/check_captured.py +56 -0
- aiogram_max-1.1.1/scripts/live_e2e.py +86 -0
- aiogram_max-1.1.1/scripts/send_test_keyboard.py +54 -0
- aiogram_max-1.1.1/src/aiogram_max/__init__.py +39 -0
- aiogram_max-1.1.1/src/aiogram_max/bot.py +75 -0
- aiogram_max-1.1.1/src/aiogram_max/converters.py +279 -0
- aiogram_max-1.1.1/src/aiogram_max/errors.py +44 -0
- aiogram_max-1.1.1/src/aiogram_max/markup.py +251 -0
- aiogram_max-1.1.1/src/aiogram_max/methods/__init__.py +12 -0
- aiogram_max-1.1.1/src/aiogram_max/methods/base.py +73 -0
- aiogram_max-1.1.1/src/aiogram_max/methods/chats.py +160 -0
- aiogram_max-1.1.1/src/aiogram_max/methods/media.py +313 -0
- aiogram_max-1.1.1/src/aiogram_max/methods/settings.py +86 -0
- aiogram_max-1.1.1/src/aiogram_max/mids.py +44 -0
- aiogram_max-1.1.1/src/aiogram_max/py.typed +0 -0
- aiogram_max-1.1.1/src/aiogram_max/session.py +538 -0
- aiogram_max-1.1.1/src/aiogram_max/uploads.py +123 -0
- aiogram_max-1.1.1/tests/__init__.py +0 -0
- aiogram_max-1.1.1/tests/fixtures/live_max_updates.json +95 -0
- aiogram_max-1.1.1/tests/test_bot_settings.py +128 -0
- aiogram_max-1.1.1/tests/test_chats.py +264 -0
- aiogram_max-1.1.1/tests/test_live_fixtures.py +99 -0
- aiogram_max-1.1.1/tests/test_markup.py +109 -0
- aiogram_max-1.1.1/tests/test_mids.py +52 -0
- aiogram_max-1.1.1/tests/test_polling.py +41 -0
- aiogram_max-1.1.1/tests/test_session.py +850 -0
- aiogram_max-1.1.1/uv.lock +1068 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master, main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
# 3.12 — минимальная поддерживаемая, 3.14 — на чём разрабатывается.
|
|
15
|
+
python-version: ["3.12", "3.13", "3.14"]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@v5
|
|
22
|
+
with:
|
|
23
|
+
enable-cache: true
|
|
24
|
+
|
|
25
|
+
- name: Create venv on Python ${{ matrix.python-version }}
|
|
26
|
+
# Именно venv, а не --system: системный Python на раннере
|
|
27
|
+
# externally-managed, и uv в него ставить отказывается.
|
|
28
|
+
run: uv venv --python ${{ matrix.python-version }}
|
|
29
|
+
|
|
30
|
+
- name: Install dependencies
|
|
31
|
+
run: uv pip install -e ".[dev]"
|
|
32
|
+
|
|
33
|
+
- name: Ruff (lint)
|
|
34
|
+
run: uv run ruff check .
|
|
35
|
+
|
|
36
|
+
- name: Ruff (format)
|
|
37
|
+
run: uv run ruff format --check .
|
|
38
|
+
|
|
39
|
+
- name: Mypy (strict)
|
|
40
|
+
run: uv run mypy
|
|
41
|
+
|
|
42
|
+
- name: Pytest
|
|
43
|
+
run: uv run pytest -q
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
# Публикация на PyPI по факту релиза на GitHub: тег уже проставлен, версия
|
|
4
|
+
# в pyproject должна ему соответствовать — это проверяется ниже до сборки.
|
|
5
|
+
on:
|
|
6
|
+
release:
|
|
7
|
+
types: [published]
|
|
8
|
+
# Ручной запуск нужен, чтобы прогнать сборку и проверки без публикации:
|
|
9
|
+
# dry_run по умолчанию включён, публикация требует осознанного выбора.
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
inputs:
|
|
12
|
+
dry_run:
|
|
13
|
+
description: Только собрать и проверить, без публикации
|
|
14
|
+
type: boolean
|
|
15
|
+
default: true
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
publish:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
permissions:
|
|
21
|
+
# Доверенная публикация PyPI: короткоживущий OIDC-токен вместо
|
|
22
|
+
# постоянного секрета в репозитории. Утёкший API-токен PyPI отзывать
|
|
23
|
+
# некому и некогда — здесь отзывать нечего.
|
|
24
|
+
id-token: write
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
|
|
28
|
+
- name: Install uv
|
|
29
|
+
uses: astral-sh/setup-uv@v5
|
|
30
|
+
with:
|
|
31
|
+
enable-cache: true
|
|
32
|
+
|
|
33
|
+
- name: Версия в pyproject совпадает с тегом
|
|
34
|
+
# Расхождение тега и версии — самая обидная ошибка релиза: пакет
|
|
35
|
+
# уезжает под чужим номером, а исправить уже нельзя, версии на PyPI
|
|
36
|
+
# не переиспользуются. Проверяем до сборки.
|
|
37
|
+
if: github.event_name == 'release'
|
|
38
|
+
run: |
|
|
39
|
+
set -euo pipefail
|
|
40
|
+
TAG="${GITHUB_REF_NAME#v}"
|
|
41
|
+
VERSION=$(uv run python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
|
|
42
|
+
echo "тег: $TAG, версия в pyproject: $VERSION"
|
|
43
|
+
test "$TAG" = "$VERSION"
|
|
44
|
+
|
|
45
|
+
- name: Сборка
|
|
46
|
+
run: uv build
|
|
47
|
+
|
|
48
|
+
- name: Проверка метаданных
|
|
49
|
+
# twine check ловит то, что ломает страницу пакета: битый README,
|
|
50
|
+
# незакрытую разметку, отсутствующие поля.
|
|
51
|
+
run: uvx twine check dist/*
|
|
52
|
+
|
|
53
|
+
- name: Публикация на PyPI
|
|
54
|
+
if: github.event_name == 'release' || inputs.dry_run == false
|
|
55
|
+
run: uv publish --trusted-publishing always
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Как поучаствовать
|
|
2
|
+
|
|
3
|
+
Библиотека покрывает не весь Telegram Bot API, и это осознанно: методы
|
|
4
|
+
добавляются по мере надобности. Если вам не хватило какого-то — скорее всего,
|
|
5
|
+
он уже помечен как «ждём PR».
|
|
6
|
+
|
|
7
|
+
## Что означает ошибка, которую вы поймали
|
|
8
|
+
|
|
9
|
+
* `NotImplementedYet` — **аналог в MAX есть, мы его не написали**. В тексте
|
|
10
|
+
ошибки лежит подсказка, каким методом MAX это делается. Такой PR приветствуется
|
|
11
|
+
и обычно занимает несколько десятков строк.
|
|
12
|
+
* `UnsupportedByMax` — **у платформы нет такой возможности**. Опросов и
|
|
13
|
+
игральных костей в MAX нет, PR тут не поможет.
|
|
14
|
+
|
|
15
|
+
Список первого вида — в `NOT_IMPLEMENTED_PR_WELCOME` (`src/aiogram_max/session.py`).
|
|
16
|
+
|
|
17
|
+
## Добавить метод
|
|
18
|
+
|
|
19
|
+
1. Напишите обработчик в `MaxSession` и зарегистрируйте его в `_HANDLERS`.
|
|
20
|
+
Обработчик обязан вернуть ровно тот тип, который объявлен у метода aiogram
|
|
21
|
+
в `__returning__` — иначе сломается типизация на стороне бота.
|
|
22
|
+
2. Уберите метод из `NOT_IMPLEMENTED_PR_WELCOME`.
|
|
23
|
+
3. Тест обязателен. Фейковый MAX (`FakeMax` в `tests/test_session.py`) отвечает
|
|
24
|
+
на новый путь — добавьте туда ветку и проверьте **тело запроса**, а не только
|
|
25
|
+
факт вызова: половина расхождений с MAX — именно в полях.
|
|
26
|
+
4. Если параметр Telegram при переводе теряется — сообщите об этом через
|
|
27
|
+
`self._degrade(...)`. Молча выбрасывать нельзя.
|
|
28
|
+
|
|
29
|
+
## Проверки
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv venv && uv pip install -e ".[dev]"
|
|
33
|
+
.venv/bin/ruff check . && .venv/bin/ruff format --check .
|
|
34
|
+
.venv/bin/mypy
|
|
35
|
+
.venv/bin/pytest -q
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Всё это гоняется в CI на Python 3.12, 3.13 и 3.14.
|
|
39
|
+
|
|
40
|
+
## О тестах на живых данных
|
|
41
|
+
|
|
42
|
+
`tests/fixtures/live_max_updates.json` — обезличенные события, снятые с
|
|
43
|
+
настоящего MAX Bot API. Тесты на них (`tests/test_live_fixtures.py`) ловят то,
|
|
44
|
+
чего не поймает фейк: расхождение между документацией и реальным поведением.
|
|
45
|
+
Один такой баг уже нашёлся — `recipient.user_id` оказался получателем
|
|
46
|
+
сообщения, а не собеседником, и id бота едва не утёк в `Chat.id`.
|
|
47
|
+
|
|
48
|
+
Если ловите новое событие от живого MAX — обезличьте и добавьте в фикстуры.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sergey Borisov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aiogram-max
|
|
3
|
+
Version: 1.1.1
|
|
4
|
+
Summary: Запускает aiogram-бота в мессенджере MAX: подменяем сессию, код бота не трогаем
|
|
5
|
+
Project-URL: Homepage, https://github.com/juntatalor/aiogram-max
|
|
6
|
+
Project-URL: Source, https://github.com/juntatalor/aiogram-max
|
|
7
|
+
Project-URL: Issues, https://github.com/juntatalor/aiogram-max/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/juntatalor/aiogram-max/releases
|
|
9
|
+
Author-email: Sergey Borisov <juntatalor@gmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: aiogram,bot,max,messenger,telegram
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Framework :: AsyncIO
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Communications :: Chat
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Requires-Dist: aiogram>=3.20
|
|
25
|
+
Requires-Dist: httpx>=0.27
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# aiogram-max
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/aiogram-max/)
|
|
36
|
+
[](https://pypi.org/project/aiogram-max/)
|
|
37
|
+
[](https://github.com/juntatalor/aiogram-max/actions/workflows/ci.yml)
|
|
38
|
+
[](https://github.com/juntatalor/aiogram-max/blob/master/LICENSE)
|
|
39
|
+
[](https://mypy-lang.org/)
|
|
40
|
+
[](https://docs.astral.sh/ruff/)
|
|
41
|
+
|
|
42
|
+
Запускает бота, написанного на **aiogram**, в мессенджере **MAX** — без правок кода бота.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from aiogram import Dispatcher
|
|
46
|
+
from aiogram_max import make_bot
|
|
47
|
+
|
|
48
|
+
bot = make_bot(max_token="...") # единственная изменённая строка
|
|
49
|
+
await Dispatcher().start_polling(bot)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Роутеры, фильтры (`Command`, магический `F`), FSM, middleware и типы остаются
|
|
53
|
+
родными aiogram-овскими.
|
|
54
|
+
|
|
55
|
+
## Статус
|
|
56
|
+
|
|
57
|
+
**Рабочая библиотека, проверенная на живом MAX.** Настоящий
|
|
58
|
+
`Dispatcher.start_polling` ходит в MAX Bot API, хендлеры срабатывают, ответы
|
|
59
|
+
и `callback.answer()` доезжают до мессенджера.
|
|
60
|
+
|
|
61
|
+
Покрыто 40 методов — всё, чему нашёлся аналог у платформы: сообщения и
|
|
62
|
+
правки, вложения всех типов, клавиатуры и callback'и, группы с участниками и
|
|
63
|
+
пинами, вебхук, команды бота, разметка. Каждый проверен на реальном боте, а
|
|
64
|
+
не только тестами.
|
|
65
|
+
|
|
66
|
+
## Как это работает
|
|
67
|
+
|
|
68
|
+
Весь исходящий трафик aiogram проходит через одну функцию:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
BaseSession.make_request(bot, method: TelegramMethod, timeout) -> TelegramType
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`Bot` не ходит в сеть сам: он собирает типизированный объект метода
|
|
75
|
+
(`SendMessage`, `GetUpdates`, `AnswerCallbackQuery`, …) и отдаёт его сессии.
|
|
76
|
+
Мы подменяем сессию — и перехватываем поток целиком:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
Бот на aiogram (код не меняется)
|
|
80
|
+
Dispatcher / Router / фильтры / FSM
|
|
81
|
+
Bot.send_message(...)
|
|
82
|
+
│ TelegramMethod
|
|
83
|
+
▼
|
|
84
|
+
MaxSession(BaseSession) ← вся библиотека здесь
|
|
85
|
+
│ HTTP
|
|
86
|
+
▼
|
|
87
|
+
MAX Bot API
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Long polling тоже работает штатно: aiogram сам зовёт `GetUpdates`, а сессия
|
|
91
|
+
ходит в `GET /updates?marker=` и собирает из событий MAX валидные
|
|
92
|
+
aiogram-`Update`.
|
|
93
|
+
|
|
94
|
+
Отличие от [obabot](https://github.com/Korean-DOG/obabot), решающего похожую
|
|
95
|
+
задачу: там своя система типов (`obabot.Message`, `obabot.FSMContext`) и
|
|
96
|
+
импорты в боте меняются. Здесь типы остаются aiogram-овскими, подменяется
|
|
97
|
+
только транспорт.
|
|
98
|
+
|
|
99
|
+
## Что уже проверено тестами
|
|
100
|
+
|
|
101
|
+
| Что | Как проверено |
|
|
102
|
+
| --- | --- |
|
|
103
|
+
| MAX-событие → валидный aiogram `Update` | `test_get_updates_returns_aiogram_updates` |
|
|
104
|
+
| Роутер + фильтр `Command` | `test_dispatcher_routes_message_to_handler` |
|
|
105
|
+
| Inline-клавиатура → MAX `inline_keyboard` | там же, сверка тела запроса |
|
|
106
|
+
| Callback + магический фильтр `F.data` | `test_callback_query_flows_through_aiogram` |
|
|
107
|
+
| FSM (`StatesGroup`, `FSMContext`) | `test_fsm_state_survives_platform_swap` |
|
|
108
|
+
| Родной `Dispatcher.start_polling` | `test_native_polling_loop_delivers_max_events` |
|
|
109
|
+
| Правка сообщения (`seq` ↔ `mid`) | `test_edit_message_uses_max_mid` |
|
|
110
|
+
| Неподдерживаемый метод | `test_unsupported_method_raises_in_strict_mode` |
|
|
111
|
+
| Живые payload'ы MAX | `tests/test_live_fixtures.py` (6 тестов на снятых с API событиях) |
|
|
112
|
+
| Потеря кнопки без аналога | `test_dropped_button_warns_but_keeps_the_rest` |
|
|
113
|
+
| Маппинг parse_mode / notify / reply | `test_supported_params_are_mapped_not_dropped` |
|
|
114
|
+
| Вложения: загрузка и отправка | `test_send_photo_uploads_and_attaches_token` |
|
|
115
|
+
| Ожидание обработки файла на стороне MAX | `test_send_photo_waits_until_attachment_is_ready` |
|
|
116
|
+
| MarkdownV2 → html (включая подчёркивание) | `tests/test_markup.py`, 11 тестов |
|
|
117
|
+
| `entities` → html без потерь | `test_entities_are_no_longer_dropped_silently` |
|
|
118
|
+
| `MarkupPolicy.RAW` не трогает текст | `test_raw_policy_leaves_text_alone` |
|
|
119
|
+
|
|
120
|
+
## Неподдерживаемое в MAX
|
|
121
|
+
|
|
122
|
+
У MAX нет части возможностей Telegram. Политика задаётся при создании бота:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
make_bot(token) # WARN, по умолчанию
|
|
126
|
+
make_bot(token, unsupported=UnsupportedPolicy.STRICT)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Расхождения бывают трёх видов:
|
|
130
|
+
|
|
131
|
+
1. **Метода нет вовсе** (`SendPoll`, `SendDice`). `WARN` — предупреждение и
|
|
132
|
+
пропуск, `STRICT` — `UnsupportedByMax` с именем метода.
|
|
133
|
+
2. **Метод есть, а параметра нет** (кнопка `web_app`, `switch_inline_query`).
|
|
134
|
+
Такая кнопка выбрасывается, остальные остаются: `WARN` пишет в лог что
|
|
135
|
+
именно потерялось, `STRICT` падает. Молча не выбрасываем никогда —
|
|
136
|
+
«кнопка исчезла, а бот не упал» ищется потом часами.
|
|
137
|
+
3. **Семантика другая** — это работа слоя конвертации, а не политики:
|
|
138
|
+
`message_id` (int) ↔ MAX `mid` (str) сшиваются через `seq`.
|
|
139
|
+
|
|
140
|
+
Где аналог есть — параметр переводится, а не теряется:
|
|
141
|
+
|
|
142
|
+
| aiogram | MAX |
|
|
143
|
+
| --- | --- |
|
|
144
|
+
| `parse_mode="HTML"` | `format: html` |
|
|
145
|
+
| `parse_mode="MarkdownV2"` | конвертация в html, см. «Разметка» |
|
|
146
|
+
| `entities=[...]` | конвертация в html, см. «Разметка» |
|
|
147
|
+
| `disable_notification=True` | `notify: false` |
|
|
148
|
+
| `reply_to_message_id` | `link: {type: reply, mid}` |
|
|
149
|
+
|
|
150
|
+
Отдельный случай — пустой `callback.answer()`. В Telegram он снимает
|
|
151
|
+
индикатор загрузки на кнопке и стоит почти в каждом хендлере; у MAX такой
|
|
152
|
+
семантики нет, `POST /answers` с пустым телом отвечает `400 proto.payload`
|
|
153
|
+
(«`message` or `notification` required»). Запрос не уходит вовсе. С текстом —
|
|
154
|
+
`answer("Принято")` — уходит как `notification`.
|
|
155
|
+
|
|
156
|
+
## Разметка
|
|
157
|
+
|
|
158
|
+
Работает из коробки: бот шлёт привычный телеграмный `parse_mode` или
|
|
159
|
+
`entities`, библиотека переводит это в html, который MAX понимает.
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
await bot.send_message(chat_id, "*жирный* и __подчёркнутый__", parse_mode="MarkdownV2")
|
|
163
|
+
# в MAX уедет: {"text": "<b>жирный</b> и <u>подчёркнутый</u>", "format": "html"}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Форматируете под MAX сами — отключите посредника:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
make_bot(token, markup=MarkupPolicy.RAW) # текст уйдёт как есть
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Почему html, а не markdown.** MAX принимает оба формата, но markdown у него
|
|
173
|
+
CommonMark, а в CommonMark `__текст__` — это жирный. В MarkdownV2 та же запись
|
|
174
|
+
означает подчёркивание, и перевод «markdown в markdown» молча превратил бы
|
|
175
|
+
одно в другое. Тип `underline` MAX отдаёт только за `<u>`. Вдобавок html не
|
|
176
|
+
требует правил экранирования, которых в MarkdownV2 полтора десятка, причём
|
|
177
|
+
внутри кодовых спанов они другие.
|
|
178
|
+
|
|
179
|
+
**Что MAX действительно поддерживает.** Официальная страница «Форматирование»
|
|
180
|
+
отдаёт 404, поэтому таблица снята с живого API: бот отправил пробу, MAX
|
|
181
|
+
вернул разобранную разметку.
|
|
182
|
+
|
|
183
|
+
| Разметка | `format: html` | `format: markdown` |
|
|
184
|
+
| --- | --- | --- |
|
|
185
|
+
| жирный | `<b>` → `strong` | `**x**` → `strong` |
|
|
186
|
+
| курсив | `<i>` → `emphasized` | `*x*`, `_x_` → `emphasized` |
|
|
187
|
+
| подчёркнутый | `<u>` → `underline` | **нет**: `__x__` даёт `strong` |
|
|
188
|
+
| зачёркнутый | `<s>` → `strikethrough` | `~~x~~` → `strikethrough` |
|
|
189
|
+
| моноширинный | `<code>`, `<pre>` → `monospaced` | `` `x` `` → `monospaced` |
|
|
190
|
+
| ссылка | `<a href>` → `link` | `[x](url)` → `link` |
|
|
191
|
+
| заголовок | не проверялся | `# x` → `heading` |
|
|
192
|
+
| цитата | `<blockquote>` — **не распознаётся** | `> x` — **не распознаётся** |
|
|
193
|
+
| списки, спойлер | нет | нет |
|
|
194
|
+
|
|
195
|
+
Чего у MAX нет вовсе — спойлер, цитата, кастомные эмодзи — проходит через ту
|
|
196
|
+
же политику `unsupported`: `WARN` пишет в лог что именно потерялось, `STRICT`
|
|
197
|
+
падает. Текст при этом сохраняется, теряется только оформление.
|
|
198
|
+
|
|
199
|
+
## Грабли MAX, которые стоит знать
|
|
200
|
+
|
|
201
|
+
* **Индикатор набора у MAX есть**, вопреки расхожему мнению: работает и в
|
|
202
|
+
личке, и в группе. Только называется иначе — телеграмное `typing` он не
|
|
203
|
+
понимает, нужно `typing_on`, и библиотека переводит сама.
|
|
204
|
+
* **Вложение не готово сразу после заливки.** Отправка отвечает
|
|
205
|
+
`400 attachment.not.ready`, пока MAX обрабатывает файл. Библиотека ждёт и
|
|
206
|
+
повторяет сама, но знать об этом стоит: в Telegram такого шага нет.
|
|
207
|
+
* **`recipient.user_id` — это получатель сообщения, а не собеседник.** В
|
|
208
|
+
событии от пользователя там лежит id бота, в сообщении бота пользователю —
|
|
209
|
+
id пользователя. Подставите его как `chat_id` — бот начнёт молча отвечать
|
|
210
|
+
сам себе. `chat_id` в диалоге MAX присылает, брать надо только его.
|
|
211
|
+
* **Позиция в ленте — `marker`, и она же `update_id`.** MAX отдаёт в ответе
|
|
212
|
+
«следующую ожидаемую» позицию, что совпадает по смыслу с телеграмным
|
|
213
|
+
`offset`. Поэтому `update_id` события — его позиция в ленте (`marker - 1`
|
|
214
|
+
для последнего события пачки), а не порядковый номер внутри процесса.
|
|
215
|
+
Для бота с дедупом по `update_id` это принципиально: счётчик из памяти
|
|
216
|
+
после рестарта начинается заново, и свежие события выглядят уже
|
|
217
|
+
обработанными.
|
|
218
|
+
* **`seq` — не маленький счётчик.** Живое значение: `116993690454357274`.
|
|
219
|
+
В `int64` влезает и aiogram переваривает, но узкое поле БД (`integer`
|
|
220
|
+
вместо `bigint`) на этом сломается.
|
|
221
|
+
* **`/start` приходит обычным `message_created`**, а не отдельным событием —
|
|
222
|
+
фильтр `Command` работает без спецобработки.
|
|
223
|
+
* **Идентификатор сообщения двойной**: aiogram знает `message_id` (int из
|
|
224
|
+
`seq`), MAX правит и удаляет по строковому `mid`. Соответствие держит
|
|
225
|
+
сессия в памяти (последние 10 000 сообщений), поэтому правка переживает
|
|
226
|
+
рестарт только в пределах жизни процесса. Обратного преобразования
|
|
227
|
+
`mid → seq` MAX не даёт, так что иначе никак.
|
|
228
|
+
|
|
229
|
+
## Что ещё не сделано
|
|
230
|
+
* Webhook (у MAX это рекомендованный для прода транспорт).
|
|
231
|
+
* Разметка подписей (`caption`, `caption_entities`) — вместе с вложениями.
|
|
232
|
+
* Покрыто 40 методов из 185 в aiogram — всё, чему есть аналог у MAX. Что уже работает, что можно добавить и
|
|
233
|
+
чего в MAX нет вовсе — в [docs/method-coverage.md](docs/method-coverage.md).
|
|
234
|
+
|
|
235
|
+
## Установка
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
pip install aiogram-max # после публикации; пока — pip install -e .
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Python 3.12+, единственные зависимости — `aiogram` и `httpx`. Библиотека
|
|
242
|
+
типизирована (`py.typed`), mypy strict проходит без ignore'ов.
|
|
243
|
+
|
|
244
|
+
## Разработка
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
uv venv && uv pip install -e ".[dev]"
|
|
248
|
+
.venv/bin/ruff check . && .venv/bin/mypy && .venv/bin/pytest -q
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Как добавить метод и чем `NotImplementedYet` отличается от `UnsupportedByMax` —
|
|
252
|
+
в [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
253
|
+
|
|
254
|
+
## Лицензия
|
|
255
|
+
|
|
256
|
+
MIT
|