mirror of
https://github.com/openmax-server/server.git
synced 2026-05-22 19:41:41 +03:00
MAX & TT: теперь полноценный чёрный список
This commit is contained in:
@@ -15,6 +15,7 @@ class Static:
|
||||
RATE_LIMITED = "rate_limited"
|
||||
CONTACT_NOT_FOUND = "contact_not_found"
|
||||
CONTACT_ALREADY_ADDED = "contact_already_added"
|
||||
CONTACT_BLOCKED = "contact_blocked"
|
||||
|
||||
class ChatTypes:
|
||||
DIALOG = "DIALOG"
|
||||
@@ -95,6 +96,12 @@ class Static:
|
||||
"message": "Contact already added",
|
||||
"title": "Контакт уже добавлен"
|
||||
},
|
||||
"contact_blocked": {
|
||||
"localizedMessage": "Вы не можете написать этому пользователю",
|
||||
"error": "contact.blocked",
|
||||
"message": "Contact is blocked",
|
||||
"title": "Вы не можете написать этому пользователю"
|
||||
},
|
||||
}
|
||||
|
||||
### Сообщения бота
|
||||
|
||||
@@ -11,14 +11,7 @@ class Tools:
|
||||
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):
|
||||
@@ -28,29 +21,18 @@ class Tools:
|
||||
except (TypeError, ValueError):
|
||||
elements = []
|
||||
|
||||
# Парсер MAX 26.15.3 (defpackage.u6h.Q) ждёт в сообщении следующие
|
||||
# поля. Отсутствие любого ломает разбор msgpack-схемы, и клиент
|
||||
# тихо роняет всю историю чата:
|
||||
# id, cid, chatId, time, type, sender, text, attaches, elements,
|
||||
# link, reactionInfo, updateTime, status, options
|
||||
# Список вытащен дизассемблированием Q() через dexdump.
|
||||
# type — int-enum для разновидности сообщения (0 = обычное text);
|
||||
# status — int-enum (1 = ACTIVE/доставлено, 0 часто означает REMOVED).
|
||||
message = {
|
||||
"id": row.get("id") if protocol_type == "mobile" else str(row.get("id")),
|
||||
"cid": int(row.get("cid") or 0),
|
||||
"chatId": int(row.get("chat_id") or 0),
|
||||
"time": int(row.get("time")),
|
||||
"type": row.get("type") or "USER", # ENUM-строка: USER/CHANNEL/CHANNEL_ADMIN/GROUP
|
||||
"type": row.get("type") or "USER",
|
||||
"sender": row.get("sender"),
|
||||
"text": row.get("text") or "",
|
||||
"attaches": attaches if isinstance(attaches, list) else [],
|
||||
"elements": elements if isinstance(elements, list) else [],
|
||||
"attaches": attaches,
|
||||
"elements": elements,
|
||||
"reactionInfo": {},
|
||||
"link": {},
|
||||
"updateTime": int(row.get("update_time") or row.get("time") or 0),
|
||||
"status": int(row.get("status") or 1), # 1 = ACTIVE
|
||||
"options": int(row.get("options") or 0),
|
||||
"link": {}
|
||||
}
|
||||
|
||||
return message
|
||||
@@ -441,44 +423,6 @@ class Tools:
|
||||
# Возвращаем айдишки
|
||||
return int(message_id), int(last_message_id), message_time
|
||||
|
||||
async def collect_bootstrap_history(
|
||||
self, chatIds, db_pool, senderId, protocol_type="mobile", limit=50, include_favourites=True
|
||||
):
|
||||
"""Собирает карту {chatId: [messages...]} для bootstrap-pre-fetch в LOGIN.
|
||||
|
||||
Десктопный MAX в ответе LOGIN ждёт поле `messages` как карту чат→история.
|
||||
Если карта пустая — клиент полагает, что у него уже есть локальная
|
||||
история и НЕ запрашивает CHAT_HISTORY (49). В итоге в окне чата
|
||||
видно только lastMessage из chats[].
|
||||
"""
|
||||
result = {}
|
||||
|
||||
async def _fetch(chat_db_id, key_for_client):
|
||||
async with db_pool.acquire() as conn:
|
||||
async with conn.cursor() as cursor:
|
||||
await cursor.execute(
|
||||
"SELECT * FROM `messages` WHERE chat_id = %s ORDER BY time DESC LIMIT %s",
|
||||
(chat_db_id, limit),
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
|
||||
if not rows:
|
||||
return
|
||||
|
||||
messages = [self.build_message_dict(row, protocol_type) for row in rows]
|
||||
messages.sort(key=lambda m: m["time"])
|
||||
result[key_for_client] = messages
|
||||
|
||||
for chatId in chatIds:
|
||||
await _fetch(chatId, chatId)
|
||||
|
||||
if include_favourites:
|
||||
# Избранное: в БД хранится как chat_id = -senderId,
|
||||
# но клиенту отдаётся под id = senderId ^ senderId (= 0)
|
||||
await _fetch(-senderId, senderId ^ senderId)
|
||||
|
||||
return result
|
||||
|
||||
async def get_last_message(self, chatId, db_pool, protocol_type="mobile"):
|
||||
"""Получение последнего сообщения в чате"""
|
||||
async with db_pool.acquire() as db_connection:
|
||||
@@ -645,3 +589,23 @@ class Tools:
|
||||
await cursor.execute("SELECT id FROM users WHERE id = %s", (user_id,))
|
||||
if not await cursor.fetchone():
|
||||
return user_id
|
||||
|
||||
async def contact_is_blocked(self, owner_id, contact_id, db_pool):
|
||||
"""
|
||||
По изначальной задумке, данная функция должна проверять, заблокирован ли контакт
|
||||
На сервере долгое время не был доделан черный список, хотя управление им было реализовано
|
||||
(на деле, это я поленился)
|
||||
|
||||
Вернёт вам true, если контакт заблокирован, иначе false
|
||||
"""
|
||||
# Проверяем наличие контакта
|
||||
async with db_pool.acquire() as conn:
|
||||
async with conn.cursor() as cursor:
|
||||
await cursor.execute("SELECT * FROM contacts WHERE owner_id = %s AND contact_id = %s AND is_blocked = %s", (owner_id, contact_id, True))
|
||||
row = await cursor.fetchone()
|
||||
|
||||
# Есди контакт существует и заблокирован, возвращаем true,
|
||||
if row:
|
||||
return True
|
||||
else: # в ином случае false
|
||||
return False
|
||||
@@ -2,7 +2,7 @@ from .assets import AssetsProcessors
|
||||
from .auth import AuthProcessors
|
||||
from .calls import CallsProcessors
|
||||
from .chats import ChatsProcessors
|
||||
from .complains import ComplainsProcessors
|
||||
from .complaints import ComplaintsProcessors
|
||||
from .contacts import ContactsProcessors
|
||||
from .folders import FoldersProcessors
|
||||
from .history import HistoryProcessors
|
||||
@@ -16,7 +16,7 @@ class Processors(
|
||||
AuthProcessors,
|
||||
CallsProcessors,
|
||||
ChatsProcessors,
|
||||
ComplainsProcessors,
|
||||
ComplaintsProcessors,
|
||||
ContactsProcessors,
|
||||
FoldersProcessors,
|
||||
HistoryProcessors,
|
||||
|
||||
@@ -3,7 +3,7 @@ import time
|
||||
from classes.baseprocessor import BaseProcessor
|
||||
from oneme.models import ComplainReasonsGetPayloadModel
|
||||
|
||||
class ComplainsProcessors(BaseProcessor):
|
||||
class ComplaintsProcessors(BaseProcessor):
|
||||
async def complain_reasons_get(self, payload, seq, writer):
|
||||
"""Обработчик получения причин жалоб"""
|
||||
# Валидируем данные пакета
|
||||
@@ -23,8 +23,6 @@ class HistoryProcessors(BaseProcessor):
|
||||
getMessages = payload.get("getMessages", True)
|
||||
getChat = payload.get("getChat", False)
|
||||
messages = []
|
||||
backward_count = 0
|
||||
forward_count = 0
|
||||
|
||||
# Если пользователь хочет получить историю из избранного,
|
||||
# то выставляем в качестве ID чата отрицательный ID отправителя
|
||||
@@ -80,32 +78,10 @@ class HistoryProcessors(BaseProcessor):
|
||||
# Сортируем сообщения по времени
|
||||
messages.sort(key=lambda x: x["time"])
|
||||
|
||||
# КОСТЫЛЬ: клиент MAX в fz2.b() фильтрует сообщения по условию
|
||||
# `message.time >= chat.createTime`. Если у пользователя чат был
|
||||
# создан недавно, а наши сообщения в БД старые — все они отбрасываются
|
||||
# (см. реверс defpackage.fz2.java:89). Сдвигаем time всех сообщений
|
||||
# в «сейчас + N мс» — гарантированно > chat.createTime, и шаг по 1мс
|
||||
# сохраняет порядок сортировки.
|
||||
if messages:
|
||||
now_ms = int(time.time() * 1000)
|
||||
for i, m in enumerate(messages):
|
||||
m["time"] = now_ms + i # на 1мс позже предыдущего
|
||||
m["updateTime"] = m["time"]
|
||||
|
||||
# Формируем ответ.
|
||||
# Реальный парсер ответа CHAT_HISTORY в MAX 26.15.x — это az2.j(),
|
||||
# который ждёт всего 3 поля:
|
||||
# chat — qs2-объект чата (опционально, если getChat=False)
|
||||
# messages — массив сообщений (jr4.a → u6h.Q для каждого)
|
||||
# messageIds — Set<Long> списка id сообщений в этом ответе
|
||||
# Поля forward/backward/pos/total — это парсер a23 для CHAT_MEDIA,
|
||||
# к chat_history они не имеют отношения.
|
||||
payload = {
|
||||
"messages": messages,
|
||||
"messageIds": [m["id"] for m in messages],
|
||||
"messages": messages
|
||||
}
|
||||
# chat-объект отдаём только если запрошен (getChat=True). Пустой
|
||||
# qs2-dict рискует свалить парсер qs2.e() — лучше вообще не слать.
|
||||
|
||||
if getChat:
|
||||
payload["chat"] = {}
|
||||
|
||||
|
||||
@@ -113,6 +113,14 @@ class MessagesProcessors(BaseProcessor):
|
||||
await self._send_error(seq, self.opcodes.MSG_SEND, self.error_types.CHAT_NOT_ACCESS, writer)
|
||||
return
|
||||
|
||||
# Проверяем блокировку собеседника
|
||||
if chat.get("type") == "DIALOG":
|
||||
contactid = [p for p in participants if p != int(senderId)][0]
|
||||
# Проверяем, заблокировал ли отправитель собеседника
|
||||
if await self.tools.contact_is_blocked(contactid, senderId, db_pool):
|
||||
await self._send_error(seq, self.opcodes.MSG_SEND, self.error_types.CONTACT_BLOCKED, writer)
|
||||
return
|
||||
|
||||
# Добавляем сообщение в историю
|
||||
messageId, lastMessageId, messageTime = await self.tools.insert_message(
|
||||
chatId=chatId,
|
||||
|
||||
@@ -105,6 +105,14 @@ class MessagesProcessors(BaseProcessor):
|
||||
await self._send_error(seq, self.opcodes.MSG_SEND, self.error_types.CHAT_NOT_ACCESS, writer)
|
||||
return
|
||||
|
||||
# Проверяем блокировку собеседника
|
||||
if chat.get("type") == "DIALOG":
|
||||
contactid = [p for p in participants if p != int(senderId)][0]
|
||||
# Проверяем, заблокировал ли отправитель собеседника
|
||||
if await self.tools.contact_is_blocked(contactid, senderId, db_pool):
|
||||
await self._send_error(seq, self.opcodes.MSG_SEND, self.error_types.CONTACT_BLOCKED, writer)
|
||||
return
|
||||
|
||||
# Добавляем сообщение в историю
|
||||
messageId, lastMessageId, messageTime = await self.tools.insert_message(
|
||||
chatId=chatId,
|
||||
|
||||
Reference in New Issue
Block a user