Перейти к содержанию

Примеры

Здесь собраны практические примеры использования MaxAPI для различных задач.

Эхо-бот

Простейший пример бота, который повторяет все текстовые сообщения:

import asyncio
import logging

from maxapi import Bot, Dispatcher, F
from maxapi.types import MessageCreated

logging.basicConfig(level=logging.INFO)

bot = Bot()
dp = Dispatcher()


@dp.message_created(F.message.body.text)
async def echo(event: MessageCreated):
    await event.message.answer(f"Повторяю за вами: {event.message.body.text}")


async def main():
    await dp.start_polling(bot)


if __name__ == '__main__':
    asyncio.run(main())

Обработка событий

Пример обработки различных событий, доступных в MaxAPI:

import asyncio
import logging

from maxapi import Bot, Dispatcher
from maxapi.types import (
    BotStarted, 
    Command, 
    MessageCreated, 
    CallbackButton, 
    MessageCallback, 
    BotAdded, 
    ChatTitleChanged, 
    MessageEdited, 
    MessageRemoved, 
    UserAdded, 
    UserRemoved,
    BotStopped,
    DialogCleared,
    DialogMuted,
    DialogUnmuted,
    ChatButton,  # deprecated: 0.9.14
    MessageChatCreated  # deprecated: 0.9.14
)
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder

logging.basicConfig(level=logging.INFO)

bot = Bot()
dp = Dispatcher()


@dp.message_created(Command('start'))
async def hello(event: MessageCreated):
    builder = InlineKeyboardBuilder()

    builder.row(
        CallbackButton(
            text='Кнопка 1',
            payload='btn_1'
        ),
        CallbackButton(
            text='Кнопка 2',
            payload='btn_2',
        )
    )
    builder.add(
        ChatButton(  # deprecated: 0.9.14
            text='Создать чат',
            chat_title='Тест чат'
        )
    )

    await event.message.answer(
        text='Привет!', 
        attachments=[
            builder.as_markup(),
        ]
    )


@dp.bot_added()
async def bot_added(event: BotAdded):
    if not event.chat:
        logging.info('Не удалось получить chat, возможно отключен auto_requests!')
        return

    await bot.send_message(
        chat_id=event.chat_id,
        text=f'Привет чат {event.chat.title}!'
    )


@dp.message_removed()
async def message_removed(event: MessageRemoved):
    await bot.send_message(
        chat_id=event.chat_id,
        text='Я всё видел!'
    )


@dp.bot_started()
async def bot_started(event: BotStarted):
    await bot.send_message(
        chat_id=event.chat_id,
        text='Привет! Отправь мне /start'
    )


@dp.chat_title_changed()
async def chat_title_changed(event: ChatTitleChanged):
    await bot.send_message(
        chat_id=event.chat_id,
        text=f'Крутое новое название "{event.title}"!'
    )


@dp.message_callback()
async def message_callback(event: MessageCallback):
    await event.answer(
        new_text=f'Вы нажали на кнопку {event.callback.payload}!'
    )


@dp.message_edited()
async def message_edited(event: MessageEdited):
    await event.message.answer(
        text='Вы отредактировали сообщение!'
    )


@dp.user_removed()
async def user_removed(event: UserRemoved):
    if not event.from_user:
        return await bot.send_message(
            chat_id=event.chat_id,
            text=f'Неизвестный кикнул {event.user.first_name} 😢'
        )

    await bot.send_message(
        chat_id=event.chat_id,
        text=f'{event.from_user.first_name} кикнул {event.user.first_name} 😢'
    )


@dp.user_added()
async def user_added(event: UserAdded):
    if not event.chat:
        return await bot.send_message(
            chat_id=event.chat_id,
            text=f'Чат приветствует вас, {event.user.first_name}!'
        )

    await bot.send_message(
        chat_id=event.chat_id,
        text=f'Чат "{event.chat.title}" приветствует вас, {event.user.first_name}!'
    )


async def main():
    await dp.start_polling(bot)


if __name__ == '__main__':
    asyncio.run(main())

MagicFilter

Использование MagicFilter для гибкой фильтрации сообщений:

import asyncio
import logging

from maxapi import Bot, Dispatcher, F
from maxapi.types import MessageCreated

logging.basicConfig(level=logging.INFO)

bot = Bot()
dp = Dispatcher()


@dp.message_created(F.message.body.text == 'привет')
async def on_hello(event: MessageCreated):
    await event.message.answer('Привет!')


@dp.message_created(F.message.body.text.lower().contains('помощь'))
async def on_help(event: MessageCreated):
    await event.message.answer('Чем могу помочь?')


@dp.message_created(F.message.body.text.regexp(r'^\d{4}$'))
async def on_code(event: MessageCreated):
    await event.message.answer('Принят 4-значный код')


@dp.message_created(F.message.body.attachments)
async def on_attachment(event: MessageCreated):
    await event.message.answer('Получено вложение')


@dp.message_created(F.message.body.text.len() > 20)
async def on_long_text(event: MessageCreated):
    await event.message.answer('Слишком длинное сообщение')


@dp.message_created(F.message.body.text.len() > 0)
async def on_non_empty(event: MessageCreated):
    await event.message.answer('Вы что-то написали.')


async def main():
    await dp.start_polling(bot)


if __name__ == '__main__':
    asyncio.run(main())

Клавиатуры

Примеры работы с различными типами кнопок и клавиатур:

import asyncio
import logging

from maxapi import Bot, Dispatcher
from maxapi.types import (
    ChatButton,  # deprecated: 0.9.14
    LinkButton, 
    CallbackButton, 
    RequestGeoLocationButton, 
    MessageButton, 
    ButtonsPayload,
    RequestContactButton, 
    OpenAppButton,
    MessageCreated, 
    MessageCallback, 
    MessageChatCreated, # deprecated: 0.9.14
    CommandStart, 
    Command
)
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder

logging.basicConfig(level=logging.INFO)

bot = Bot()
dp = Dispatcher()


@dp.message_created(CommandStart())
async def echo(event: MessageCreated):
    await event.message.answer(
        (
            'Привет! Мои команды:\n\n'

            '/builder - Клавиатура из InlineKeyboardBuilder\n'
            '/payload - Клавиатура из pydantic моделей\n'
        )
    )


@dp.message_created(Command('builder'))
async def builder(event: MessageCreated):
    builder = InlineKeyboardBuilder()

    builder.row(
        ChatButton(  # deprecated: 0.9.14
            text="Создать чат", 
            chat_title='Test', 
            chat_description='Test desc'
        ),
        LinkButton(
            text="Документация MAX", 
            url="https://dev.max.ru/docs"
        ),
    )

    builder.row(
        RequestGeoLocationButton(text="Геолокация"),
        MessageButton(text="Сообщение"),
    )

    builder.row(
        RequestContactButton(text="Контакт"),
        OpenAppButton(
            text="Приложение", 
            web_app=event.bot.me.username, 
            contact_id=event.bot.me.user_id
        ),
    )

    builder.row(
        CallbackButton(
            text='Callback',
            payload='test',
        )
    )

    await event.message.answer(
        text='Клавиатура из InlineKeyboardBuilder',
        attachments=[
            builder.as_markup()
        ])


@dp.message_created(Command('payload'))
async def payload(event: MessageCreated):
    buttons = [
        [
            ChatButton(  # deprecated: 0.9.14
                text="Создать чат", 
                chat_title='Test', 
                chat_description='Test desc'
            ),
            LinkButton(
                text="Документация MAX", 
                url="https://dev.max.ru/docs"
            ),
        ],
        [
            RequestGeoLocationButton(text="Геолокация"),
            MessageButton(text="Сообщение"),
        ],
        [
            RequestContactButton(text="Контакт"),
            OpenAppButton(
                text="Приложение", 
                web_app=event.bot.me.username, 
                contact_id=event.bot.me.user_id
            ),
        ],
        [
            CallbackButton(
                text='Callback',
                payload='test',
            )
        ]
    ]

    buttons_payload = ButtonsPayload(buttons=buttons).pack()

    await event.message.answer(
        text='Клавиатура из pydantic моделей',
        attachments=[
            buttons_payload
        ])


@dp.message_chat_created()  # deprecated: 0.9.14
async def message_chat_created(obj: MessageChatCreated):
    await obj.bot.send_message(
        chat_id=obj.chat.chat_id,
        text=f'Чат создан! Ссылка: {obj.chat.link}'
    )


@dp.message_callback()
async def message_callback(callback: MessageCallback):
    await callback.message.answer('Вы нажали на Callback!')


async def main():
    await dp.start_polling(bot)


if __name__ == '__main__':
    asyncio.run(main())

Получение ID

Пример получения различных ID из событий:

import asyncio
import logging

from maxapi import Bot, Dispatcher, F
from maxapi.enums.parse_mode import ParseMode
from maxapi.types import MessageCreated

logging.basicConfig(level=logging.INFO)

bot = Bot()
dp = Dispatcher()


@dp.message_created(F.message.link.type == 'forward')
async def get_ids_from_forward(event: MessageCreated):
    text = (
        'Информация о пересланном сообщении:\n\n'

        f'Из чата: <b>{event.message.link.chat_id}</b>\n'
        f'От пользователя: <b>{event.message.link.sender.user_id}</b>'
    )
    await event.message.reply(text)


@dp.message_created()
async def get_ids(event: MessageCreated):
    text = (
        f'Ваш ID: <b>{event.from_user.user_id}</b>\n'
        f'ID этого чата: <b>{event.chat.chat_id}</b>'
    )
    await event.message.answer(text, parse_mode=ParseMode.HTML)


async def main():
    await dp.start_polling(bot)


if __name__ == '__main__':
    asyncio.run(main())

Создание собственного фильтра

Пример создания кастомного фильтра на основе BaseFilter:

import asyncio
import logging

from maxapi import Bot, Dispatcher
from maxapi.types import MessageCreated, CommandStart, UpdateUnion
from maxapi.filters import BaseFilter

logging.basicConfig(level=logging.INFO)

bot = Bot()
dp = Dispatcher()


class FilterChat(BaseFilter):

    """
    Фильтр, который срабатывает только в чате с названием `Test`
    """

    async def __call__(self, event: UpdateUnion):

        if not event.chat:
            return False

        return event.chat == 'Test'


@dp.message_created(CommandStart(), FilterChat())
async def custom_data(event: MessageCreated):
    await event.message.answer('Привет!')


async def main():
    await dp.start_polling(bot)


if __name__ == '__main__':
    asyncio.run(main())

Фильтр callback payload

Пример использования типизированных payload для callback-кнопок:

import asyncio
import logging

from maxapi import Bot, Dispatcher, F
from maxapi.filters.callback_payload import CallbackPayload
from maxapi.filters.command import CommandStart
from maxapi.types import (
    CallbackButton,
    MessageCreated,
    MessageCallback,
)
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder

logging.basicConfig(level=logging.INFO)

bot = Bot()
dp = Dispatcher()


class MyPayload(CallbackPayload, prefix='mypayload'):
    foo: str
    action: str


class AnotherPayload(CallbackPayload, prefix='another'):
    bar: str
    value: int


@dp.message_created(CommandStart())
async def show_keyboard(event: MessageCreated):
    kb = InlineKeyboardBuilder()
    kb.row(
        CallbackButton( 
            text='Первая кнопка',
            payload=MyPayload(foo='123', action='edit').pack(), 
        ), 
        CallbackButton(
            text='Вторая кнопка',
            payload=AnotherPayload(bar='abc', value=42).pack(),
        ),
    )
    await event.message.answer('Нажми кнопку!', attachments=[kb.as_markup()])


@dp.message_callback(MyPayload.filter(F.foo == '123'))
async def on_first_callback(event: MessageCallback, payload: MyPayload):
    await event.answer(new_text=f'Первая кнопка: foo={payload.foo}, action={payload.action}')


@dp.message_callback(AnotherPayload.filter())
async def on_second_callback(event: MessageCallback, payload: AnotherPayload):
    await event.answer(new_text=f'Вторая кнопка: bar={payload.bar}, value={payload.value}')


async def main():
    await dp.start_polling(bot)


if __name__ == '__main__':
    asyncio.run(main())

Middleware в обработчиках

Пример использования middleware на уровне обработчиков:

import asyncio
import logging

from typing import Any, Awaitable, Callable, Dict

from maxapi import Bot, Dispatcher
from maxapi.filters.middleware import BaseMiddleware
from maxapi.types import MessageCreated, Command, UpdateUnion

logging.basicConfig(level=logging.INFO)

bot = Bot()
dp = Dispatcher()


class CheckChatTitleMiddleware(BaseMiddleware):
    async def __call__(
        self,
        handler: Callable[[Any, Dict[str, Any]], Awaitable[Any]],
        event_object: UpdateUnion,
        data: Dict[str, Any],
    ) -> Any:

        if event_object.chat.title == 'MAXApi':
            return await handler(event_object, data)


class CustomDataMiddleware(BaseMiddleware):
    async def __call__(
        self,
        handler: Callable[[Any, Dict[str, Any]], Awaitable[Any]],
        event_object: UpdateUnion,
        data: Dict[str, Any],
    ) -> Any:

        data['custom_data'] = f'Это ID того кто вызвал команду: {event_object.from_user.user_id}'

        await handler(event_object, data)


@dp.message_created(Command('start'), CheckChatTitleMiddleware())
async def start(event: MessageCreated):
    await event.message.answer('Это сообщение было отправлено, так как ваш чат называется "MAXApi"!')


@dp.message_created(Command('custom_data'), CustomDataMiddleware())
async def custom_data(event: MessageCreated, custom_data: str):
    await event.message.answer(custom_data)


@dp.message_created(Command('many_middlewares'), CheckChatTitleMiddleware(), CustomDataMiddleware())
async def many_middlewares(event: MessageCreated, custom_data: str):
    await event.message.answer('Это сообщение было отправлено, так как ваш чат называется "MAXApi"!')
    await event.message.answer(custom_data)


async def main():
    await dp.start_polling(bot)


if __name__ == '__main__':
    asyncio.run(main())

Middleware в роутерах

Пример использования middleware на уровне роутера:

import asyncio
import logging

from typing import Any, Awaitable, Callable, Dict

from maxapi import Bot, Dispatcher
from maxapi.types import MessageCreated, Command, UpdateUnion
from maxapi.filters.middleware import BaseMiddleware

logging.basicConfig(level=logging.INFO)

bot = Bot()
dp = Dispatcher()


class CustomDataForRouterMiddleware(BaseMiddleware):
    async def __call__(
        self,
        handler: Callable[[Any, Dict[str, Any]], Awaitable[Any]],
        event_object: UpdateUnion,
        data: Dict[str, Any],
    ) -> Any:

        data['custom_data'] = f'Это ID того кто вызвал команду: {event_object.from_user.user_id}'
        result = await handler(event_object, data)
        return result


@dp.message_created(Command('custom_data'))
async def custom_data(event: MessageCreated, custom_data: str):
    await event.message.answer(custom_data)


async def main():
    dp.middleware(CustomDataForRouterMiddleware())

    await dp.start_polling(bot)


if __name__ == '__main__':
    asyncio.run(main())

Роутеры, InputMedia и контекст

Пример использования роутеров, InputMedia и работы с контекстом и состояниями:

import asyncio
import logging

from maxapi import Bot, Dispatcher, F
from maxapi.context import MemoryContext, State, StatesGroup
from maxapi.types import BotStarted, Command, MessageCreated, CallbackButton, MessageCallback, BotCommand
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder

from router import router

logging.basicConfig(level=logging.INFO)

bot = Bot()
dp = Dispatcher()

dp.include_routers(router)


start_text = '''Пример чат-бота для MAX 💙

Мои команды:

/clear очищает ваш контекст
/state или /context показывают ваше контекстное состояние
/data показывает вашу контекстную память
'''


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


@dp.on_started()
async def _():
    logging.info('Бот стартовал!')


@dp.bot_started()
async def bot_started(event: BotStarted):
    await event.bot.send_message(
        chat_id=event.chat_id,
        text='Привет! Отправь мне /start'
    )


@dp.message_created(Command('clear'))
async def hello(event: MessageCreated, context: MemoryContext):
    await context.clear()
    await event.message.answer(f"Ваш контекст был очищен!")


@dp.message_created(Command('data'))
async def hello(event: MessageCreated, context: MemoryContext):
    data = await context.get_data()
    await event.message.answer(f"Ваша контекстная память: {str(data)}")


@dp.message_created(Command('context'))
@dp.message_created(Command('state'))
async def hello(event: MessageCreated, context: MemoryContext):
    data = await context.get_state()
    await event.message.answer(f"Ваше контекстное состояние: {str(data)}")


@dp.message_created(Command('start'))
async def hello(event: MessageCreated):
    builder = InlineKeyboardBuilder()

    builder.row(
        CallbackButton(
            text='Ввести свое имя',
            payload='btn_1'
        ),
        CallbackButton(
            text='Ввести свой возраст',
            payload='btn_2'
        )
    )
    builder.row(
        CallbackButton(
            text='Не хочу',
            payload='btn_3'
        )
    )

    await event.message.answer(
        text=start_text, 
        attachments=[
            builder.as_markup(),
        ]
    )


@dp.message_callback(F.callback.payload == 'btn_1')
async def hello(event: MessageCallback, context: MemoryContext):
    await context.set_state(Form.name)
    await event.message.delete()
    await event.message.answer(f'Отправьте свое имя:')


@dp.message_callback(F.callback.payload == 'btn_2')
async def hello(event: MessageCallback, context: MemoryContext):
    await context.set_state(Form.age)
    await event.message.delete()
    await event.message.answer(f'Отправьте ваш возраст:')


@dp.message_callback(F.callback.payload == 'btn_3')
async def hello(event: MessageCallback, context: MemoryContext):
    await event.message.delete()
    await event.message.answer(f'Ну ладно 🥲')


@dp.message_created(F.message.body.text, Form.name)
async def hello(event: MessageCreated, context: MemoryContext):
    await context.update_data(name=event.message.body.text)

    data = await context.get_data()

    await event.message.answer(f"Приятно познакомиться, {data['name'].title()}!")


@dp.message_created(F.message.body.text, Form.age)
async def hello(event: MessageCreated, context: MemoryContext):
    await context.update_data(age=event.message.body.text)

    await event.message.answer(f"Ого! А мне всего пару недель 😁")


async def main():
    await bot.set_my_commands(
        BotCommand(
            name='/start',
            description='Перезапустить бота'
        ),
        BotCommand(
            name='/clear',
            description='Очищает ваш контекст'
        ),
        BotCommand(
            name='/state',
            description='Показывают ваше контекстное состояние'
        ),
        BotCommand(
            name='/data',
            description='Показывает вашу контекстную память'
        ),
        BotCommand(
            name='/context',
            description='Показывают ваше контекстное состояние'
        )
    )
    await dp.start_polling(bot)


if __name__ == '__main__':
    asyncio.run(main())

И соответствующий роутер (router.py):

from maxapi import F, Router
from maxapi.types import Command, MessageCreated
from maxapi.types import InputMedia

router = Router()
file = __file__.split('\\')[-1]


@router.message_created(Command('router'))
async def hello(obj: MessageCreated):
    await obj.message.answer(f"Пишу тебе из роута {file}")


# новая команда для примера, /media, 
# пример использования: /media image.png (медиафайл берется указанному пути)
@router.message_created(Command('media'))
async def hello(event: MessageCreated):
    await event.message.answer(
        attachments=[
            InputMedia(
                path=event.message.body.text.replace('/media ', '')
            )
        ]
    )

Webhook

Высокоуровневый подход

Простой способ работы с webhook через встроенный метод:

import asyncio
import logging

from maxapi import Bot, Dispatcher
from maxapi.types import MessageCreated

logging.basicConfig(level=logging.INFO)

bot = Bot()
dp = Dispatcher()


@dp.message_created()
async def handle_message(event: MessageCreated):
    await event.message.answer('Бот работает через вебхук!')


async def main():
    await dp.handle_webhook(
        bot=bot, 
        host='localhost',
        port=8080,
        log_level='critical'
    )


if __name__ == '__main__':
    asyncio.run(main())

Низкоуровневый подход

Более гибкий способ с использованием FastAPI:

import asyncio
import logging

try:
    from fastapi import Request
    from fastapi.responses import JSONResponse
except ImportError:
    raise ImportError(
        '\n\t Не установлен fastapi!'
        '\n\t Выполните команду для установки fastapi: '
        '\n\t pip install fastapi>=0.68.0'
        '\n\t Или сразу все зависимости для работы вебхука:'
        '\n\t pip install maxapi[webhook]'
    )

from maxapi import Bot, Dispatcher
from maxapi.methods.types.getted_updates import process_update_webhook
from maxapi.types import MessageCreated

logging.basicConfig(level=logging.INFO)

bot = Bot()
dp = Dispatcher()


@dp.message_created()
async def handle_message(event: MessageCreated):
    await event.message.answer('Бот работает через вебхук!')

# Регистрация обработчика для вебхука
@dp.webhook_post('/')
async def _(request: Request):

    # Сериализация полученного запроса
    event_json = await request.json()

    # Десериализация полученного запроса в pydantic
    event_object = await process_update_webhook(
        event_json=event_json,
        bot=bot
    )

    # ...свой код
    print(f'Информация из вебхука: {event_json}')
    # ...свой код

    # Окончательная обработка запроса
    await dp.handle(event_object)

    # Ответ вебхука
    return JSONResponse(content={'ok': True}, status_code=200)


async def main():
    # Запуск сервера
    await dp.init_serve(bot, log_level='critical')


if __name__ == '__main__':
    asyncio.run(main())

Настройка прокси

Подключение через прокси

Для использования прокси-сервера передайте параметр proxy в DefaultConnectionProperties:

import asyncio
from maxapi import Bot, Dispatcher
from maxapi.client import DefaultConnectionProperties
from maxapi.types import MessageCreated, Command

# URL прокси в формате: http://login:password@ip:port
proxy_url = "http://login:password@ip:port"

# Создание настроек соединения с прокси
connection_props = DefaultConnectionProperties(proxy=proxy_url)

# Инициализация бота с настройками соединения
bot = Bot(default_connection=connection_props)
dp = Dispatcher()

@dp.message_created(Command('start'))
async def start_handler(event: MessageCreated):
    await event.message.answer("Привет!")

async def main():
    await dp.start_polling(bot)

if __name__ == '__main__':
    asyncio.run(main())

Использование прокси из переменных окружения

Для использования прокси из переменных окружения используйте параметр trust_env=True:

import asyncio
from maxapi import Bot, Dispatcher
from maxapi.client import DefaultConnectionProperties
from maxapi.types import MessageCreated, Command

bot = Bot(
    "YOUR-TOKEN",
    default_connection=DefaultConnectionProperties(trust_env=True),
)
dp = Dispatcher()

@dp.message_created(Command('start'))
async def start_handler(event: MessageCreated):
    await event.message.answer("Привет!")

async def main():
    await dp.start_polling(bot)

if __name__ == '__main__':
    asyncio.run(main())

Что такое trust_env?

Параметр trust_env=True в aiohttp позволяет автоматически читать настройки прокси из переменных окружения системы. Когда этот параметр включен, aiohttp будет искать следующие переменные окружения:

  • HTTP_PROXY — прокси для HTTP-запросов (например, http://proxy.example.com:8080)
  • HTTPS_PROXY — прокси для HTTPS-запросов (например, https://proxy.example.com:8080)
  • NO_PROXY — список доменов, для которых прокси не используется (например, localhost,127.0.0.1,*.local)

Важно: Если переменные окружения не установлены, trust_env=True не вызовет ошибку — просто прокси использоваться не будет.