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

API Reference

MaxClient

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

Bases: ApiMixin, WebSocketMixin

Основной клиент для работы с 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.me: Me | None = None
    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_handlers: list[
        tuple[Callable[[Message], Any], Filter | 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

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_

Names

pymax.Names(name: str, first_name: str, last_name: str | None, type: str)

Source code in src\pymax\types.py
def __init__(
    self, name: str, first_name: str, last_name: str | None, type: str
) -> None:
    self.name = name
    self.first_name = first_name
    self.last_name = last_name
    self.type = type

Me

pymax.Me(id: int, account_status: int, phone: str, names: list[Names], update_time: int, options: list[str] | None = None)

Source code in src\pymax\types.py
def __init__(
    self,
    id: int,
    account_status: int,
    phone: str,
    names: list[Names],
    update_time: int,
    options: list[str] | None = None,
) -> None:
    self.id = id
    self.account_status = account_status
    self.phone = phone
    self.update_time = update_time
    self.options = options
    self.names = names

Исключения

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

Интерфейсы

ClientProtocol

pymax.ClientProtocol(logger: Logger)

Bases: ABC

Source code in src\pymax\interfaces.py
def __init__(self, logger: Logger) -> None:
    super().__init__()
    self.logger = logger
    self._users: dict[int, User] = {}
    self.chats: list[Chat] = []
    self.phone: str = ""
    self._database: Database
    self._device_id: UUID
    self._on_message_handlers: list[
        tuple[Callable[[Message], Any], Filter | None]
    ] = []
    self.uri: str
    self.is_connected: bool = False
    self.phone: str
    self.chats: list[Chat] = []
    self.dialogs: list[Dialog] = []
    self.channels: list[Channel] = []
    self.me: Me | None = None
    self._users: dict[int, User] = {}
    self._work_dir: str
    self._database_path: Path
    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.user_agent = Constants.DEFAULT_USER_AGENT.value
    self._on_message_handlers: list[
        tuple[Callable[[Message], Any], Filter | None]
    ] = []
    self._on_start_handler: Callable[[], Any | Awaitable[Any]] | None = None
    self._background_tasks: set[asyncio.Task[Any]] = set()

Фильтры

Filter

pymax.Filter(user_id: int | None = None, text: list[str] | None = None, status: MessageStatus | str | None = None, type: MessageType | str | None = None, text_contains: str | None = None, reaction_info: bool | None = None)

Source code in src\pymax\filters.py
def __init__(
    self,
    user_id: int | None = None,
    text: list[str] | None = None,
    status: MessageStatus | str | None = None,
    type: MessageType | str | None = None,
    text_contains: str | None = None,
    reaction_info: bool | None = None,
) -> None:
    self.user_id = user_id
    self.text = text
    self.status = status
    self.type = type
    self.reaction_info = reaction_info
    self.text_contains = text_contains

Утилиты

pack_packet

pymax.pack_packet(ver: int, cmd: int, seq: int, opcode: int, payload: dict[str, Any]) -> bytes

Source code in src\pymax\utils.py
def pack_packet(
    ver: int, cmd: int, seq: int, opcode: int, payload: dict[str, Any]
) -> bytes:
    ver_b = ver.to_bytes(1, "big")
    cmd_b = cmd.to_bytes(2, "big")
    seq_b = seq.to_bytes(1, "big")
    opcode_b = opcode.to_bytes(2, "big")
    payload_bytes = msgpack.packb(payload)
    if payload_bytes is None:
        payload_bytes = b""
    payload_len_b = len(payload_bytes).to_bytes(4, "big")
    return ver_b + cmd_b + seq_b + opcode_b + payload_len_b + payload_bytes

unpack_packet

pymax.unpack_packet(data: bytes) -> None | dict[str, Any]

Source code in src\pymax\utils.py
def unpack_packet(data: bytes) -> None | dict[str, Any]:
    ver = int.from_bytes(data[0:1], "big")
    cmd = int.from_bytes(data[1:3], "big")
    seq = int.from_bytes(data[3:4], "big")
    opcode = int.from_bytes(data[4:6], "big")
    packed_len = int.from_bytes(data[6:10], "big", signed=False)
    comp_flag = packed_len >> 24
    payload_length = packed_len & 0xFFFFFF
    payload_bytes = data[10 : 10 + payload_length]
    if comp_flag != 0:
        compressed_data = payload_bytes
        try:
            payload_bytes = lz4.block.decompress(compressed_data, uncompressed_size=255)
        except lz4.block.LZ4BlockError:
            return None
    payload = msgpack.unpackb(payload_bytes, raw=False)
    return {"ver": ver, "cmd": cmd, "seq": seq, "opcode": opcode, "payload": payload}