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

Package: maxapi.utils

maxapi.utils

commands

extract_commands(handler, bot)

Извлечь команды из обработчика и добавить их в бота.

Source code in maxapi/utils/commands.py
def extract_commands(handler: Handler, bot: Bot) -> None:
    """Извлечь команды из обработчика и добавить их в бота."""
    if handler.base_filters is None:
        return

    handler_info = get_handler_info(handler)

    for base_filter in handler.base_filters:
        commands = getattr(base_filter, "commands", None)

        if commands and isinstance(commands, list):
            command = CommandsInfo(commands=commands, info=handler_info)
            bot.commands.append(command)

get_handler_info(handler)

Получить описание обработчика.

Source code in maxapi/utils/commands.py
def get_handler_info(handler: Handler) -> str | None:
    """Получить описание обработчика."""
    handler_doc = handler.func_event.__doc__
    if not handler_doc:
        return None

    from_pattern = search(
        pattern=COMMANDS_INFO_PATTERN, string=handler_doc, flags=DOTALL
    )
    if not from_pattern:
        return None

    info = from_pattern.group(1).strip()
    return info or None

formatting

Text(*parts)

Bases: _Node

Контейнер — объединяет несколько элементов без обёрток.

Пример::

Text("Hello, ", Bold("world"), "!")
Source code in maxapi/utils/formatting.py
def __init__(self, *parts: Any) -> None:
    self._parts: list[_Node] = []
    for p in parts:
        if isinstance(p, _Node):
            if isinstance(p, Text):
                self._parts.extend(p._parts)  # noqa: SLF001
            else:
                self._parts.append(p)
        else:
            self._parts.append(_Plain(p))

Bold(*parts)

Bases: _Styled

Жирный текст: <b> / **...**.

Source code in maxapi/utils/formatting.py
def __init__(self, *parts: Any) -> None:
    self._inner = Text(*parts)

Italic(*parts)

Bases: _Styled

Курсив: <i> / *...*.

Source code in maxapi/utils/formatting.py
def __init__(self, *parts: Any) -> None:
    self._inner = Text(*parts)

Underline(*parts)

Bases: _Styled

Подчёркнутый текст: <ins> / ++...++.

Source code in maxapi/utils/formatting.py
def __init__(self, *parts: Any) -> None:
    self._inner = Text(*parts)

Strikethrough(*parts)

Bases: _Styled

Зачёркнутый текст: <s> / ~~...~~.

Source code in maxapi/utils/formatting.py
def __init__(self, *parts: Any) -> None:
    self._inner = Text(*parts)

Code(*parts)

Bases: _Styled

Моноширинный (inline code): <code> / `...`.

Source code in maxapi/utils/formatting.py
def __init__(self, *parts: Any) -> None:
    self._inner = Text(*parts)

Heading(*parts)

Bases: _Styled

Заголовок: <b> / ### ....

Source code in maxapi/utils/formatting.py
def __init__(self, *parts: Any) -> None:
    self._inner = Text(*parts)

Bases: _Node

Гиперссылка: <a href="url">text</a> / [text](url).

Parameters:

Name Type Description Default
text

Текст ссылки (строки или узлы форматирования).

required
url str

URL ссылки.

required
Source code in maxapi/utils/formatting.py
def __init__(self, *parts: Any, url: str) -> None:
    self._inner = Text(*parts)
    self._url = url

UserMention(display_text, user_id)

Bases: _Node

Упоминание пользователя по отображаемому тексту и user_id.

В MAX API: ссылка max://user/<user_id>, текст — полное имя.

Parameters:

Name Type Description Default
display_text Any

Текст упоминания (полное имя: имя и фамилия, или только имя).

required
user_id int

ID пользователя для URL (max://user/user_id).

required
Source code in maxapi/utils/formatting.py
def __init__(self, display_text: Any, user_id: int) -> None:
    self._text = str(display_text)
    self._user_id = user_id

as_html(*parts)

Собирает HTML-строку из набора элементов.

Пример::

as_html("Hello, ", Bold("world"), "!")
# 'Hello, <b>world</b>!'
Source code in maxapi/utils/formatting.py
def as_html(*parts: Any) -> str:
    """Собирает HTML-строку из набора элементов.

    Пример::

        as_html("Hello, ", Bold("world"), "!")
        # 'Hello, <b>world</b>!'
    """
    return Text(*parts).as_html()

as_markdown(*parts)

Собирает Markdown-строку из набора элементов.

Пример::

as_markdown("Hello, ", Bold("world"), "!")
# 'Hello, **world**!'
Source code in maxapi/utils/formatting.py
def as_markdown(*parts: Any) -> str:
    """Собирает Markdown-строку из набора элементов.

    Пример::

        as_markdown("Hello, ", Bold("world"), "!")
        # 'Hello, **world**!'
    """
    return Text(*parts).as_markdown()

inline_keyboard

InlineKeyboardBuilder()

Конструктор инлайн-клавиатур.

Позволяет удобно собирать кнопки в ряды и формировать из них клавиатуру для отправки в сообщениях.

Source code in maxapi/utils/inline_keyboard.py
def __init__(self):
    self.payload: list[list[InlineButtonUnion]] = [[]]
row(*buttons)

Добавить новый ряд кнопок в клавиатуру.

Parameters:

Name Type Description Default
*buttons InlineButtonUnion

Произвольное количество кнопок для добавления в ряд.

()
Source code in maxapi/utils/inline_keyboard.py
def row(self, *buttons: InlineButtonUnion) -> InlineKeyboardBuilder:
    """
    Добавить новый ряд кнопок в клавиатуру.

    Args:
        *buttons: Произвольное количество кнопок для добавления в ряд.
    """

    if not self.payload[-1]:
        self.payload[-1].extend(buttons)
    else:
        self.payload.append([*buttons])
    return self
add(*buttons)

Добавить кнопки в последний ряд клавиатуры.

Parameters:

Name Type Description Default
*buttons InlineButtonUnion

Кнопки для добавления.

()
Source code in maxapi/utils/inline_keyboard.py
def add(self, *buttons: InlineButtonUnion) -> InlineKeyboardBuilder:
    """
    Добавить кнопки в последний ряд клавиатуры.

    Args:
        *buttons: Кнопки для добавления.
    """

    for button in buttons:
        self.payload[-1].append(button)
    return self
adjust(*sizes)

Перераспределить кнопки по рядам в соответствии с указанными размерами.

Parameters:

Name Type Description Default
*sizes int

Количество кнопок в каждом ряду. Если кнопок больше, чем сумма размеров, размеры повторяются циклично.

()

Returns:

Name Type Description
InlineKeyboardBuilder InlineKeyboardBuilder

Текущий объект для цепочки вызовов.

Source code in maxapi/utils/inline_keyboard.py
def adjust(self, *sizes: int) -> InlineKeyboardBuilder:
    """
    Перераспределить кнопки по рядам в соответствии с указанными размерами.

    Args:
        *sizes: Количество кнопок в каждом ряду.
               Если кнопок больше, чем сумма размеров, размеры
               повторяются циклично.

    Returns:
        InlineKeyboardBuilder: Текущий объект для цепочки вызовов.
    """
    if not sizes:
        sizes = (1,)

    flat_buttons = []
    for row in self.payload:
        flat_buttons.extend(row)

    if not flat_buttons:
        return self

    new_payload: list[list[InlineButtonUnion]] = []
    button_index = 0
    size_index = 0

    while button_index < len(flat_buttons):
        size = sizes[size_index % len(sizes)]
        if size <= 0:
            size = 1
        row_buttons = flat_buttons[button_index : button_index + size]
        new_payload.append(row_buttons)
        button_index += size
        size_index += 1

    self.payload = new_payload
    return self
as_markup()

Собрать клавиатуру в объект для отправки.

Returns:

Name Type Description
Attachment Attachment

Объект вложения с типом INLINE_KEYBOARD.

Source code in maxapi/utils/inline_keyboard.py
def as_markup(self) -> Attachment:
    """
    Собрать клавиатуру в объект для отправки.

    Returns:
        Attachment: Объект вложения с типом INLINE_KEYBOARD.
    """

    return Attachment(
        type=AttachmentType.INLINE_KEYBOARD,
        payload=ButtonsPayload(buttons=self.payload),
    )  # type: ignore

message

process_input_media(base_connection, bot, att) async

Загружает файл вложения и формирует объект AttachmentUpload.

Parameters:

Name Type Description Default
base_connection BaseConnection

Базовое соединение для загрузки файла.

required
bot Bot

Экземпляр бота.

required
att InputMedia | InputMediaBuffer

Объект вложения для загрузки.

required

Returns:

Name Type Description
AttachmentUpload AttachmentUpload

Загруженное вложение с токеном.

Source code in maxapi/utils/message.py
async def process_input_media(
    base_connection: BaseConnection,
    bot: Bot,
    att: InputMedia | InputMediaBuffer,
) -> AttachmentUpload:
    """
    Загружает файл вложения и формирует объект AttachmentUpload.

    Args:
        base_connection (BaseConnection): Базовое соединение для
            загрузки файла.
        bot (Bot): Экземпляр бота.
        att (InputMedia | InputMediaBuffer): Объект вложения
            для загрузки.

    Returns:
        AttachmentUpload: Загруженное вложение с токеном.
    """

    upload = await _get_upload_info(bot=bot, upload_type=att.type)
    upload_file_response = await _upload_input_media(
        base_connection=base_connection,
        upload_url=upload.url,
        att=att,
    )
    token = await _resolve_attachment_token(
        bot=bot,
        upload_type=att.type,
        upload_token=upload.token,
        upload_file_response=upload_file_response,
    )

    return AttachmentUpload(
        type=att.type, payload=AttachmentPayload(token=token)
    )

time

to_ms(value)

Преобразует datetime или числовую метку времени в миллисекунды (int).

Если value — объект datetime, возвращает int(timestamp * 1000). Если value — int или float (уже временная метка), возвращает int(value).

Source code in maxapi/utils/time.py
def to_ms(value: datetime | float) -> int:
    """Преобразует datetime или числовую метку времени в миллисекунды (int).

    Если `value` — объект datetime, возвращает int(timestamp * 1000).
    Если `value` — int или float (уже временная метка), возвращает int(value).
    """
    if isinstance(value, datetime):
        return int(value.timestamp() * 1000)
    return int(value)

from_ms(value)

Преобразует миллисекунды с эпохи в объект datetime.

Если value равен None, возвращает None. Иначе возвращает datetime.fromtimestamp(value / 1000).

Source code in maxapi/utils/time.py
def from_ms(value: float | None) -> datetime | None:
    """Преобразует миллисекунды с эпохи в объект datetime.

    Если value равен None, возвращает None.
    Иначе возвращает datetime.fromtimestamp(value / 1000).
    """
    if value is None:
        return None
    return datetime.fromtimestamp(int(value) / 1000)

updates

enrich_event(event_object, bot) async

Дополняет объект события данными чата, пользователя и ссылкой на бота.

Parameters:

Name Type Description Default
event_object Any

Событие, которое нужно дополнить.

required
bot Bot

Экземпляр бота.

required

Returns:

Name Type Description
Any Any

Обновлённый объект события.

Source code in maxapi/utils/updates.py
async def enrich_event(event_object: Any, bot: Bot) -> Any:
    """
    Дополняет объект события данными чата, пользователя и ссылкой на бота.

    Args:
        event_object (Any): Событие, которое нужно дополнить.
        bot (Bot): Экземпляр бота.

    Returns:
        Any: Обновлённый объект события.
    """

    if not bot.auto_requests:
        return event_object

    # Определяем заранее: чат недоступен (удалён или бот убран из канала)
    is_chat_unavailable = isinstance(event_object, DialogRemoved) or (
        isinstance(event_object, BotRemoved)
        and getattr(event_object, "is_channel", False)
    )

    if hasattr(event_object, "chat_id"):
        # Если чат недоступен — не пытаемся его получить
        if not is_chat_unavailable:
            event_object.chat = await bot.get_chat_by_id(event_object.chat_id)
        else:
            event_object.chat = None

    if isinstance(event_object, (MessageCreated, MessageEdited)):
        recipient = event_object.message.recipient
        if recipient.chat_id is not None and event_object.chat is None:
            event_object.chat = await bot.get_chat_by_id(recipient.chat_id)

        event_object.from_user = getattr(event_object.message, "sender", None)

    elif isinstance(event_object, MessageCallback):
        message = event_object.message
        if message is not None and message.recipient.chat_id is not None:
            chat_id = message.recipient.chat_id
            if event_object.chat is None:
                event_object.chat = await bot.get_chat_by_id(chat_id)

        event_object.from_user = getattr(event_object.callback, "user", None)

    elif isinstance(event_object, MessageRemoved):
        if event_object.chat is None:
            event_object.chat = await bot.get_chat_by_id(event_object.chat_id)

        if event_object.chat and event_object.chat.type == ChatType.CHAT:
            event_object.from_user = await bot.get_chat_member(
                chat_id=event_object.chat_id, user_id=event_object.user_id
            )

        elif event_object.chat and event_object.chat.type == ChatType.DIALOG:
            event_object.from_user = event_object.chat

    elif isinstance(event_object, UserRemoved):
        if event_object.chat is None:
            event_object.chat = await bot.get_chat_by_id(event_object.chat_id)
        if event_object.admin_id:
            event_object.from_user = await bot.get_chat_member(
                chat_id=event_object.chat_id, user_id=event_object.admin_id
            )

    elif isinstance(
        event_object,
        (
            UserAdded,
            BotAdded,
            BotRemoved,
            BotStarted,
            ChatTitleChanged,
            BotStopped,
            DialogCleared,
            DialogMuted,
            DialogUnmuted,
        ),
    ):
        if event_object.chat is None and not is_chat_unavailable:
            event_object.chat = await bot.get_chat_by_id(event_object.chat_id)
        event_object.from_user = event_object.user

    elif isinstance(event_object, DialogRemoved):
        # Чат уже удалён — получить его невозможно
        event_object.from_user = event_object.user

    if isinstance(
        event_object, (MessageCreated, MessageEdited, MessageCallback)
    ):
        object_message = event_object.message

        if object_message is not None:
            object_message.bot = bot
            if object_message.body is not None:
                for att in object_message.body.attachments or []:
                    if hasattr(att, "bot"):
                        att.bot = bot

    if hasattr(event_object, "bot"):
        event_object.bot = bot

    return event_object

vcf

VcfInfo(full_name, phones, fields) dataclass

Результат парсинга VCF строки.

phone property

Первый телефон (если есть).

parse_vcf_info(vcf_info)

Парсит строку VCF, возвращая извлечённые поля.

Поддерживаемые поля: - FN: полное имя (full_name) - TEL: телефоны (phones)

Source code in maxapi/utils/vcf.py
def parse_vcf_info(vcf_info: str) -> VcfInfo:
    """Парсит строку VCF, возвращая извлечённые поля.

    Поддерживаемые поля:
    - FN: полное имя (full_name)
    - TEL: телефоны (phones)
    """

    if not vcf_info:
        return VcfInfo(full_name=None, phones=(), fields={})

    lines = [ln.strip() for ln in vcf_info.replace("\r\n", "\n").split("\n")]
    lines = [ln for ln in lines if ln]

    fields: dict[str, list[str]] = {}

    inside = False
    for line in lines:
        upper = line.upper()
        if upper == "BEGIN:VCARD":
            inside = True
            continue
        if upper == "END:VCARD":
            break
        if not inside:
            continue

        if ":" not in line:
            continue

        left, value = line.split(":", 1)
        key = left.split(";", 1)[0].strip().upper()
        value = value.strip()
        if not key:
            continue

        fields.setdefault(key, []).append(value)

    norm_fields: dict[str, tuple[str, ...]] = {
        k: tuple(v) for k, v in fields.items()
    }

    full_name = norm_fields.get("FN", (None,))[0]
    phones = norm_fields.get("TEL", ())

    return VcfInfo(full_name=full_name, phones=phones, fields=norm_fields)