diff --git a/src/common/static.py b/src/common/static.py index d10b6c6..1028796 100644 --- a/src/common/static.py +++ b/src/common/static.py @@ -116,15 +116,84 @@ class Static: ### Причины для жалоб 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}, + ]}, ] ### Заглушка для папок diff --git a/src/common/tools.py b/src/common/tools.py index 6f3ea6a..5df66f9 100644 --- a/src/common/tools.py +++ b/src/common/tools.py @@ -23,6 +23,12 @@ class Tools: profileOptions=[], includeProfileOptions=True, username=None, + + # для контактов, собственно + custom_firstname=None, + custom_lastname=None, + + blocked=False ): contact = { "id": id, @@ -51,6 +57,19 @@ class Tools: if 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: return {"contact": contact, "profileOptions": profileOptions} else: @@ -216,6 +235,124 @@ class Tools: 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( self, chatId, senderId, text, attaches, elements, cid, type, db_pool ): diff --git a/src/oneme/controller.py b/src/oneme/controller.py index 4c6f374..df48761 100644 --- a/src/oneme/controller.py +++ b/src/oneme/controller.py @@ -18,6 +18,10 @@ class OnemeController(ControllerBase): eventType = eventData.get("eventType") writer = client.get("writer") + # Не отправляем событие самому себе + if writer == eventData.get("writer"): + return + # Обрабатываем событие if eventType == "new_msg": # Данные сообщения @@ -72,9 +76,8 @@ class OnemeController(ControllerBase): ) # Отправляем пакет - if writer != eventData.get("writer"): - writer.write(packet) - await writer.drain() + writer.write(packet) + await writer.drain() def launch(self, api): async def _start_all(): diff --git a/src/oneme/processors/auth.py b/src/oneme/processors/auth.py index 80d2e21..d53f70b 100644 --- a/src/oneme/processors/auth.py +++ b/src/oneme/processors/auth.py @@ -30,6 +30,27 @@ class AuthProcessors(BaseProcessor): self.server_config = OnemeConfig().SERVER_CONFIG 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): """Обработчик запроса кода""" try: @@ -356,12 +377,11 @@ class AuthProcessors(BaseProcessor): await cursor.execute( """ INSERT INTO user_data - (phone, contacts, folders, user_config, chat_config) - VALUES (%s, %s, %s, %s, %s) + (phone, folders, user_config, chat_config) + VALUES (%s, %s, %s, %s) """, ( phone, - json.dumps([]), json.dumps(self.static.USER_FOLDERS), json.dumps(self.static.USER_SETTINGS), json.dumps({}), @@ -506,17 +526,23 @@ class AuthProcessors(BaseProcessor): username=user.get("username"), ) + # Генерируем список чатов chats = await self.tools.generate_chats( 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 = { "profile": profile, "chats": chats, "chatMarker": 0, "messages": {}, - "contacts": [], + "contacts": contacts, "presence": {}, "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 ) + # print( + # json.dumps(payload, indent=4) + # ) + # Отправляем await self._send(writer, packet) + + # Отправляем баннеры + await self._send_banners(writer) + return int(user.get("phone")), int(user.get("id")), hashed_token async def logout(self, seq, writer, hashedToken): diff --git a/src/oneme/processors/history.py b/src/oneme/processors/history.py index 3470f67..d1a540e 100644 --- a/src/oneme/processors/history.py +++ b/src/oneme/processors/history.py @@ -67,7 +67,8 @@ class HistoryProcessors(BaseProcessor): "text": row.get("text"), "attaches": json.loads(row.get("attaches")), "elements": json.loads(row.get("elements")), - "reactionInfo": {} + "reactionInfo": {}, + "options": 1, }) if forward > 0: diff --git a/src/oneme/processors/messages.py b/src/oneme/processors/messages.py index d7e193a..4a691f5 100644 --- a/src/oneme/processors/messages.py +++ b/src/oneme/processors/messages.py @@ -49,7 +49,8 @@ class MessagesProcessors(BaseProcessor): "eventType": "typing", "chatId": chatId, "type": type, - "userId": senderId + "userId": senderId, + "writer": writer, } ) diff --git a/src/oneme/processors/search.py b/src/oneme/processors/search.py index 353ee18..5211c38 100644 --- a/src/oneme/processors/search.py +++ b/src/oneme/processors/search.py @@ -9,7 +9,7 @@ from oneme.models import ( class SearchProcessors(BaseProcessor): - async def contact_info(self, payload, seq, writer): + async def contact_info(self, payload, seq, writer, senderId): """Поиск пользователей по ID""" # Валидируем данные пакета try: @@ -39,6 +39,16 @@ class SearchProcessors(BaseProcessor): avatar_url = None if not photoId else self.config.avatar_base_url + photoId 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( self.tools.generate_profile( @@ -54,7 +64,10 @@ class SearchProcessors(BaseProcessor): accountStatus=int(user.get("accountstatus")), profileOptions=json.loads(user.get("profileoptions")), 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 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( id=user.get("id"), @@ -133,7 +158,10 @@ class SearchProcessors(BaseProcessor): accountStatus=int(user.get("accountstatus")), profileOptions=json.loads(user.get("profileoptions")), includeProfileOptions=False, - username=user.get("username") + username=user.get("username"), + custom_firstname=custom_firstname, + custom_lastname=custom_lastname, + blocked=blocked, ) # Создаем данные пакета diff --git a/src/oneme/socket.py b/src/oneme/socket.py index 820d09d..7079056 100644 --- a/src/oneme/socket.py +++ b/src/oneme/socket.py @@ -250,6 +250,7 @@ class OnemeMobile: payload, seq, writer, + userId, ) case self.opcodes.COMPLAIN_REASONS_GET: await self.auth_required( diff --git a/src/oneme/websocket.py b/src/oneme/websocket.py index 276e9e8..0da3c7d 100644 --- a/src/oneme/websocket.py +++ b/src/oneme/websocket.py @@ -224,6 +224,7 @@ class OnemeWS: payload, seq, websocket, + userId, ) case self.opcodes.COMPLAIN_REASONS_GET: await self.auth_required( diff --git a/tables.sql b/tables.sql index e118764..30b13b6 100644 --- a/tables.sql +++ b/tables.sql @@ -1,5 +1,5 @@ CREATE TABLE `users` ( - `id` INT PRIMARY KEY, + `id` INT NOT NULL, `phone` VARCHAR(20) UNIQUE, `telegram_id` VARCHAR(64) UNIQUE, `firstname` VARCHAR(59) NOT NULL, @@ -12,7 +12,8 @@ CREATE TABLE `users` ( `options` JSON NOT NULL, `accountstatus` VARCHAR(16) NOT NULL, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - `username` VARCHAR(60) UNIQUE + `username` VARCHAR(60) UNIQUE, + PRIMARY KEY (`id`) ); CREATE TABLE `tokens` ( @@ -21,7 +22,8 @@ CREATE TABLE `tokens` ( `device_type` VARCHAR(256) NOT NULL, `device_name` 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` ( @@ -29,25 +31,27 @@ CREATE TABLE `auth_tokens` ( `token_hash` VARCHAR(64) NOT NULL, `code_hash` VARCHAR(64) NOT NULL, `expires` VARCHAR(16) NOT NULL, - `state` VARCHAR(16) + `state` VARCHAR(16), + PRIMARY KEY (`phone`, `token_hash`) ); CREATE TABLE `user_data` ( - `phone` VARCHAR(20) NOT NULL UNIQUE PRIMARY KEY, - `contacts` JSON NOT NULL, + `phone` VARCHAR(20) NOT NULL UNIQUE, `folders` JSON NOT NULL, `user_config` JSON NOT NULL, - `chat_config` JSON NOT NULL + `chat_config` JSON NOT NULL, + PRIMARY KEY (`phone`) ); CREATE TABLE `chats` ( - `id` INT NOT NULL PRIMARY KEY, + `id` INT NOT NULL, `owner` INT NOT NULL, - `type` VARCHAR(16) NOT NULL + `type` VARCHAR(16) NOT NULL, + PRIMARY KEY (`id`) ); CREATE TABLE `messages` ( - `id` INT NOT NULL PRIMARY KEY, + `id` INT NOT NULL, `chat_id` INT NOT NULL, `sender` INT NOT NULL, `time` VARCHAR(32) NOT NULL, @@ -55,7 +59,8 @@ CREATE TABLE `messages` ( `attaches` JSON NOT NULL, `cid` VARCHAR(32) NOT NULL, `elements` JSON NOT NULL, - `type` VARCHAR(16) NOT NULL + `type` VARCHAR(16) NOT NULL, + PRIMARY KEY (`id`) ); CREATE TABLE `chat_participants` ( @@ -64,3 +69,12 @@ CREATE TABLE `chat_participants` ( `joined_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 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`) +);