MAX: заглушка для баннеров, правка пакета со списком жалоб, отдача контактов и прочие улучшения

This commit is contained in:
Alexey Polyakov
2026-04-20 22:22:02 +03:00
parent d9798a6fc6
commit 4121bd0e1d
10 changed files with 321 additions and 32 deletions

View File

@@ -116,15 +116,84 @@ class Static:
### Причины для жалоб ### Причины для жалоб
COMPLAIN_REASONS = [ COMPLAIN_REASONS = [
"Порнография или эротика", {"typeId": 5, "reasons": [
"Экстремизм или терроризм", {"reasonTitle": "Мошенничество", "reasonId": 8},
"Фейк", {"reasonTitle": "Спам", "reasonId": 9},
"Мошенничество", {"reasonTitle": "Порнографический контент", "reasonId": 23},
"Нарушение авторского права", {"reasonTitle": "Насилие", "reasonId": 18},
"Шокирующий контент", {"reasonTitle": "Оскорбления", "reasonId": 11},
"Персональные данные", {"reasonTitle": "Экстремизм", "reasonId": 20},
"Незаконная услуга", {"reasonTitle": "Запрещенные товары", "reasonId": 21},
"Это законно, но надо удалить" {"reasonTitle": "Мне не нравится", "reasonId": 22},
{"reasonTitle": "Другое", "reasonId": 7},
]},
{"typeId": 4, "reasons": [
{"reasonTitle": "Мошенничество", "reasonId": 8},
{"reasonTitle": "Спам", "reasonId": 9},
{"reasonTitle": "Порнографический контент", "reasonId": 23},
{"reasonTitle": "Насилие", "reasonId": 18},
{"reasonTitle": "Оскорбления", "reasonId": 11},
{"reasonTitle": "Экстремизм", "reasonId": 20},
{"reasonTitle": "Запрещенные товары", "reasonId": 21},
{"reasonTitle": "Другое", "reasonId": 7},
]},
{"typeId": 3, "reasons": [
{"reasonTitle": "Мошенничество", "reasonId": 8},
{"reasonTitle": "Спам", "reasonId": 9},
{"reasonTitle": "Порнографический контент", "reasonId": 23},
{"reasonTitle": "Насилие", "reasonId": 18},
{"reasonTitle": "Оскорбления", "reasonId": 11},
{"reasonTitle": "Экстремизм", "reasonId": 20},
{"reasonTitle": "Запрещенные товары", "reasonId": 21},
{"reasonTitle": "Другое", "reasonId": 7},
]},
{"typeId": 7, "reasons": [
{"reasonTitle": "Мошенничество", "reasonId": 8},
{"reasonTitle": "Спам", "reasonId": 9},
{"reasonTitle": "Порнографический контент", "reasonId": 23},
{"reasonTitle": "Насилие", "reasonId": 18},
{"reasonTitle": "Оскорбления", "reasonId": 11},
{"reasonTitle": "Экстремизм", "reasonId": 20},
{"reasonTitle": "Запрещенные товары", "reasonId": 21},
{"reasonTitle": "Другое", "reasonId": 7},
]},
{"typeId": 8, "reasons": [
{"reasonTitle": "Спам", "reasonId": 9},
{"reasonTitle": "Шантаж", "reasonId": 10},
{"reasonTitle": "Оскорбления", "reasonId": 11},
{"reasonTitle": "Другое", "reasonId": 7},
]},
{"typeId": 2, "reasons": [
{"reasonTitle": "Мошенничество", "reasonId": 8},
{"reasonTitle": "Спам", "reasonId": 9},
{"reasonTitle": "Порнографический контент", "reasonId": 23},
{"reasonTitle": "Насилие", "reasonId": 18},
{"reasonTitle": "Оскорбления", "reasonId": 11},
{"reasonTitle": "Экстремизм", "reasonId": 20},
{"reasonTitle": "Запрещенные товары", "reasonId": 21},
{"reasonTitle": "Мне не нравится", "reasonId": 22},
{"reasonTitle": "Другое", "reasonId": 7},
]},
{"typeId": 6, "reasons": [
{"reasonTitle": "Мошенничество", "reasonId": 8},
{"reasonTitle": "Спам", "reasonId": 9},
{"reasonTitle": "Порнографический контент", "reasonId": 23},
{"reasonTitle": "Насилие", "reasonId": 18},
{"reasonTitle": "Оскорбления", "reasonId": 11},
{"reasonTitle": "Экстремизм", "reasonId": 20},
{"reasonTitle": "Запрещенные товары", "reasonId": 21},
{"reasonTitle": "Другое", "reasonId": 7},
]},
{"typeId": 1, "reasons": [
{"reasonTitle": "Мошенничество", "reasonId": 8},
{"reasonTitle": "Спам", "reasonId": 9},
{"reasonTitle": "Порнографический контент", "reasonId": 23},
{"reasonTitle": "Насилие", "reasonId": 18},
{"reasonTitle": "Оскорбления", "reasonId": 11},
{"reasonTitle": "Экстремизм", "reasonId": 20},
{"reasonTitle": "Запрещенные товары", "reasonId": 21},
{"reasonTitle": "Другое", "reasonId": 7},
]},
] ]
### Заглушка для папок ### Заглушка для папок

View File

@@ -23,6 +23,12 @@ class Tools:
profileOptions=[], profileOptions=[],
includeProfileOptions=True, includeProfileOptions=True,
username=None, username=None,
# для контактов, собственно
custom_firstname=None,
custom_lastname=None,
blocked=False
): ):
contact = { contact = {
"id": id, "id": id,
@@ -51,6 +57,19 @@ class Tools:
if username: if username:
contact["link"] = "https://max.ru/" + username contact["link"] = "https://max.ru/" + username
if custom_firstname:
contact["names"].append(
{
"name": custom_firstname,
"firstName": custom_firstname,
"lastName": custom_lastname,
"type": "CUSTOM"
}
)
if blocked:
contact["status"] = "BLOCKED"
if includeProfileOptions: if includeProfileOptions:
return {"contact": contact, "profileOptions": profileOptions} return {"contact": contact, "profileOptions": profileOptions}
else: else:
@@ -216,6 +235,124 @@ class Tools:
return chats return chats
async def generate_contacts(
self,
contacts,
db_pool,
avatar_base_url="",
):
"""
Генерация контакт-листа для отдачи клиенту
[notes]
В contacts должен поступать список вида
[
{
"firstname": "test",
"lastname": "testovich",
"id": 4323
}
]
А формировать мы должны его до вызова функции,
ибо я хочу вынести контакты в отдельную таблицу,
по моему мнению так будет намного практичнее и лучше
"""
# Готовый список с контакт-листом
contact_list = []
# Формируем список контактов
for contact in contacts:
# ID контакта
contact_id = contact.get("id")
# Имя и фамилия которые указал юзер для контакта
firstname = contact.get("firstname")
lastname = contact.get("lastname")
blocked = contact.get("blocked", False)
async with db_pool.acquire() as db_connection:
async with db_connection.cursor() as cursor:
# Получаем контакт по id
await cursor.execute(
"SELECT * FROM `users` WHERE id = %s", (contact_id,)
)
user = await cursor.fetchone()
if user:
# Аватарка с биографией
photoId = (
None
if not user.get("avatar_id")
else int(user.get("avatar_id"))
)
avatar_url = (
None
if not photoId
else avatar_base_url + str(photoId)
)
description = (
None
if not user.get("description")
else user.get("description")
)
# Создаем профиль
contact = self.generate_profile(
id=user.get("id"),
phone=int(user.get("phone")),
avatarUrl=avatar_url,
photoId=photoId,
updateTime=int(user.get("updatetime")),
firstName=user.get("firstname"),
lastName=user.get("lastname"),
options=json.loads(user.get("options")),
description=description,
accountStatus=int(user.get("accountstatus")),
includeProfileOptions=False,
username=user.get("username"),
custom_firstname=firstname,
custom_lastname=lastname,
blocked=blocked,
)
# Выносим результат в лист
contact_list.append(contact)
return contact_list
async def collect_user_contacts(
self,
owner_id,
db_pool,
avatar_base_url="",
):
"""Собирает все контакты пользователя и возвращает готовый контакт-лист"""
contacts = []
async with db_pool.acquire() as db_connection:
async with db_connection.cursor() as cursor:
await cursor.execute(
"SELECT * FROM `contacts` WHERE owner_id = %s",
(owner_id,),
)
rows = await cursor.fetchall()
for row in rows:
contacts.append(
{
"id": int(row.get("contact_id")),
"firstname": row.get("custom_firstname"),
"lastname": row.get("custom_lastname"),
"blocked": bool(row.get("is_blocked")),
}
)
return await self.generate_contacts(
contacts, db_pool, avatar_base_url=avatar_base_url
)
async def insert_message( async def insert_message(
self, chatId, senderId, text, attaches, elements, cid, type, db_pool self, chatId, senderId, text, attaches, elements, cid, type, db_pool
): ):

View File

@@ -18,6 +18,10 @@ class OnemeController(ControllerBase):
eventType = eventData.get("eventType") eventType = eventData.get("eventType")
writer = client.get("writer") writer = client.get("writer")
# Не отправляем событие самому себе
if writer == eventData.get("writer"):
return
# Обрабатываем событие # Обрабатываем событие
if eventType == "new_msg": if eventType == "new_msg":
# Данные сообщения # Данные сообщения
@@ -72,9 +76,8 @@ class OnemeController(ControllerBase):
) )
# Отправляем пакет # Отправляем пакет
if writer != eventData.get("writer"): writer.write(packet)
writer.write(packet) await writer.drain()
await writer.drain()
def launch(self, api): def launch(self, api):
async def _start_all(): async def _start_all():

View File

@@ -30,6 +30,27 @@ class AuthProcessors(BaseProcessor):
self.server_config = OnemeConfig().SERVER_CONFIG self.server_config = OnemeConfig().SERVER_CONFIG
self.telegram_bot = telegram_bot self.telegram_bot = telegram_bot
async def _send_banners(self, writer):
"""Функция отправки баннеров клиенту"""
payload = {
"showTime": 86400000, # Сколько будет показываться баннер, тут сутки в миллисекундах
# можно в будущем переделать, и сделать выбор в конфигурации
# думаю, было бы прикольно
"updateTime": int(time.time() * 1000),
"banners": [
# TODO: разобраться как работают баннеры и их реализовать
# думаю админам инстансов было бы прикольно, и нам
]
}
# Собираем пакет
packet = self.proto.pack_packet(
cmd=0, opcode=self.opcodes.NOTIF_BANNERS, payload=payload
)
# Отправляет
await self._send(writer, packet)
async def auth_request(self, payload, seq, writer): async def auth_request(self, payload, seq, writer):
"""Обработчик запроса кода""" """Обработчик запроса кода"""
try: try:
@@ -356,12 +377,11 @@ class AuthProcessors(BaseProcessor):
await cursor.execute( await cursor.execute(
""" """
INSERT INTO user_data INSERT INTO user_data
(phone, contacts, folders, user_config, chat_config) (phone, folders, user_config, chat_config)
VALUES (%s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s)
""", """,
( (
phone, phone,
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({}),
@@ -506,17 +526,23 @@ class AuthProcessors(BaseProcessor):
username=user.get("username"), username=user.get("username"),
) )
# Генерируем список чатов
chats = await self.tools.generate_chats( chats = await self.tools.generate_chats(
chats, self.db_pool, user.get("id"), protocol_type=self.type chats, self.db_pool, user.get("id"), protocol_type=self.type
) )
# Генерируем список контактов
contacts = await self.tools.collect_user_contacts(
user.get("id"), self.db_pool, self.config.avatar_base_url
)
# Формируем данные пакета # Формируем данные пакета
payload = { payload = {
"profile": profile, "profile": profile,
"chats": chats, "chats": chats,
"chatMarker": 0, "chatMarker": 0,
"messages": {}, "messages": {},
"contacts": [], "contacts": contacts,
"presence": {}, "presence": {},
"config": { "config": {
"server": self.server_config, "server": self.server_config,
@@ -532,8 +558,16 @@ class AuthProcessors(BaseProcessor):
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
) )
# print(
# json.dumps(payload, indent=4)
# )
# Отправляем # Отправляем
await self._send(writer, packet) await self._send(writer, packet)
# Отправляем баннеры
await self._send_banners(writer)
return int(user.get("phone")), int(user.get("id")), hashed_token return int(user.get("phone")), int(user.get("id")), hashed_token
async def logout(self, seq, writer, hashedToken): async def logout(self, seq, writer, hashedToken):

View File

@@ -67,7 +67,8 @@ class HistoryProcessors(BaseProcessor):
"text": row.get("text"), "text": row.get("text"),
"attaches": json.loads(row.get("attaches")), "attaches": json.loads(row.get("attaches")),
"elements": json.loads(row.get("elements")), "elements": json.loads(row.get("elements")),
"reactionInfo": {} "reactionInfo": {},
"options": 1,
}) })
if forward > 0: if forward > 0:

View File

@@ -49,7 +49,8 @@ class MessagesProcessors(BaseProcessor):
"eventType": "typing", "eventType": "typing",
"chatId": chatId, "chatId": chatId,
"type": type, "type": type,
"userId": senderId "userId": senderId,
"writer": writer,
} }
) )

View File

@@ -9,7 +9,7 @@ from oneme.models import (
class SearchProcessors(BaseProcessor): class SearchProcessors(BaseProcessor):
async def contact_info(self, payload, seq, writer): async def contact_info(self, payload, seq, writer, senderId):
"""Поиск пользователей по ID""" """Поиск пользователей по ID"""
# Валидируем данные пакета # Валидируем данные пакета
try: try:
@@ -39,6 +39,16 @@ class SearchProcessors(BaseProcessor):
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
description = None if not user.get("description") else user.get("description") description = None if not user.get("description") else user.get("description")
# Получаем данные контакта
await cursor.execute(
"SELECT * FROM contacts WHERE owner_id = %s AND contact_id = %s",
(senderId, contactId),
)
contact_row = await cursor.fetchone()
custom_firstname = contact_row.get("custom_firstname") if contact_row else None
custom_lastname = contact_row.get("custom_lastname") if contact_row else None
blocked = bool(contact_row.get("is_blocked")) if contact_row else False
# Генерируем профиль # Генерируем профиль
users.append( users.append(
self.tools.generate_profile( self.tools.generate_profile(
@@ -54,7 +64,10 @@ class SearchProcessors(BaseProcessor):
accountStatus=int(user.get("accountstatus")), accountStatus=int(user.get("accountstatus")),
profileOptions=json.loads(user.get("profileoptions")), profileOptions=json.loads(user.get("profileoptions")),
includeProfileOptions=False, includeProfileOptions=False,
username=user.get("username") username=user.get("username"),
custom_firstname=custom_firstname,
custom_lastname=custom_lastname,
blocked=blocked,
) )
) )
@@ -119,6 +132,18 @@ class SearchProcessors(BaseProcessor):
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
description = None if not user.get("description") else user.get("description") description = None if not user.get("description") else user.get("description")
# Получаем данные контакта
async with self.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",
(senderId, user.get("id")),
)
contact_row = await cursor.fetchone()
custom_firstname = contact_row.get("custom_firstname") if contact_row else None
custom_lastname = contact_row.get("custom_lastname") if contact_row else None
blocked = bool(contact_row.get("is_blocked")) if contact_row else False
# Генерируем профиль # Генерируем профиль
profile = self.tools.generate_profile( profile = self.tools.generate_profile(
id=user.get("id"), id=user.get("id"),
@@ -133,7 +158,10 @@ class SearchProcessors(BaseProcessor):
accountStatus=int(user.get("accountstatus")), accountStatus=int(user.get("accountstatus")),
profileOptions=json.loads(user.get("profileoptions")), profileOptions=json.loads(user.get("profileoptions")),
includeProfileOptions=False, includeProfileOptions=False,
username=user.get("username") username=user.get("username"),
custom_firstname=custom_firstname,
custom_lastname=custom_lastname,
blocked=blocked,
) )
# Создаем данные пакета # Создаем данные пакета

View File

@@ -250,6 +250,7 @@ class OnemeMobile:
payload, payload,
seq, seq,
writer, writer,
userId,
) )
case self.opcodes.COMPLAIN_REASONS_GET: case self.opcodes.COMPLAIN_REASONS_GET:
await self.auth_required( await self.auth_required(

View File

@@ -224,6 +224,7 @@ class OnemeWS:
payload, payload,
seq, seq,
websocket, websocket,
userId,
) )
case self.opcodes.COMPLAIN_REASONS_GET: case self.opcodes.COMPLAIN_REASONS_GET:
await self.auth_required( await self.auth_required(

View File

@@ -1,5 +1,5 @@
CREATE TABLE `users` ( CREATE TABLE `users` (
`id` INT PRIMARY KEY, `id` INT NOT NULL,
`phone` VARCHAR(20) UNIQUE, `phone` VARCHAR(20) UNIQUE,
`telegram_id` VARCHAR(64) UNIQUE, `telegram_id` VARCHAR(64) UNIQUE,
`firstname` VARCHAR(59) NOT NULL, `firstname` VARCHAR(59) NOT NULL,
@@ -12,7 +12,8 @@ CREATE TABLE `users` (
`options` JSON NOT NULL, `options` JSON NOT NULL,
`accountstatus` VARCHAR(16) NOT NULL, `accountstatus` VARCHAR(16) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`username` VARCHAR(60) UNIQUE `username` VARCHAR(60) UNIQUE,
PRIMARY KEY (`id`)
); );
CREATE TABLE `tokens` ( CREATE TABLE `tokens` (
@@ -21,7 +22,8 @@ CREATE TABLE `tokens` (
`device_type` VARCHAR(256) NOT NULL, `device_type` VARCHAR(256) NOT NULL,
`device_name` VARCHAR(256) NOT NULL, `device_name` VARCHAR(256) NOT NULL,
`location` VARCHAR(256) NOT NULL, `location` VARCHAR(256) NOT NULL,
`time` VARCHAR(16) NOT NULL `time` VARCHAR(16) NOT NULL,
PRIMARY KEY (`phone`, `token_hash`)
); );
CREATE TABLE `auth_tokens` ( CREATE TABLE `auth_tokens` (
@@ -29,25 +31,27 @@ CREATE TABLE `auth_tokens` (
`token_hash` VARCHAR(64) NOT NULL, `token_hash` VARCHAR(64) NOT NULL,
`code_hash` VARCHAR(64) NOT NULL, `code_hash` VARCHAR(64) NOT NULL,
`expires` VARCHAR(16) NOT NULL, `expires` VARCHAR(16) NOT NULL,
`state` VARCHAR(16) `state` VARCHAR(16),
PRIMARY KEY (`phone`, `token_hash`)
); );
CREATE TABLE `user_data` ( CREATE TABLE `user_data` (
`phone` VARCHAR(20) NOT NULL UNIQUE PRIMARY KEY, `phone` VARCHAR(20) NOT NULL UNIQUE,
`contacts` JSON NOT NULL,
`folders` JSON NOT NULL, `folders` JSON NOT NULL,
`user_config` JSON NOT NULL, `user_config` JSON NOT NULL,
`chat_config` JSON NOT NULL `chat_config` JSON NOT NULL,
PRIMARY KEY (`phone`)
); );
CREATE TABLE `chats` ( CREATE TABLE `chats` (
`id` INT NOT NULL PRIMARY KEY, `id` INT NOT NULL,
`owner` INT NOT NULL, `owner` INT NOT NULL,
`type` VARCHAR(16) NOT NULL `type` VARCHAR(16) NOT NULL,
PRIMARY KEY (`id`)
); );
CREATE TABLE `messages` ( CREATE TABLE `messages` (
`id` INT NOT NULL PRIMARY KEY, `id` INT NOT NULL,
`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,
@@ -55,7 +59,8 @@ CREATE TABLE `messages` (
`attaches` JSON NOT NULL, `attaches` JSON NOT NULL,
`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,
PRIMARY KEY (`id`)
); );
CREATE TABLE `chat_participants` ( CREATE TABLE `chat_participants` (
@@ -64,3 +69,12 @@ CREATE TABLE `chat_participants` (
`joined_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `joined_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`chat_id`, `user_id`) PRIMARY KEY (`chat_id`, `user_id`)
); );
CREATE TABLE `contacts` (
`owner_id` INT NOT NULL,
`contact_id` INT NOT NULL,
`custom_firstname` VARCHAR(59),
`custom_lastname` VARCHAR(59),
`is_blocked` BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (`owner_id`, `contact_id`)
);