Вынес список участников чата в отдельную таблицу

This commit is contained in:
Alexey Polyakov 2026-03-21 15:05:38 +03:00
parent 2cf18b878a
commit ef512b060f
7 changed files with 103 additions and 39 deletions

View File

@ -13,6 +13,6 @@ class SQLQueries:
INSERT_USER_DATA = """ INSERT_USER_DATA = """
INSERT INTO user_data INSERT INTO user_data
(phone, chats, contacts, folders, user_config, chat_config) (phone, contacts, folders, user_config, chat_config)
VALUES (%s, %s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %s)
""" """

View File

@ -136,7 +136,7 @@ class Tools:
) )
# Формируем список участников с временем последней активности # Формируем список участников с временем последней активности
participant_ids = json.loads(row.get("participants")) participant_ids = await self.get_chat_participants(chatId, db_pool)
participants = await self.get_participant_last_activity( participants = await self.get_participant_last_activity(
chatId, participant_ids, db_pool chatId, participant_ids, db_pool
) )
@ -171,6 +171,7 @@ class Tools:
# Получаем ID предыдущего сообщения для избранного (чат ID = senderId) # Получаем ID предыдущего сообщения для избранного (чат ID = senderId)
prevMessageId = await self.get_previous_message_id(senderId, db_pool) prevMessageId = await self.get_previous_message_id(senderId, db_pool)
# Хардкодим в лист чатов избранное # Хардкодим в лист чатов избранное
chats.append( chats.append(
self.generate_chat( self.generate_chat(
@ -195,17 +196,17 @@ class Tools:
row = await cursor.fetchone() or {} row = await cursor.fetchone() or {}
last_message_id = row.get("id") or 0 # последнее id сообщения в чате last_message_id = row.get("id") or 0 # последнее id сообщения в чате
message_id = self.generate_id()
message_time = int(time.time() * 1000) # время отправки сообщения
# Вносим новое сообщение в таблицу # Вносим новое сообщение в таблицу
await cursor.execute( await cursor.execute(
"INSERT INTO `messages` (chat_id, sender, time, text, attaches, cid, elements, type) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)", "INSERT INTO `messages` (id, chat_id, sender, time, text, attaches, cid, elements, type) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)",
(chatId, senderId, int(time.time() * 1000), text, json.dumps(attaches), cid, json.dumps(elements), type) (message_id, chatId, senderId, message_time, text, json.dumps(attaches), cid, json.dumps(elements), type)
) )
message_id = cursor.lastrowid # id сообщения
# Возвращаем айдишки # Возвращаем айдишки
return int(message_id), int(last_message_id) return int(message_id), int(last_message_id), message_time
async def get_last_message(self, chatId, db_pool): async def get_last_message(self, chatId, db_pool):
"""Получение последнего сообщения в чате""" """Получение последнего сообщения в чате"""
@ -284,11 +285,22 @@ class Tools:
return result return result
async def get_chat_participants(self, chatId, db_pool):
"""Возвращает список ID участников чата из таблицы chat_participants."""
async with db_pool.acquire() as db_connection:
async with db_connection.cursor() as cursor:
await cursor.execute(
"SELECT user_id FROM chat_participants WHERE chat_id = %s",
(chatId,)
)
rows = await cursor.fetchall()
return [row["user_id"] for row in rows]
async def auth_required(self, userPhone, coro, *args): async def auth_required(self, userPhone, coro, *args):
if userPhone: if userPhone:
await coro(*args) await coro(*args)
def generate_user_id(self): def generate_id(self):
# Получаем время в юниксе # Получаем время в юниксе
timestamp = int(time.time()) timestamp = int(time.time())
@ -300,4 +312,4 @@ class Tools:
unique_id = int(hashlib.md5(combined).hexdigest(), 16) % 1000000000 unique_id = int(hashlib.md5(combined).hexdigest(), 16) % 1000000000
# Возвращаем # Возвращаем
return unique_id return unique_id

View File

@ -126,4 +126,8 @@ class AuthConfirmRegisterPayloadModel(pydantic.BaseModel):
class ChatHistoryPayloadModel(pydantic.BaseModel): class ChatHistoryPayloadModel(pydantic.BaseModel):
chatId: int chatId: int
backward: int backward: int
class ChatSubscribePayloadModel(pydantic.BaseModel):
chatId: int
subscribe: bool

View File

@ -365,8 +365,8 @@ class Processors:
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""", """,
( (
self.tools.generate_user_id(), phone, None, first_name, last_name, None, self.tools.generate_id(), phone, None, first_name, last_name, None,
json.dumps([]), json.dumps(["ONEME"]), json.dumps([]), json.dumps(["TT", "ONEME"]),
0, str(now_ms), str(now_s), 0, str(now_ms), str(now_s),
) )
) )
@ -377,12 +377,12 @@ class Processors:
await cursor.execute( await cursor.execute(
""" """
INSERT INTO user_data INSERT INTO user_data
(phone, chats, contacts, folders, user_config, chat_config) (phone, contacts, folders, user_config, chat_config)
VALUES (%s, %s, %s, %s, %s, %s) VALUES (%s %s, %s, %s, %s)
""", """,
( (
phone, phone,
json.dumps([]), json.dumps([]), json.dumps([]),
json.dumps(self.static.USER_FOLDERS), json.dumps(self.static.USER_FOLDERS),
json.dumps(self.static.USER_SETTINGS), json.dumps(self.static.USER_SETTINGS),
json.dumps({}), json.dumps({}),
@ -442,6 +442,9 @@ class Processors:
await self._send_error(seq, self.opcodes.LOGIN, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(seq, self.opcodes.LOGIN, self.error_types.INVALID_PAYLOAD, writer)
return return
# Чаты, где состоит пользователь
chats = []
# Получаем данные из пакета # Получаем данные из пакета
token = payload.get("token") token = payload.get("token")
@ -467,6 +470,18 @@ class Processors:
await cursor.execute("SELECT * FROM user_data WHERE phone = %s", (token_data.get("phone"),)) await cursor.execute("SELECT * FROM user_data WHERE phone = %s", (token_data.get("phone"),))
user_data = await cursor.fetchone() user_data = await cursor.fetchone()
# Ищем все чаты, где состоит пользователь
await cursor.execute(
"SELECT * FROM chat_participants WHERE user_id = %s",
(user.get('id'))
)
user_chats = await cursor.fetchall()
for chat in user_chats:
chats.append(
chat.get("chat_id")
)
# Аватарка с биографией # Аватарка с биографией
photoId = None if not user.get("avatar_id") else int(user.get("avatar_id")) photoId = None if not user.get("avatar_id") else int(user.get("avatar_id"))
avatar_url = None if not photoId else self.config.avatar_base_url + photoId avatar_url = None if not photoId else self.config.avatar_base_url + photoId
@ -490,8 +505,7 @@ class Processors:
) )
chats = await self.tools.generate_chats( chats = await self.tools.generate_chats(
json.loads(user_data.get("chats")), chats, self.db_pool, user.get("id")
self.db_pool, user.get("id")
) )
# Формируем данные пакета # Формируем данные пакета
@ -511,6 +525,10 @@ class Processors:
"time": int(time.time() * 1000) "time": int(time.time() * 1000)
} }
print(
json.dumps(payload, indent=4)
)
# Собираем пакет # Собираем пакет
packet = self.proto.pack_packet( packet = self.proto.pack_packet(
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.LOGIN, payload=payload cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.LOGIN, payload=payload
@ -609,9 +627,6 @@ class Processors:
cid = message.get("cid") or 0 cid = message.get("cid") or 0
text = message.get("text") or "" text = message.get("text") or ""
# Время отправки сообщения
messageTime = int(time.time() * 1000)
# Вычисляем ID чата по ID пользователя и ID отправителя, # Вычисляем ID чата по ID пользователя и ID отправителя,
# в случае отсутствия ID чата # в случае отсутствия ID чата
if not chatId: if not chatId:
@ -637,7 +652,7 @@ class Processors:
return return
# Список участников # Список участников
participants = json.loads(chat.get("participants")) participants = await self.tools.get_chat_participants(chatId, db_pool)
# Проверяем, является ли отправитель участником чата # Проверяем, является ли отправитель участником чата
if int(senderId) not in participants: if int(senderId) not in participants:
@ -645,7 +660,7 @@ class Processors:
return return
# Добавляем сообщение в историю # Добавляем сообщение в историю
messageId, lastMessageId = await self.tools.insert_message( messageId, lastMessageId, messageTime = await self.tools.insert_message(
chatId=chatId, chatId=chatId,
senderId=senderId, senderId=senderId,
text=text, text=text,
@ -854,10 +869,10 @@ class Processors:
if chat: if chat:
# Проверяем, является ли пользователь участником чата # Проверяем, является ли пользователь участником чата
participants = await self.tools.get_chat_participants(chatId, self.db_pool)
# (в max нельзя смотреть и отправлять сообщения в чат, в котором ты не участник, в отличие от tg (например, комментарии в каналах), # (в max нельзя смотреть и отправлять сообщения в чат, в котором ты не участник, в отличие от tg (например, комментарии в каналах),
# так что надо тоже так делать) # так что надо тоже так делать)
if senderId not in json.loads(chat.get("participants")): if int(senderId) not in participants:
continue continue
# Получаем последнее сообщение из чата # Получаем последнее сообщение из чата
@ -868,8 +883,8 @@ class Processors:
# Добавляем чат в список # Добавляем чат в список
chats.append( chats.append(
self.tools.generate_chat( self.tools.generate_chat(
chatId, chat.get("owner"), chatId, chat.get("owner"),
chat.get("type"), json.loads(chat.get("participants")), chat.get("type"), participants,
message, messageTime message, messageTime
) )
) )
@ -935,10 +950,19 @@ class Processors:
# Если диалога нет - создаем # Если диалога нет - создаем
if not chat: if not chat:
await cursor.execute( await cursor.execute(
"INSERT INTO chats (id, owner, type, participants) VALUES (%s, %s, %s, %s)", "INSERT INTO chats (id, owner, type) VALUES (%s, %s, %s)",
(chatId, senderId, "DIALOG", json.dumps([int(senderId), int(user.get("id"))])) (chatId, senderId, "DIALOG")
) )
# Добавляем участников в таблицу chat_participants
participants = [int(senderId), int(user.get("id"))]
for user_id in participants:
await cursor.execute(
"INSERT INTO chat_participants (chat_id, user_id) VALUES (%s, %s)",
(chatId, user_id)
)
# Аватарка с биографией # Аватарка с биографией
photoId = None if not user.get("avatar_id") else int(user.get("avatar_id")) photoId = None if not user.get("avatar_id") else int(user.get("avatar_id"))
avatar_url = None if not photoId else self.config.avatar_base_url + photoId avatar_url = None if not photoId else self.config.avatar_base_url + photoId
@ -1014,7 +1038,7 @@ class Processors:
return return
# Участники чата # Участники чата
participants = json.loads(chat.get("participants")) participants = await self.tools.get_chat_participants(chatId, self.db_pool)
# Проверяем, является ли отправитель участником чата # Проверяем, является ли отправитель участником чата
if int(senderId) not in participants: if int(senderId) not in participants:
@ -1105,7 +1129,7 @@ class Processors:
return return
# Проверяем, является ли пользователь участником чата # Проверяем, является ли пользователь участником чата
participants = json.loads(chat.get("participants")) participants = await self.tools.get_chat_participants(chatId, self.db_pool)
if int(senderId) not in participants: if int(senderId) not in participants:
await self._send_error(seq, self.opcodes.CHAT_HISTORY, self.error_types.CHAT_NOT_ACCESS, writer) await self._send_error(seq, self.opcodes.CHAT_HISTORY, self.error_types.CHAT_NOT_ACCESS, writer)
return return
@ -1241,4 +1265,20 @@ class Processors:
"eventType": "profile_updated", "eventType": "profile_updated",
"profile": profile "profile": profile
} }
) )
async def chat_subscribe(self, payload, seq, writer):
# Валидируем входные данные
try:
ChatSubscribePayloadModel.model_validate(payload)
except Exception as e:
await self._send_error(seq, self.opcodes.CHAT_SUBSCRIBE, self.error_types.INVALID_PAYLOAD, writer)
return
# Созадаем пакет
packet = self.proto.pack_packet(
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CHAT_SUBSCRIBE, payload=None
)
# Отправялем
await self._send(writer, packet)

View File

@ -159,6 +159,10 @@ class OnemeMobileServer:
await self.processors.profile( await self.processors.profile(
payload, seq, writer, userId=userId, userPhone=userPhone payload, seq, writer, userId=userId, userPhone=userPhone
) )
case self.opcodes.CHAT_SUBSCRIBE:
await self.auth_required(
userPhone, self.processors.chat_subscribe, payload, seq, writer
)
case _: case _:
self.logger.warning(f"Неизвестный опкод {opcode}") self.logger.warning(f"Неизвестный опкод {opcode}")
except Exception as e: except Exception as e:

View File

@ -74,7 +74,7 @@ class TelegramBot:
await cursor.execute( await cursor.execute(
self.sql_queries.INSERT_USER, self.sql_queries.INSERT_USER,
( (
self.tools.generate_user_id(), self.tools.generate_id(),
new_phone, # phone new_phone, # phone
tg_id, # telegram_id tg_id, # telegram_id
message.from_user.first_name[:59], # firstname message.from_user.first_name[:59], # firstname
@ -93,7 +93,6 @@ class TelegramBot:
self.sql_queries.INSERT_USER_DATA, self.sql_queries.INSERT_USER_DATA,
( (
new_phone, # phone new_phone, # phone
json.dumps([]), # chats
json.dumps([]), # contacts json.dumps([]), # contacts
json.dumps(self.static.USER_FOLDERS), # folders json.dumps(self.static.USER_FOLDERS), # folders
json.dumps(self.static.USER_SETTINGS), # user settings json.dumps(self.static.USER_SETTINGS), # user settings

View File

@ -34,7 +34,6 @@ CREATE TABLE `auth_tokens` (
CREATE TABLE `user_data` ( CREATE TABLE `user_data` (
`phone` VARCHAR(20) NOT NULL UNIQUE PRIMARY KEY, `phone` VARCHAR(20) NOT NULL UNIQUE PRIMARY KEY,
`chats` JSON NOT NULL,
`contacts` JSON NOT NULL, `contacts` JSON NOT NULL,
`folders` JSON NOT NULL, `folders` JSON NOT NULL,
`user_config` JSON NOT NULL, `user_config` JSON NOT NULL,
@ -44,12 +43,11 @@ CREATE TABLE `user_data` (
CREATE TABLE `chats` ( CREATE TABLE `chats` (
`id` INT NOT NULL PRIMARY KEY, `id` INT NOT NULL PRIMARY KEY,
`owner` INT NOT NULL, `owner` INT NOT NULL,
`type` VARCHAR(16) NOT NULL, `type` VARCHAR(16) NOT NULL
`participants` JSON NOT NULL
); );
CREATE TABLE `messages` ( CREATE TABLE `messages` (
`id` INT AUTO_INCREMENT PRIMARY KEY, `id` INT NOT NULL PRIMARY KEY,
`chat_id` INT NOT NULL, `chat_id` INT NOT NULL,
`sender` INT NOT NULL, `sender` INT NOT NULL,
`time` VARCHAR(32) NOT NULL, `time` VARCHAR(32) NOT NULL,
@ -58,4 +56,11 @@ CREATE TABLE `messages` (
`cid` VARCHAR(32) NOT NULL, `cid` VARCHAR(32) NOT NULL,
`elements` JSON NOT NULL, `elements` JSON NOT NULL,
`type` VARCHAR(16) NOT NULL `type` VARCHAR(16) NOT NULL
); );
CREATE TABLE `chat_participants` (
`chat_id` INT NOT NULL,
`user_id` INT NOT NULL,
`joined_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`chat_id`, `user_id`)
);