Предыстория: два мессенджера, две кодовые базы

Max запустили как полноценную альтернативу Telegram для российского рынка, с открытым Bot API. Разработчики оказались перед классической дилеммой: поддерживать двойную кодовую базу или выбрать одну платформу, теряя аудиторию второй.

Типичная ситуация выглядит так: есть готовый Telegram-бот на aiogram, и нужно запустить его ещё в Max. Варианты неприятные: либо дублировать код, либо писать тонну адаптеров вручную, либо мириться с расхождением фич между платформами.

Библиотека obabot предлагает другой путь: один код — два мессенджера, чтобы править всеми.

Что такое obabot?

obabot — это асинхронная Python-библиотека с API, совместимым с aiogram 3.x, которая позволяет запускать одного бота сразу на Telegram и Max. Под капотом: для Telegram используется нативный aiogram без какой-либо дополнительной обёртки, а для Max — адаптерный слой, который транслирует события Max-платформы в aiogram-совместимые объекты.

Устанавливается одной командой:

pip install obabot aiogram>=3.0.0 umaxbot>=0.1.7

Быстрый старт

Центральная точка входа — функция create_bot(). Она принимает токены нужных платформ и возвращает готовый кортеж (bot, dispatcher, router).

Только Telegram

from obabot import create_bot
from obabot.filters import Command

bot, dp, router = create_bot(tg_token="ВАШ_TG_ТОКЕН")

@router.message(Command("start"))
async def start(message):
    await message.answer(f"Привет с {message.platform}!")

await dp.start_polling(bot)

Только Max

from obabot import create_bot
from obabot.filters import Command

bot, dp, router = create_bot(max_token="ВАШ_MAX_ТОКЕН")

@router.message(Command("start"))
async def start(message):
    await message.answer(f"Привет с {message.platform}!")

await dp.start_polling(bot)

Обратите внимание: код обработчика абсолютно идентичен. Разница только в аргументе токена. В каждый объект добавляется атрибут platform для написания платформ-специфичного кода

Обе платформы одновременно

from obabot import create_bot
from obabot.filters import Command

bot, dp, router = create_bot(
    tg_token="ВАШ_TG_ТОКЕН",
    max_token="ВАШ_MAX_ТОКЕН"
)

@router.message(Command("start"))
async def start(message):
    # Атрибут platform показывает, откуда пришло сообщение
    await message.answer(f"Привет с {message.platform}!")

# Polling запускается на обеих платформах параллельно
await dp.start_polling(bot)

Миграция с aiogram за 5 минут

Если у вас уже есть бот на aiogram 3.x, миграция сводится к замене импортов и инициализации.

До:

from aiogram import Bot, Dispatcher, Router
from aiogram.filters import Command
from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.context import FSMContext

bot = Bot(token="TOKEN")
dp = Dispatcher()
router = Router()
dp.include_router(router)

@router.message(Command("start"))
async def start(message):
    await message.answer("Привет!")

await dp.start_polling(bot)

После:

from obabot import create_bot
from obabot.filters import Command
from obabot.fsm import State, StatesGroup, FSMContext

bot, dp, router = create_bot(tg_token="TOKEN")

@router.message(Command("start"))
async def start(message):
    await message.answer("Привет!")

await dp.start_polling(bot)

Изменилось ровно 2 вещи:

  • импорты: aiogramobabot

  • инициализация: Bot/Dispatcher/Routercreate_bot()

    всё остальное осталось 100% идентичным

Поддерживаемые возможности

Объект Message

Все сообщения — и из Telegram, и из Max — реализуют единый интерфейс:

message.text          # Текст сообщения
message.from_user     # Пользователь
message.chat          # Чат
message.message_id    # ID сообщения
message.platform      # "telegram" или "max"

await message.answer("Ответ")
await message.reply("Ответить на сообщение")
await message.delete()
await message.edit_text("Новый текст")

FSM (Finite State Machine, машина состояний, конечный автомат)

FSM работает так же, как в aiogram:

from obabot.fsm import State, StatesGroup, FSMContext

class Form(StatesGroup):
    name = State()
    age = State()

@router.message(Command("start"))
async def start(message, state: FSMContext):
    await state.set_state(Form.name)
    await message.answer("Как тебя зовут?")

@router.message(Form.name)
async def process_name(message, state: FSMContext):
    await state.update_data(name=message.text)
    await state.set_state(Form.age)
    await message.answer("Сколько тебе лет?")

Инлайн-клавиатуры

from obabot.types import InlineKeyboardMarkup, InlineKeyboardButton

keyboard = InlineKeyboardMarkup(inline_keyboard=[
    [
        InlineKeyboardButton(text="Кнопка 1", callback_data="btn1"),
        InlineKeyboardButton(text="Кнопка 2", callback_data="btn2"),
    ]
])

await message.answer("Выберите:", reply_markup=keyboard)

Фильтры

from obabot.filters import Command, F

@router.message(Command("start", "help"))     # Несколько команд
@router.message(F.text.startswith("!"))       # Магический фильтр
@router.message(F.photo)                      # Только фото
@router.callback_query(F.data == "click")     # Callback

Тестовый режим

Для unit-тестов предусмотрен специальный режим — токены не нужны, сетевых вызовов нет:

bot, dp, router = create_bot(test_mode=True)

Тесты покрывают Python 3.10, 3.13, 3.14 и aiogram 3.0.0, 3.24.

Репозиторий: github.com/Korean-DOG/obabot

Итог

obabot закрывает конкретную практическую боль: запускать Telegram-бота ещё и в Max без переписывания кода.