💬 Чаты

Список чатов, история сообщений, создание, настройки, участники.

property client.cached_chats

Список чатов из кэша LOGIN-ответа. Заполняется после успешного LOGIN, без дополнительных запросов. Подтверждено ✅

→ list[dict]

Структура объекта чата

КлючТипОписание
idintID чата
typestrDIALOG, GROUP, CHANNEL
statusstrACTIVE, BLOCKED
newMessagesintКоличество непрочитанных
lastMessagedictПоследнее сообщение чата
lastEventTimeintВремя последнего события (Unix ms)
participantsdictuid → timestamp (для диалогов)
ownerintUID владельца (для групп)
optionsdictФлаги: SERVICE_CHAT и др.
chats = client.cached_chats
for chat in chats:
    unread = chat.get('newMessages', 0)
    last   = chat.get('lastMessage', {}).get('text', '')
    print(f"[{chat['type']}] id={chat['id']} unread={unread}")
    if last:
        print(f"  └─ {last[:60]}")
async get_chats() Кэш LOGIN

Возвращает полный список чатов из кэша, полученного при входе (LOGIN, opcode 19). Никаких сетевых запросов не делает.

✅ Подтверждено — 7 чатов при тесте. Это основной способ получить список чатов.
→ list[dict] — снапшот чатов на момент входа.
async poll_chats(count=50) CHATS_LIST · opcode 53

Delta-опрос: запрашивает у сервера чаты, изменившиеся с момента последнего маркера. Автоматически мёрджит изменения в кэш и обновляет маркер для следующего вызова.

⚠️
Статус на март 2026: не подтверждено. В нескольких живых проверках метод возвращал пустой delta-список даже после нового исходящего сообщения и даже после создания нового чата в 15-секундном окне. Документацию ниже стоит читать как описание предполагаемого протокола, а не как подтверждённое рабочее поведение текущей Python-обёртки.

Как работает delta-sync

Протокол реализует delta-синхронизацию через поле marker:

  1. При логине LOGIN отдаёт полный снапшот чатов + chatMarker=N
  2. poll_chats() отправляет CHATS_LIST{marker: N, count: 50}
  3. Сервер возвращает только чаты с новой активностью начиная с маркера N
  4. В ответе приходит новый маркер — он сохраняется для следующего poll_chats()

Wire-параметры (opcode 53)

{
  "marker": 0,   # обязательно! без него ошибка proto.payload
  "count":  50
}
⚠️
Поле marker обязательно — его отсутствие вызывает ошибку proto.payload: Field requirement failed: marker.
Передача types или withLastMessage вызывает немедленный TCP-дисконнект (битые параметры сервера).
→ list[dict] — список изменившихся чатов (пустой, если нет новых).

Пример

# Получить все чаты (снапшот при входе)
chats = await client.get_chats()

# Получить обновления чатов с момента последнего опроса
delta = await client.poll_chats()
if delta:
    print(f"Обновилось {len(delta)} чатов")
async get_chat_history(chat_id, count=20, from_msg=None) CHAT_HISTORY · opcode 49

Загружает историю сообщений чата. Подтверждено ✅

ПараметрТипОписание
chat_idintID чата
countintКоличество сообщений (backward). По умолчанию 20.
from_msgintНеобязательно. Точка отчёта (Unix ms). По умолчанию — текущее время.
→ dict с ключом messages (list)

Wire-параметры запроса

{
  "chatId":      34540359,
  "from":        1774188368109,  # текущее время в ms ← обязательно!
  "backward":    20,
  "forward":     0,
  "getMessages": true,
  "getChat":     false,
  "itemType":    "REGULAR",  # или "DELAYED"
  "interactive": true
}
⚠️
Поле from обязательно. Без него сервер вернёт пустой ответ. Допустимые значения itemType: "REGULAR" и "DELAYED".

Структура сообщения в ответе

КлючТипОписание
idintID сообщения (64-бит)
timeintВремя отправки (Unix ms)
typestrUSER, SYSTEM, BOT
senderintUID отправителя
textstrТекст сообщения
attacheslistВложения (фото, файлы и т.д.)
elementslistФорматирование (BOLD, LINK и т.д.)

Пример

data = await client.get_chat_history(chat_id=34540359, count=10)
for msg in data.get("messages", []):
    text = msg.get("text", "")[:80]
    print(f"[{msg['sender']}]: {text}")
async get_chat_info(chat_id) CHAT_INFO · opcode 48

Возвращает полную информацию о чате. Статус текущей Python-обёртки: проблемный / не подтверждён.

⚠️
Живая проверка показала, что текущий вызов с параметром {"chatId": id} не совпадает с реальным ожидаемым wire-форматом сервера. Для DIALOG пришла ошибка {"error": "proto.payload", "message": "'chatIds' or 'link' must be present"}.
⚠️
Дополнительная проверка на CHANNEL с отрицательным id привела к TCP-дисконнекту. Поэтому метод пока нельзя считать рабочим ни для DIALOG, ни для групп/каналов, несмотря на более ранние предположения.
ℹ️
Практический вывод: проблема, вероятно, не в самом opcode 48, а в том, что Python-обёртка отправляет неправильную структуру параметров. До отдельной верификации безопаснее считать get_chat_info() экспериментальным методом.
→ dict
info = await client.get_chat_info(chat_id=34540359)
async create_chat(title, member_ids=None) CHAT_CREATE · opcode 63

Создаёт новый групповой чат. Статус текущей Python-обёртки: проблемный / не подтверждён.

⚠️
Живая проверка на реальной авторизованной сессии показала, что текущий вызов create_chat(title, member_ids=None) стабильно возвращает {"error": "proto.opcode", "message": "Unknown opcode"}. Это указывает на несоответствие между текущей Python-реализацией и фактическим серверным протоколом.
ПараметрТипОписание
titlestrНазвание группы
member_idslist[int]Необязательно. Список UID участников
→ dict
chat = await client.create_chat(
    title="Моя группа",
    member_ids=[27993802, 12277280]
)
async get_chat_members(chat_id, count=100) CHAT_MEMBERS · opcode 59

Список участников чата. Подтверждено в бою ✅

⚠️
Для личных диалогов метод не подходит. Для минусовых CHAT-чатов метод успешно отработал и у владельца, и у обычного участника. Для некоторых CHANNEL сервер может вернуть chat.denied / User is not admin, если у сессии недостаточно прав.
→ dict с ключом members
async join_chat(link) CHAT_JOIN · opcode 57

Присоединяется к публичному чату/каналу по invite-ссылке. Подтверждено в бою ✅

ℹ️
Живая проверка на TEST через реальную invite-ссылку вернула объект chat и control event joinByLink в последнем сообщении чата.
→ dict
await client.join_chat("https://max.ru/join/...")
async leave_chat(chat_id) CHAT_LEAVE · opcode 58

Покидает чат. Подтверждено в бою ✅

ℹ️
В живой проверке сервер вернул служебное сообщение с вложением {"_type": "CONTROL", "event": "leave"}.
→ dict

📡 Входящие уведомления (NOTIF)

КонстантаOpcodeСобытие
NOTIF_CHAT135Изменение чата (название, аватар, участники)
NOTIF_PRESENCE132Онлайн-статус собеседника
NOTIF_CONTACT_SORT139Изменение сортировки контактов
NOTIF_FOLDERS277Изменение папок
NOTIF_LOCATION147Новая геопозиция