Skip to main content

Перенос ботов из Telegram: что сломается, и как чинить

Bot API совместим с Telegram на ~99% — большинство ботов взлетают подменой api.telegram.org → api.telefon.chat. Но есть несколько семантических различий, которые молча ломают портированных ботов. Прежде чем переносить — пройдитесь по этому списку.

1. User.username ведёт себя как в Telegram​

В Telefon каждый пользователь задаёт @username при регистрации (это обязательное поле в onboarding), поэтому from.username приходит так же стабильно, как у Telegram. Фильтры по username работают штатно:

@router.message(filters.User(username="admin"))
async def admin_only(m): ...

# Тоже работает
@dp.message(F.from_user.username.in_(["alice", "bob"]))
async def handler(m): ...

Тем не менее по спецификации Telegram username остаётся опциональным — пользователь может его сбросить. Для долгоживущих whitelist'ов и привязок (admin, биллинг, прав) безопаснее использовать **числовой ** — он уникален и стабилен.

2. Chat.username тоже может быть None​

То же самое для каналов и публичных групп. Если вы фильтруете по chat.username — переходите на chat.id.

# ❌
if message.chat.username == "my_channel":

# ✅
if message.chat.id == -100123456789:

3. ID-схема совместима, но цифры свои​

user.id, chat.id, message.id — все int, как в Telegram. Но числа другие — у нас своё пространство ID. Значит:

  • Захардкоженные chat_id из Telegram не работают — нужно получить наш ID (например, через chat.id в первом апдейте от пользователя).
  • Сохраняйте маппинг external_id → ваш_user_id если у вас своя БД пользователей.

Префикс знака сохранён: -100… для каналов/супергрупп, положительные для приватных, отрицательные не-100 для обычных групп.

4. Команды (commands) — массив, а не строка​

В Telegram через multipart/form-data библиотеки иногда сериализуют commands как JSON-строку. Telefon принимает это (с автоматическим парсингом), но в старых записях БД из ранних версий могло сохраниться как строка — мы это уже починили миграцией. Если ваш бот сам пишет в setMyCommands — используйте обычный массив, как в спецификации.

5. Платежи: нет официальных провайдеров​

sendInvoice / successful_payment пока stub'ы. Используйте внешние платёжные ссылки (Stripe Checkout, ЮKassa) и Mini App для приёма платежей.

6. Stars (XTR) недоступны​

Цифровая валюта Telegram Stars не реализована. Подписки делайте через свой биллинг.

7. Голосовые / video_note — наш формат​

Voice — audio/webm, video_note — video/webm. Telegram отдаёт audio/ogg и video/mp4. Если ваш бот качает их и обрабатывает — ставьте ffmpeg для конвертации.

8. Inline mode — без CDN-кеша Telegram​

answerInlineQuery работает, но кеша картинок Telegram'а у нас нет. Подавайте URL HTTPS-картинок которые отдаются быстро (CDN), иначе будет лаг.

Чек-лист при переносе​

  • Уберите все filters.User(username=...), from_user.username == "...", allowed_telegram_usernames — переведите на user.id
  • Уберите захардкоженные chat_id/user_id из Telegram — получайте их в рантайме
  • Проверьте платежи — отключите/замените на внешний провайдер
  • Конвертируйте audio/ogg → audio/webm в коде если работаете с voice
  • Запустите бота, отправьте тестовое сообщение — посмотрите в логах что ваши хендлеры срабатывают

Помощь и обратная связь​

Если бот ведёт себя странно — пишите в dev@telefon.chat с примером лога. Часто оказывается что фильтр проглотил сообщение из-за username == None.