From fa0ed34adc1a4dade4bb01fa7fc052cf823d6180 Mon Sep 17 00:00:00 2001 From: zavolo Date: Sun, 10 May 2026 22:17:18 +0300 Subject: [PATCH] =?UTF-8?q?MAX:=20=D0=B8=D1=81=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=82=D0=B0=D0=BA=D0=B8=20=D0=B7=D0=B0=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=B0=D0=BB=D0=B0=20=E2=80=94=20cid/link/rea?= =?UTF-8?q?ctionInfo=20=D0=BE=D0=B1=D1=8F=D0=B7=D0=B0=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=20=D0=B2=20=D1=81=D1=85=D0=B5=D0=BC=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Десктопный MAX подключается через TCP (mobile-протокол) и парсит msgpack по фиксированной схеме. Если в сообщении выпадает любое из полей — клиент молча обрывает соединение. После 87cfc19 как раз такие условные `if elements: ...` / `if link: ...` (а link и reaction_info там всегда были `{}`, то есть falsy) вырезали поля из ответа CHAT_HISTORY и MSG_SEND, чем и сломали историю. - src/common/tools.py: новый build_message_dict() — единая сборка тела сообщения, где все поля (id, cid, time, type, sender, text, attaches, elements, reactionInfo, link) присутствуют ВСЕГДА. get_last_message переписан через него. - src/oneme/processors/history.py: chat_history использует build_message_dict вместо ручной логики с условными if-ками. - src/oneme/processors/messages.py: msg_send.bodyMessage теперь отдает cid / reactionInfo / link даже пустыми и приводит id к int для mobile, str для web. Цепная польза: auth.py LOGIN bootstrap (через generate_chats → get_last_message) и search.py тоже теперь шлют корректную схему. --- src/common/tools.py | 62 +++++++++++++++++--------------- src/oneme/processors/history.py | 60 ++----------------------------- src/oneme/processors/messages.py | 15 +++++--- 3 files changed, 46 insertions(+), 91 deletions(-) diff --git a/src/common/tools.py b/src/common/tools.py index af3e9d3..1b322f3 100644 --- a/src/common/tools.py +++ b/src/common/tools.py @@ -10,6 +10,39 @@ class Tools: def __init__(self): pass + def build_message_dict(self, row, protocol_type="mobile"): + """Унифицированная сборка тела сообщения для отправки клиенту. + + Десктоп MAX (TCP, protocol_type='mobile') и официальный + api.oneme.ru ожидают, что в сообщении будут ВСЕГДА присутствовать + поля cid / elements / link / reactionInfo, даже если они пустые. + Любое отсутствие поля приводит к тому, что клиент бросает соединение + при разборе msgpack-схемы (классическая регрессия из коммита 87cfc19). + """ + try: + attaches = json.loads(row.get("attaches") or "[]") + except (TypeError, ValueError): + attaches = [] + try: + elements = json.loads(row.get("elements") or "[]") + except (TypeError, ValueError): + elements = [] + + message = { + "id": row.get("id") if protocol_type == "mobile" else str(row.get("id")), + "cid": int(row.get("cid") or 0), + "time": int(row.get("time")), + "type": row.get("type"), + "sender": row.get("sender"), + "text": row.get("text") or "", + "attaches": attaches if isinstance(attaches, list) else [], + "elements": elements if isinstance(elements, list) else [], + "reactionInfo": {}, + "link": {}, + } + + return message + def generate_profile( self, id=1, @@ -412,35 +445,8 @@ class Tools: if not row: return None, None - message = { - "sender": row.get("sender"), - "id": row.get("id") - if protocol_type == "mobile" - else str(row.get("id")), - "time": int(row.get("time")), - "text": row.get("text"), - "type": row.get("type"), - "attaches": json.loads(row.get("attaches")) - } - - elements = json.loads(row.get("elements")) - link = {} - reaction_info = {} - - if elements: - message["elements"] = elements - - if link: - message["link"] = link - - if reaction_info: - message["reactionInfo"] = reaction_info - - if protocol_type == "web": - message["cid"] = int(row.get("cid")) - # Возвращаем - return message, int(row.get("time")) + return self.build_message_dict(row, protocol_type), int(row.get("time")) async def get_previous_message_id(self, chatId, db_pool, protocol_type="mobile"): """Получение ID предыдущего сообщения (второго с конца) в чате.""" diff --git a/src/oneme/processors/history.py b/src/oneme/processors/history.py index 8b5db5b..a5e8aaf 100644 --- a/src/oneme/processors/history.py +++ b/src/oneme/processors/history.py @@ -59,35 +59,7 @@ class HistoryProcessors(BaseProcessor): result = await cursor.fetchall() for row in result: - # TODO: Сборку тела сообщения нужно вынести в отдельную функцию - message = { - "sender": row.get("sender"), - "id": row.get("id") - if self.type == "mobile" - else str(row.get("id")), - "time": int(row.get("time")), - "text": row.get("text"), - "type": row.get("type"), - "attaches": json.loads(row.get("attaches")) - } - - elements = json.loads(row.get("elements")) - link = {} - reaction_info = {} - - if elements: - message["elements"] = elements - - if link: - message["link"] = link - - if reaction_info: - message["reactionInfo"] = reaction_info - - if self.type == "web": - message["cid"] = int(row.get("cid")) - - messages.append(message) + messages.append(self.tools.build_message_dict(row, self.type)) if forward > 0: await cursor.execute( "SELECT * FROM messages WHERE chat_id = %s AND time > %s ORDER BY time ASC LIMIT %s", @@ -97,35 +69,7 @@ class HistoryProcessors(BaseProcessor): result = await cursor.fetchall() for row in result: - # TODO: Сборку тела сообщения нужно вынести в отдельную функцию - message = { - "sender": row.get("sender"), - "id": row.get("id") - if self.type == "mobile" - else str(row.get("id")), - "time": int(row.get("time")), - "text": row.get("text"), - "type": row.get("type"), - "attaches": json.loads(row.get("attaches")) - } - - elements = json.loads(row.get("elements")) - link = {} - reaction_info = {} - - if elements: - message["elements"] = elements - - if link: - message["link"] = link - - if reaction_info: - message["reactionInfo"] = reaction_info - - if self.type == "web": - message["cid"] = int(row.get("cid")) - - messages.append(message) + messages.append(self.tools.build_message_dict(row, self.type)) # Сортируем сообщения по времени messages.sort(key=lambda x: x["time"]) diff --git a/src/oneme/processors/messages.py b/src/oneme/processors/messages.py index ab7a4cd..b007eab 100644 --- a/src/oneme/processors/messages.py +++ b/src/oneme/processors/messages.py @@ -125,16 +125,21 @@ class MessagesProcessors(BaseProcessor): db_pool=self.db_pool ) - # Готовое тело сообщения + # Готовое тело сообщения. Поля cid / elements / reactionInfo / link + # должны присутствовать ВСЕГДА (даже пустые) — десктопный MAX + # ожидает фиксированную msgpack-схему и обрывает соединение + # при отсутствии любого из них (см. регрессию из 87cfc19). bodyMessage = { - "id": messageId, + "id": messageId if self.type == "mobile" else str(messageId), + "cid": int(cid or 0), "time": messageTime, "type": "USER", "sender": senderId, - "cid": cid, "text": text, - "attaches": attaches, - "elements": elements + "attaches": attaches if isinstance(attaches, list) else [], + "elements": elements if isinstance(elements, list) else [], + "reactionInfo": {}, + "link": {}, } # Отправляем событие всем участникам чата