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

API Reference

MaxClient

pymax.MaxClient(phone: str, uri: str = Constants.WEBSOCKET_URI.value, work_dir: str = '.', logger: logging.Logger | None = None)

Основной клиент для работы с WebSocket API сервиса Max.

Parameters:

Name Type Description Default
phone str

Номер телефона для авторизации.

required
uri str

URI WebSocket сервера. По умолчанию Constants.WEBSOCKET_URI.value.

WEBSOCKET_URI.value
work_dir str

Рабочая директория для хранения базы данных. По умолчанию ".".

'.'
logger Logger | None

Пользовательский логгер. Если не передан — используется логгер модуля с именем f"{name}.MaxClient".

None

Raises:

Type Description
InvalidPhoneError

Если формат номера телефона неверный.

Source code in src\pymax\core.py
def __init__(
    self,
    phone: str,
    uri: str = Constants.WEBSOCKET_URI.value,
    work_dir: str = ".",
    logger: logging.Logger | None = None,
) -> None:
    self.uri: str = uri
    self.is_connected: bool = False
    self.phone: str = phone
    self.chats: list[Chat] = []
    self.dialogs: list[Dialog] = []
    self.channels: list[Channel] = []
    self._users: dict[int, User] = {}
    if not self._check_phone():
        raise InvalidPhoneError(self.phone)
    self._work_dir: str = work_dir
    self._database_path: Path = Path(work_dir) / "session.db"
    self._database_path.parent.mkdir(parents=True, exist_ok=True)
    self._database_path.touch(exist_ok=True)
    self._database = Database(self._work_dir)
    self._ws: websockets.ClientConnection | None = None
    self._seq: int = 0
    self._pending: dict[int, asyncio.Future[dict[str, Any]]] = {}
    self._recv_task: asyncio.Task[Any] | None = None
    self._incoming: asyncio.Queue[dict[str, Any]] | None = None
    self._device_id = self._database.get_device_id()
    self._token = self._database.get_auth_token()
    self.user_agent = Constants.DEFAULT_USER_AGENT.value
    self._on_message_handler: Callable[[Message], Any] | None = None
    self._on_start_handler: Callable[[], Any | Awaitable[Any]] | None = None
    self._background_tasks: set[asyncio.Task[Any]] = set()
    self.logger = logger or logging.getLogger(f"{__name__}.MaxClient")
    self._setup_logger()

    self.logger.debug(
        "Initialized MaxClient uri=%s work_dir=%s", self.uri, self._work_dir
    )

Functions

on_message(handler: Callable[[Message], Any | Awaitable[Any]]) -> Callable[[Message], Any | Awaitable[Any]]

Устанавливает обработчик входящих сообщений.

Parameters:

Name Type Description Default
handler Callable[[Message], Any | Awaitable[Any]]

Функция или coroutine, принимающая объект Message.

required

Returns:

Type Description
Callable[[Message], Any | Awaitable[Any]]

Установленный обработчик.

Source code in src\pymax\core.py
def on_message(
    self, handler: Callable[[Message], Any | Awaitable[Any]]
) -> Callable[[Message], Any | Awaitable[Any]]:
    """
    Устанавливает обработчик входящих сообщений.

    Args:
        handler: Функция или coroutine, принимающая объект Message.

    Returns:
        Установленный обработчик.
    """
    self._on_message_handler = handler
    self.logger.debug("on_message handler set: %r", handler)
    return handler

on_start(handler: Callable[[], Any | Awaitable[Any]]) -> Callable[[], Any | Awaitable[Any]]

Устанавливает обработчик, вызываемый при старте клиента.

Parameters:

Name Type Description Default
handler Callable[[], Any | Awaitable[Any]]

Функция или coroutine без аргументов.

required

Returns:

Type Description
Callable[[], Any | Awaitable[Any]]

Установленный обработчик.

Source code in src\pymax\core.py
def on_start(
    self, handler: Callable[[], Any | Awaitable[Any]]
) -> Callable[[], Any | Awaitable[Any]]:
    """
    Устанавливает обработчик, вызываемый при старте клиента.

    Args:
        handler: Функция или coroutine без аргументов.

    Returns:
        Установленный обработчик.
    """
    self._on_start_handler = handler
    self.logger.debug("on_start handler set: %r", handler)
    return handler

send_message(text: str, chat_id: int, notify: bool) -> Message | None async

Отправляет сообщение в чат.

Source code in src\pymax\core.py
async def send_message(
    self, text: str, chat_id: int, notify: bool
) -> Message | None:
    """
    Отправляет сообщение в чат.
    """
    try:
        self.logger.info("Sending message to chat_id=%s notify=%s", chat_id, notify)
        payload = SendMessagePayload(
            chat_id=chat_id,
            message=SendMessagePayloadMessage(
                text=text,
                cid=int(time.time() * 1000),
                elements=[],
                attaches=[],
            ),
            notify=notify,
        ).model_dump(by_alias=True)

        data = await self._send_and_wait(
            opcode=Opcode.SEND_MESSAGE, payload=payload
        )
        if error := data.get("payload", {}).get("error"):
            self.logger.error("Send message error: %s", error)
        msg = (
            Message.from_dict(data["payload"]["message"])
            if data.get("payload")
            else None
        )
        self.logger.debug("send_message result: %r", msg)
        return msg
    except Exception:
        self.logger.exception("Send message failed")
        return None

edit_message(chat_id: int, message_id: int, text: str) -> Message | None async

Редактирует сообщение.

Source code in src\pymax\core.py
async def edit_message(
    self, chat_id: int, message_id: int, text: str
) -> Message | None:
    """
    Редактирует сообщение.
    """
    try:
        self.logger.info(
            "Editing message chat_id=%s message_id=%s", chat_id, message_id
        )
        payload = EditMessagePayload(
            chat_id=chat_id,
            message_id=message_id,
            text=text,
            elements=[],
            attaches=[],
        ).model_dump(by_alias=True)
        data = await self._send_and_wait(
            opcode=Opcode.EDIT_MESSAGE, payload=payload
        )
        if error := data.get("payload", {}).get("error"):
            self.logger.error("Edit message error: %s", error)
        msg = (
            Message.from_dict(data["payload"]["message"])
            if data.get("payload")
            else None
        )
        self.logger.debug("edit_message result: %r", msg)
        return msg
    except Exception:
        self.logger.exception("Edit message failed")
        return None

delete_message(chat_id: int, message_ids: list[int], for_me: bool) -> bool async

Удаляет сообщения.

Source code in src\pymax\core.py
async def delete_message(
    self, chat_id: int, message_ids: list[int], for_me: bool
) -> bool:
    """
    Удаляет сообщения.
    """
    try:
        self.logger.info(
            "Deleting messages chat_id=%s ids=%s for_me=%s",
            chat_id,
            message_ids,
            for_me,
        )

        payload = DeleteMessagePayload(
            chat_id=chat_id, message_ids=message_ids, for_me=for_me
        ).model_dump(by_alias=True)

        data = await self._send_and_wait(
            opcode=Opcode.DELETE_MESSAGE, payload=payload
        )
        if error := data.get("payload", {}).get("error"):
            self.logger.error("Delete message error: %s", error)
            return False
        self.logger.debug("delete_message success")
        return True
    except Exception:
        self.logger.exception("Delete message failed")
        return False

get_cached_user(user_id: int) -> User | None

Получает юзера из кеша по его ID

Parameters:

Name Type Description Default
user_id int

ID пользователя.

required

Returns:

Type Description
User | None

User | None: Объект User или None при ошибке.

Source code in src\pymax\core.py
def get_cached_user(self, user_id: int) -> User | None:
    """
    Получает юзера из кеша по его ID

    Args:
        user_id (int): ID пользователя.

    Returns:
        User | None: Объект User или None при ошибке.
    """
    user = self._users.get(user_id)
    self.logger.debug("get_cached_user id=%s hit=%s", user_id, bool(user))
    return user

get_users(user_ids: list[int]) -> list[User] async

Получает информацию о пользователях по их ID (с кешем).

Source code in src\pymax\core.py
async def get_users(self, user_ids: list[int]) -> list[User]:
    """
    Получает информацию о пользователях по их ID (с кешем).
    """
    self.logger.debug("get_users ids=%s", user_ids)
    cached = {uid: self._users[uid] for uid in user_ids if uid in self._users}
    missing_ids = [uid for uid in user_ids if uid not in self._users]

    if missing_ids:
        self.logger.debug("Fetching missing users: %s", missing_ids)
        fetched_users = await self.fetch_users(missing_ids)
        if fetched_users:
            for user in fetched_users:
                self._users[user.id] = user
                cached[user.id] = user

    ordered = [cached[uid] for uid in user_ids if uid in cached]
    self.logger.debug("get_users result_count=%d", len(ordered))
    return ordered

get_user(user_id: int) -> User | None async

Получает информацию о пользователе по его ID (с кешем).

Source code in src\pymax\core.py
async def get_user(self, user_id: int) -> User | None:
    """
    Получает информацию о пользователе по его ID (с кешем).
    """
    self.logger.debug("get_user id=%s", user_id)
    if user_id in self._users:
        return self._users[user_id]

    users = await self.fetch_users([user_id])
    if users:
        self._users[user_id] = users[0]
        return users[0]
    return None

fetch_users(user_ids: list[int]) -> None | list[User] async

Получает информацию о пользователях по их ID.

Source code in src\pymax\core.py
async def fetch_users(self, user_ids: list[int]) -> None | list[User]:
    """
    Получает информацию о пользователях по их ID.
    """
    try:
        self.logger.info("Fetching users count=%d", len(user_ids))

        payload = FetchContactsPayload(contact_ids=user_ids).model_dump(
            by_alias=True
        )

        data = await self._send_and_wait(
            opcode=Opcode.GET_CONTACTS_INFO, payload=payload
        )
        if error := data.get("payload", {}).get("error"):
            self.logger.error("Fetch users error: %s", error)
            return None

        users = [User.from_dict(u) for u in data["payload"].get("contacts", [])]
        for user in users:
            self._users[user.id] = user

        self.logger.debug("Fetched users: %d", len(users))
        return users
    except Exception:
        self.logger.exception("Fetch users failed")
        return []

fetch_history(chat_id: int, from_time: int | None = None, forward: int = 0, backward: int = 200) -> list[Message] | None async

Получает историю сообщений чата.

Source code in src\pymax\core.py
async def fetch_history(
    self,
    chat_id: int,
    from_time: int | None = None,
    forward: int = 0,
    backward: int = 200,
) -> list[Message] | None:
    """
    Получает историю сообщений чата.
    """
    if from_time is None:
        from_time = int(time.time() * 1000)

    try:
        self.logger.info(
            "Fetching history chat_id=%s from=%s forward=%s backward=%s",
            chat_id,
            from_time,
            forward,
            backward,
        )

        payload = FetchHistoryPayload(
            chat_id=chat_id,
            from_time=from_time,  # pyright: ignore[reportCallIssue] FIXME: Pydantic Field alias
            forward=forward,
            backward=backward,
        ).model_dump(by_alias=True)

        self.logger.debug("Payload dict keys: %s", list(payload.keys()))

        data = await self._send_and_wait(
            opcode=Opcode.FETCH_HISTORY, payload=payload, timeout=10
        )

        if error := data.get("payload", {}).get("error"):
            self.logger.error("Fetch history error: %s", error)
            return None

        messages = [
            Message.from_dict(msg) for msg in data["payload"].get("messages", [])
        ]
        self.logger.debug("History fetched: %d messages", len(messages))
        return messages
    except Exception:
        self.logger.exception("Fetch history failed")
        return None

start() -> None async

Запускает клиент, подключается к WebSocket, авторизует пользователя (если нужно) и запускает фоновый цикл.

Source code in src\pymax\core.py
async def start(self) -> None:
    """
    Запускает клиент, подключается к WebSocket, авторизует
    пользователя (если нужно) и запускает фоновый цикл.
    """
    try:
        self.logger.info("Client starting")
        await self._connect(self.user_agent)
        if self._token is None:
            await self._login()
        else:
            await self._sync()

        if self._on_start_handler:
            self.logger.debug("Calling on_start handler")
            result = self._on_start_handler()
            if asyncio.iscoroutine(result):
                await result

        if self._ws:
            ping_task = asyncio.create_task(self._send_interactive_ping())
            self._background_tasks.add(ping_task)
            ping_task.add_done_callback(
                lambda t: self._background_tasks.discard(t)
                or self._log_task_exception(t)
            )

            try:
                await self._ws.wait_closed()
            except asyncio.CancelledError:
                self.logger.debug("wait_closed cancelled")
    except Exception:
        self.logger.exception("Client start failed")

Типы данных

Message

pymax.Message(sender: int | None, elements: list[Element] | None, reaction_info: dict[str, Any] | None, options: int | None, id: int, time: int, text: str, status: MessageStatus | str | None, type: MessageType | str, attaches: list[Any])

Source code in src\pymax\types.py
def __init__(
    self,
    sender: int | None,
    elements: list[Element] | None,
    reaction_info: dict[str, Any] | None,
    options: int | None,
    id: int,
    time: int,
    text: str,
    status: MessageStatus | str | None,
    type: MessageType | str,
    attaches: list[Any],
) -> None:
    self.sender = sender
    self.elements = elements
    self.options = options
    self.id = id
    self.time = time
    self.text = text
    self.type = type
    self.attaches = attaches
    self.status = status
    self.reactionInfo = reaction_info

User

pymax.User(account_status: int, update_time: int, id: int, names: list[Names], options: list[str] | None = None, base_url: str | None = None, base_raw_url: str | None = None, photo_id: int | None = None, description: str | None = None, gender: int | None = None, link: str | None = None, web_app: str | None = None, menu_button: dict[str, Any] | None = None)

Source code in src\pymax\types.py
def __init__(
    self,
    account_status: int,
    update_time: int,
    id: int,
    names: list[Names],
    options: list[str] | None = None,
    base_url: str | None = None,
    base_raw_url: str | None = None,
    photo_id: int | None = None,
    description: str | None = None,
    gender: int | None = None,
    link: str | None = None,
    web_app: str | None = None,
    menu_button: dict[str, Any] | None = None,
) -> None:
    self.account_status = account_status
    self.update_time = update_time
    self.id = id
    self.names = names
    self.options = options or []
    self.base_url = base_url
    self.base_raw_url = base_raw_url
    self.photo_id = photo_id
    self.description = description
    self.gender = gender
    self.link = link
    self.web_app = web_app
    self.menu_button = menu_button

Chat

pymax.Chat(participants_count: int, access: AccessType | str, invited_by: int | None, link: str | None, chat_type: ChatType | str, title: str | None, last_fire_delayed_error_time: int, last_delayed_update_time: int, options: dict[str, bool], base_raw_icon_url: str | None, base_icon_url: str | None, description: str | None, modified: int, id_: int, admin_participants: dict[int, dict[Any, Any]], participants: dict[int, int], owner: int, join_time: int, created: int, last_message: Message | None, prev_message_id: str | None, last_event_time: int, messages_count: int, admins: list[int], restrictions: int | None, status: str, cid: int)

Source code in src\pymax\types.py
def __init__(
    self,
    participants_count: int,
    access: AccessType | str,
    invited_by: int | None,
    link: str | None,
    chat_type: ChatType | str,
    title: str | None,
    last_fire_delayed_error_time: int,
    last_delayed_update_time: int,
    options: dict[str, bool],
    base_raw_icon_url: str | None,
    base_icon_url: str | None,
    description: str | None,
    modified: int,
    id_: int,
    admin_participants: dict[int, dict[Any, Any]],
    participants: dict[int, int],
    owner: int,
    join_time: int,
    created: int,
    last_message: Message | None,
    prev_message_id: str | None,
    last_event_time: int,
    messages_count: int,
    admins: list[int],
    restrictions: int | None,
    status: str,
    cid: int,
) -> None:
    self.participants_count = participants_count
    self.access = access
    self.invited_by = invited_by
    self.link = link
    self.type = chat_type
    self.title = title
    self.last_fire_delayed_error_time = last_fire_delayed_error_time
    self.last_delayed_update_time = last_delayed_update_time
    self.options = options
    self.base_raw_icon_url = base_raw_icon_url
    self.base_icon_url = base_icon_url
    self.description = description
    self.modified = modified
    self.id = id_
    self.admin_participants = admin_participants
    self.participants = participants
    self.owner = owner
    self.join_time = join_time
    self.created = created
    self.last_message = last_message
    self.prev_message_id = prev_message_id
    self.last_event_time = last_event_time
    self.messages_count = messages_count
    self.admins = admins
    self.restrictions = restrictions
    self.status = status
    self.cid = cid

Dialog

pymax.Dialog(cid: int | None, owner: int, has_bots: bool | None, join_time: int, created: int, last_message: Message | None, type: ChatType | str, last_fire_delayed_error_time: int, last_delayed_update_time: int, prev_message_id: str | None, options: dict[str, bool], modified: int, last_event_time: int, id: int, status: str, participants: dict[str, int])

Source code in src\pymax\types.py
def __init__(
    self,
    cid: int | None,
    owner: int,
    has_bots: bool | None,
    join_time: int,
    created: int,
    last_message: Message | None,
    type: ChatType | str,
    last_fire_delayed_error_time: int,
    last_delayed_update_time: int,
    prev_message_id: str | None,
    options: dict[str, bool],
    modified: int,
    last_event_time: int,
    id: int,
    status: str,
    participants: dict[str, int],
) -> None:
    self.cid = cid
    self.owner = owner
    self.has_bots = has_bots
    self.join_time = join_time
    self.created = created
    self.last_message = last_message
    self.type = type
    self.last_fire_delayed_error_time = last_fire_delayed_error_time
    self.last_delayed_update_time = last_delayed_update_time
    self.prev_message_id = prev_message_id
    self.options = options
    self.modified = modified
    self.last_event_time = last_event_time
    self.id = id
    self.status = status
    self.participants = participants

Channel

pymax.Channel(participants_count: int, access: AccessType | str, invited_by: int | None, link: str | None, chat_type: ChatType | str, title: str | None, last_fire_delayed_error_time: int, last_delayed_update_time: int, options: dict[str, bool], base_raw_icon_url: str | None, base_icon_url: str | None, description: str | None, modified: int, id_: int, admin_participants: dict[int, dict[Any, Any]], participants: dict[int, int], owner: int, join_time: int, created: int, last_message: Message | None, prev_message_id: str | None, last_event_time: int, messages_count: int, admins: list[int], restrictions: int | None, status: str, cid: int)

Bases: Chat

Source code in src\pymax\types.py
def __init__(
    self,
    participants_count: int,
    access: AccessType | str,
    invited_by: int | None,
    link: str | None,
    chat_type: ChatType | str,
    title: str | None,
    last_fire_delayed_error_time: int,
    last_delayed_update_time: int,
    options: dict[str, bool],
    base_raw_icon_url: str | None,
    base_icon_url: str | None,
    description: str | None,
    modified: int,
    id_: int,
    admin_participants: dict[int, dict[Any, Any]],
    participants: dict[int, int],
    owner: int,
    join_time: int,
    created: int,
    last_message: Message | None,
    prev_message_id: str | None,
    last_event_time: int,
    messages_count: int,
    admins: list[int],
    restrictions: int | None,
    status: str,
    cid: int,
) -> None:
    self.participants_count = participants_count
    self.access = access
    self.invited_by = invited_by
    self.link = link
    self.type = chat_type
    self.title = title
    self.last_fire_delayed_error_time = last_fire_delayed_error_time
    self.last_delayed_update_time = last_delayed_update_time
    self.options = options
    self.base_raw_icon_url = base_raw_icon_url
    self.base_icon_url = base_icon_url
    self.description = description
    self.modified = modified
    self.id = id_
    self.admin_participants = admin_participants
    self.participants = participants
    self.owner = owner
    self.join_time = join_time
    self.created = created
    self.last_message = last_message
    self.prev_message_id = prev_message_id
    self.last_event_time = last_event_time
    self.messages_count = messages_count
    self.admins = admins
    self.restrictions = restrictions
    self.status = status
    self.cid = cid

Element

pymax.Element(type: ElementType | str, length: int, from_: int | None = None)

Source code in src\pymax\types.py
def __init__(
    self, type: ElementType | str, length: int, from_: int | None = None
) -> None:
    self.type = type
    self.length = length
    self.from_ = from_

Исключения

InvalidPhoneError

pymax.InvalidPhoneError(phone: str)

Bases: Exception

Исключение, вызываемое при неверном формате номера телефона.

Parameters:

Name Type Description Default
phone str

Некорректный номер телефона.

required
Source code in src\pymax\exceptions.py
def __init__(self, phone: str) -> None:
    super().__init__(f"Invalid phone number format: {phone}")

WebSocketNotConnectedError

pymax.WebSocketNotConnectedError()

Bases: Exception

Исключение, вызываемое при попытке обращения к WebSocket, если соединение не установлено.

Source code in src\pymax\exceptions.py
def __init__(self) -> None:
    super().__init__("WebSocket is not connected")

Константы

MessageType

pymax.MessageType

Bases: str, Enum

MessageStatus

pymax.MessageStatus

Bases: str, Enum

ChatType

pymax.ChatType

Bases: str, Enum

AuthType

pymax.AuthType

Bases: str, Enum

AccessType

pymax.AccessType

Bases: str, Enum

DeviceType

pymax.DeviceType

Bases: str, Enum

ElementType

pymax.ElementType

Bases: str, Enum

Opcode

pymax.Opcode

Bases: IntEnum