feat: 10 примеров ботов для быстрого старта и миграции с Telegram#97
feat: 10 примеров ботов для быстрого старта и миграции с Telegram#97love-apples merged 8 commits intolove-apples:mainfrom
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
Добавляет в репозиторий набор самодостаточных примеров ботов и сопутствующую документацию, чтобы упростить быстрый старт и миграцию с Telegram-экосистемы на maxapi.
Changes:
- Добавлены 10 Python-скриптов примеров ботов в
examples/(polling/webhook, middleware, router, FSM, media, callback payloads). - Добавлен подробный гайд
examples/README.mdи секция про примеры в корневомREADME.md. - Обновлены настройки Ruff (per-file ignores) и
.gitignoreдля публикацииexamples/.
Reviewed changes
Copilot reviewed 13 out of 15 changed files in this pull request and generated 21 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Добавлена секция со ссылками на примеры и мини-таблица миграции. |
| pyproject.toml | Добавлены Ruff per-file-ignores для examples/**.py. |
| examples/README.md | Новый подробный гайд по запуску примеров, FAQ и таблица миграции. |
| examples/01_echo_bot.py | Пример эхо-бота (polling). |
| examples/02_formatting_bot.py | Пример форматирования (HTML/Markdown). |
| examples/03_keyboard_bot.py | Пример inline-клавиатур и callback-обработки. |
| examples/04_fsm_bot.py | Пример FSM/контекста состояний. |
| examples/05_media_bot.py | Пример отправки/загрузки медиа и обработки вложений. |
| examples/06_admin_bot.py | Пример админ-команд (pin/delete/edit/info/members) и событий чата. |
| examples/07_router_bot.py | Пример роутеров, фильтров и middleware на уровне роутера. |
| examples/08_middleware_bot.py | Пример middleware-цепочки (logging/throttle/auth/errors). |
| examples/09_webhook_bot.py | Пример webhook-интеграции с FastAPI/uvicorn. |
| examples/10_callback_payload_bot.py | Пример типизированных callback payloads и навигации. |
| examples/.DS_Store | Добавлен бинарный артефакт macOS Finder. |
| .gitignore | Убрано игнорирование examples/. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ```bash | ||
| python -m venv .venv | ||
| source .venv/bin/activate # Linux/macOS | ||
| # .venv\Scripts\activate # Windows | ||
|
|
||
| pip install maxapi | ||
| ``` | ||
|
|
||
| Для примера с webhook (09): | ||
| ```bash | ||
| pip install maxapi[fastapi] | ||
| ``` |
There was a problem hiding this comment.
Инструкция предлагает pip install maxapi, но все примеры импортируют python-dotenv и вызывают load_dotenv(). Сейчас python-dotenv находится только в dev-зависимостях, поэтому запуск примеров у пользователя приведёт к ModuleNotFoundError. Либо сделайте импорт dotenv опциональным в примерах, либо добавьте python-dotenv в зависимости/extra и обновите инструкцию установки.
| Логгеры библиотеки: `bot`, `dispatcher`, `connection`. | ||
|
|
||
| **Как перейти с polling на webhook?** | ||
| Смотрите пример [`09_webhook_bot.py`](09_webhook_bot.py). Не забудьте удалить подписки polling через `await bot.delete_webhook()` если они были. |
There was a problem hiding this comment.
Фраза про «удалить подписки polling через await bot.delete_webhook()» некорректна: delete_webhook() относится к webhook, а не к polling. Лучше переформулировать как «перед переходом на polling удалите webhook» или «если был webhook — выполните delete_webhook()».
| Смотрите пример [`09_webhook_bot.py`](09_webhook_bot.py). Не забудьте удалить подписки polling через `await bot.delete_webhook()` если они были. | |
| Смотрите пример [`09_webhook_bot.py`](09_webhook_bot.py). Если ранее был настроен webhook, удалите его через `await bot.delete_webhook()` перед переходом на polling. |
| from dotenv import load_dotenv | ||
| from maxapi import Bot, Dispatcher, F | ||
| from maxapi.enums.sender_action import SenderAction | ||
| from maxapi.filters.command import CommandStart | ||
| from maxapi.types.updates.bot_started import BotStarted | ||
| from maxapi.types.updates.message_created import MessageCreated | ||
|
|
||
| load_dotenv() |
There was a problem hiding this comment.
from dotenv import load_dotenv делает пример зависимым от python-dotenv, который не устанавливается при pip install maxapi (см. pyproject.toml). Чтобы примеры запускались «из коробки», сделайте импорт/вызов load_dotenv() опциональным (try/except ImportError) или обновите зависимости/инструкцию установки.
| from dotenv import load_dotenv | |
| from maxapi import Bot, Dispatcher, F | |
| from maxapi.enums.sender_action import SenderAction | |
| from maxapi.filters.command import CommandStart | |
| from maxapi.types.updates.bot_started import BotStarted | |
| from maxapi.types.updates.message_created import MessageCreated | |
| load_dotenv() | |
| try: | |
| from dotenv import load_dotenv | |
| except ImportError: | |
| load_dotenv = None | |
| from maxapi import Bot, Dispatcher, F | |
| from maxapi.enums.sender_action import SenderAction | |
| from maxapi.filters.command import CommandStart | |
| from maxapi.types.updates.bot_started import BotStarted | |
| from maxapi.types.updates.message_created import MessageCreated | |
| if load_dotenv is not None: | |
| load_dotenv() |
| from dotenv import load_dotenv | ||
| from maxapi import Bot, Dispatcher | ||
| from maxapi.enums.parse_mode import TextFormat | ||
| from maxapi.filters.command import Command, CommandStart | ||
| from maxapi.types.updates.message_created import MessageCreated | ||
|
|
||
| # Импорты билдеров форматирования | ||
| from maxapi.utils.formatting import ( | ||
| Bold, | ||
| Code, | ||
| Heading, | ||
| Italic, | ||
| Link, | ||
| Strikethrough, | ||
| Text, | ||
| Underline, | ||
| UserMention, | ||
| ) | ||
|
|
||
| load_dotenv() | ||
| logging.basicConfig(level=logging.INFO) | ||
|
|
There was a problem hiding this comment.
from dotenv import load_dotenv делает пример зависимым от python-dotenv, который не устанавливается при pip install maxapi. Лучше сделать импорт/вызов load_dotenv() опциональным или явно указать установку python-dotenv в документации.
| from dotenv import load_dotenv | ||
| from maxapi import Bot, Dispatcher | ||
| from maxapi.filters.command import CommandStart | ||
| from maxapi.types.attachments.buttons.callback_button import CallbackButton | ||
| from maxapi.types.attachments.buttons.link_button import LinkButton | ||
| from maxapi.types.attachments.buttons.request_contact import ( | ||
| RequestContactButton, | ||
| ) | ||
| from maxapi.types.attachments.buttons.request_geo_location_button import ( | ||
| RequestGeoLocationButton, | ||
| ) | ||
| from maxapi.types.updates.message_callback import MessageCallback | ||
| from maxapi.types.updates.message_created import MessageCreated | ||
| from maxapi.utils.inline_keyboard import InlineKeyboardBuilder | ||
|
|
||
| load_dotenv() | ||
| logging.basicConfig(level=logging.INFO) | ||
|
|
There was a problem hiding this comment.
from dotenv import load_dotenv делает пример зависимым от python-dotenv, который не устанавливается при pip install maxapi. Лучше сделать импорт/вызов load_dotenv() опциональным или явно указать установку python-dotenv в документации.
| WEBHOOK_URL: str = os.getenv("WEBHOOK_URL", "https://example.com/webhook") | ||
| WEBHOOK_SECRET: str | None = os.getenv("WEBHOOK_SECRET") or None | ||
| WEBHOOK_HOST: str = os.getenv( | ||
| "WEBHOOK_HOST", "0.0.0.0" | ||
| ) # В production используйте 127.0.0.1 за reverse proxy | ||
| WEBHOOK_PORT: int = int(os.getenv("WEBHOOK_PORT", "8080")) | ||
| WEBHOOK_PATH: str = os.getenv("WEBHOOK_PATH", "/webhook") | ||
|
|
||
| bot = Bot() | ||
| dp = Dispatcher() | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Хендлеры | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
|
|
||
| @dp.on_started() | ||
| async def on_dp_started() -> None: | ||
| """Вызывается один раз при старте диспетчера. | ||
|
|
||
| Регистрируем webhook в MAX-платформе, чтобы она слала обновления | ||
| на наш URL. | ||
| """ | ||
| log.info("Диспетчер запущен, подписываемся на webhook: %s", WEBHOOK_URL) | ||
| try: | ||
| await bot.subscribe_webhook( | ||
| url=WEBHOOK_URL, | ||
| update_types=[ | ||
| UpdateType.MESSAGE_CREATED, | ||
| UpdateType.MESSAGE_CALLBACK, | ||
| UpdateType.BOT_STARTED, | ||
| ], | ||
| secret=WEBHOOK_SECRET, | ||
| ) | ||
| log.info("Подписка на webhook зарегистрирована успешно.") | ||
| except Exception as exc: | ||
| log.error("Не удалось зарегистрировать webhook: %s", exc) | ||
|
|
There was a problem hiding this comment.
WEBHOOK_URL берётся с дефолтом https://example.com/webhook, после чего subscribe_webhook() вызывается автоматически в on_started. Если пользователь забудет выставить переменную окружения, пример будет пытаться зарегистрировать заведомо неверный URL. Лучше сделать WEBHOOK_URL обязательной (без дефолта) и/или при отсутствии значения пропускать подписку с понятным сообщением и завершением процесса.
| from dotenv import load_dotenv | ||
| from maxapi import Bot, Dispatcher, F | ||
| from maxapi.enums.sender_action import SenderAction | ||
| from maxapi.filters.callback_payload import CallbackPayload | ||
| from maxapi.filters.command import CommandStart | ||
| from maxapi.types.attachments.buttons.callback_button import CallbackButton | ||
| from maxapi.types.updates.bot_started import BotStarted | ||
| from maxapi.types.updates.message_callback import MessageCallback | ||
| from maxapi.types.updates.message_created import MessageCreated | ||
| from maxapi.utils.inline_keyboard import InlineKeyboardBuilder | ||
|
|
||
| load_dotenv() | ||
| logging.basicConfig(level=logging.INFO) | ||
|
|
There was a problem hiding this comment.
from dotenv import load_dotenv делает пример зависимым от python-dotenv, который не устанавливается при pip install maxapi. Лучше сделать импорт/вызов load_dotenv() опциональным или явно указать установку python-dotenv в документации.
| # Кнопка «Купить» (для демонстрации — просто уведомление) | ||
| buy_payload = DetailPayload( | ||
| item_id=item_id, category_id=category_id | ||
| ).pack() | ||
| builder.row(CallbackButton(text="Купить", payload=buy_payload)) |
There was a problem hiding this comment.
Название DetailPayload и его докстринг («Подробнее») не соответствуют фактическому использованию: этим payload обрабатывается нажатие кнопки «Купить». Чтобы пример был понятнее, лучше выделить отдельный payload (например, BuyPayload) или переименовать текущий класс/описание под реальное действие.
| # Кнопка «Купить» (для демонстрации — просто уведомление) | |
| buy_payload = DetailPayload( | |
| item_id=item_id, category_id=category_id | |
| ).pack() | |
| builder.row(CallbackButton(text="Купить", payload=buy_payload)) | |
| # Кнопка «Подробнее» использует payload детального просмотра | |
| buy_payload = DetailPayload( | |
| item_id=item_id, category_id=category_id | |
| ).pack() | |
| builder.row(CallbackButton(text="Подробнее", payload=buy_payload)) |
| [tool.ruff.lint.per-file-ignores] | ||
| "tests/**.py" = [ | ||
| "ARG", | ||
| "E402", | ||
| "PL", | ||
| "S", | ||
| "SLF", | ||
| ] | ||
| "examples/**.py" = [ | ||
| "INP001", | ||
| "PTH", | ||
| "ASYNC240", | ||
| "PLC0415", | ||
| "S", | ||
| "T20", | ||
| "FBT", | ||
| "SIM105", | ||
| ] |
There was a problem hiding this comment.
Перечень per-file-ignores добавлен для examples/**.py, но сами примеры сейчас требуют python-dotenv (см. импорты from dotenv import load_dotenv). При этом python-dotenv не входит в основную зависимость пакета. Чтобы примеры действительно работали при обычной установке библиотеки, стоит либо добавить python-dotenv в dependencies/optional extra, либо сделать dotenv опциональным в самих примерах.
| В директории [`examples/`](examples/) находятся готовые к запуску примеры ботов, покрывающие основные сценарии использования библиотеки. Каждый пример — самодостаточный `.py` файл с подробными комментариями на русском языке. | ||
|
|
||
| Запуск любого примера: | ||
| ```bash | ||
| MAX_BOT_TOKEN=ваш_токен python examples/01_echo_bot.py | ||
| ``` |
There was a problem hiding this comment.
В README предлагается запускать примеры напрямую после установки, но сами примеры импортируют python-dotenv и вызывают load_dotenv(). При pip install maxapi эта зависимость не устанавливается, поэтому примеры могут не запуститься. Либо сделайте dotenv опциональным в примерах, либо уточните в README необходимость pip install python-dotenv/соответствующего extra.
|
@love-apples поправил все 4 замечания в 5754e19:
Параллельно в #96 починил тест, который упал из-за мерджа main в ветку (лог в |
Добавлена директория examples/ с 10 готовыми к запуску примерами ботов, покрывающими основные сценарии разработки и миграции с Telegram. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Made python-dotenv optional (try/except ImportError) in all 10 examples - Added note about optional python-dotenv to README.md and examples/README.md - Added event.message is None guard in 03_keyboard_bot.py and 04_fsm_bot.py - Added chat_id is None early return in 05_media_bot.py cmd_photo - Fixed AuthMiddleware in 08_middleware_bot.py to extract user_id from callbacks - Fixed docstring in 09_webhook_bot.py (webhook.run() -> uvicorn.Server) - Made WEBHOOK_URL required (os.environ["WEBHOOK_URL"]) in 09_webhook_bot.py - Renamed DetailPayload to BuyPayload in 10_callback_payload_bot.py - Fixed incorrect phrase in examples/README.md (polling -> webhook subscriptions) - Removed examples/.DS_Store and added .DS_Store to .gitignore Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- F401: добавлен вызов load_dotenv() (был только импорт) - E501: комментарий перенесён на отдельную строку
- Добавлен guard `if chat_id is None: return` в cmd_buffer, cmd_upload и on_attachment - Исправлена формулировка про webhook/polling в FAQ - Добавлено упоминание python-dotenv как опциональной зависимости Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
P1: В 03_keyboard_bot.py и 04_fsm_bot.py передавался attachments=None
для удаления клавиатуры, но Message.edit() при None подставляет
существующие вложения обратно. Теперь используем пустой список — это
единственный способ реально удалить клавиатуру.
P1: В 05_media_bot.py команды /photo и /upload падали в «файл не найден»,
потому что sample.jpg не лежит в репозитории. Добавлен fallback:
если sample.jpg рядом нет — создаём временный PNG 1×1 пиксель, так
пример запускается «из коробки» (см. обещание в README.md).
P2: В 05_media_bot.py определение типа вложения шло по несуществующим
именам (PhotoAttachment/VideoAttachment/…). Реальные классы в
maxapi — Image, Video, Audio, File, Sticker. Перешёл на isinstance
проверки по реальным классам — это заодно защищает от опечаток.
P3: В examples/README.md опечатка bot.get_members_chat() исправлена на
bot.get_chat_members().
…ела получения токена - Откат добавленных 55 строк в корневом README.md (секция «Примеры ботов» дублировала содержимое examples/README.md). - В examples/README.md удалён раздел «2. Получение токена» (избыточная инструкция по установке MAX + создание бота через MasterBot — выходит за рамки примеров библиотеки). - Перенумерованы оставшиеся подразделы 3→2, 4→3. - Обновлена якорная ссылка в FAQ на love-apples#2-настройка-токена.
5754e19 to
f7dfd1b
Compare
|
Сузил scope:
По коду примеров ничего не трогал. |
| try: | ||
| from dotenv import load_dotenv | ||
|
|
||
| load_dotenv() | ||
| except ImportError: | ||
| pass |
There was a problem hiding this comment.
Не очень хорошая практика
- Для глушения в 0 есть suppress.
- Если load не предусмотрен, то зачем писать? Если предусмотрен - лучше падать.
There was a problem hiding this comment.
Поправил по п. 1 — заменил try/except: pass на contextlib.suppress(ImportError) во всех 10 примерах (5d8ac1b). Заодно убрал SIM105 из examples/**.py per-file-ignores в pyproject.toml — после правки заглушка не нужна.
По п. 2: dotenv-блок осознанно сохранил как опциональный QoL-хук для разработчика (помечено комментарием над with). python-dotenv — в [dependency-groups] dev, в публичный контракт не входит, поэтому жёсткий fail (как в webhook/fastapi.py) тут уместен меньше: пример должен запускаться и без неё через MAX_BOT_TOKEN=… python …. Если такая трактовка не ок — уберу блок целиком отдельным коммитом, скажи.
Заодно вернул [tool.setuptools.package-data] maxapi = ["py.typed"] — случайно стёрлось при rebase'е.
По замечанию @Olegt0rr (love-apples#97 r3127205121): try/except: pass — анти- паттерн (ruff SIM105). Заменено на contextlib.suppress(ImportError) во всех 10 примерах. dotenv-блок осознанно сохранён как опциональный QoL-хук для разработчика (помечено комментарием), python-dotenv в [dependency-groups] dev. - 10 examples/0*.py: try/except → with contextlib.suppress(ImportError) - pyproject.toml: убран SIM105 из examples/**.py per-file-ignores (после правки уже не нужен) - pyproject.toml: восстановлено [tool.setuptools.package-data] maxapi = ["py.typed"] (артефакт rebase'а — случайно стёрто) - 08_middleware_bot.py: тот же SIM105-фикс на пре-существующий LoggingMiddleware (try/except Exception → contextlib.suppress)
Описание
Добавлены 10 готовых к запуску примеров ботов в директории
examples/, покрывающих основные сценарии разработки на maxapi. Каждый пример — самодостаточный.pyфайл с подробными комментариями на русском языке и указанием аналога из экосистемы Telegram (aiogram / python-telegram-bot).Примеры прошли 5 раундов ревью: code review, security audit, architecture review, проверка API-сигнатур по исходникам библиотеки.
Что добавлено
10 примеров ботов (examples/)
Базовые
01_echo_bot.py02_formatting_bot.py03_keyboard_bot.pyСредний уровень
04_fsm_bot.py05_media_bot.py06_admin_bot.pyПродвинутый
07_router_bot.py08_middleware_bot.py09_webhook_bot.py10_callback_payload_bot.pyДокументация (examples/README.md)
Подробный гайд на ~360 строк:
Обновление README.md
Добавлена секция «Примеры ботов» с таблицами по уровням сложности (базовый / средний / продвинутый) и краткой таблицей миграции maxapi ↔ aiogram.
Настройка линтера (pyproject.toml)
Добавлены исключения ruff для
examples/**.py— правила стиля, специфичные для примеров (INP001, PTH, PLC0415 и др.), чтобы код примеров оставался чистым и понятным..gitignore
Убрана строка
examples/из .gitignore (ранее директория игнорировалась).Качество кода
Каждый пример проверен на:
ast.parse)ruff check examples/→ 0 ошибокruff format examples/ --check→ 0 измененийТестирование
Существующие тесты библиотеки проходят без изменений.
Для кого это