From 2dab85356958cc77ff723c00ffaced35b5e00b2f Mon Sep 17 00:00:00 2001 From: Alexey Polyakov Date: Thu, 19 Mar 2026 00:10:21 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B0=D0=B2=D1=82=D0=BE=D1=80=D0=B8=D0=B7=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8E=20=D0=B2=20=D0=A2=D0=B0=D0=BC=D0=A2=D0=B0=D0=BC=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tamtam_tcp/models.py | 4 ++ src/tamtam_tcp/processors.py | 101 +++++++++++++++++++++++++++++++++++ src/tamtam_tcp/server.py | 52 ++++++++++++++++++ 3 files changed, 157 insertions(+) diff --git a/src/tamtam_tcp/models.py b/src/tamtam_tcp/models.py index 9c58d64..5e5ecc8 100644 --- a/src/tamtam_tcp/models.py +++ b/src/tamtam_tcp/models.py @@ -27,4 +27,8 @@ class FinalAuthPayloadModel(pydantic.BaseModel): deviceType: str tokenType: str deviceId: str + token: str + +class LoginPayloadModel(pydantic.BaseModel): + interactive: bool token: str \ No newline at end of file diff --git a/src/tamtam_tcp/processors.py b/src/tamtam_tcp/processors.py index 1d8fe6f..ad0a655 100644 --- a/src/tamtam_tcp/processors.py +++ b/src/tamtam_tcp/processors.py @@ -289,3 +289,104 @@ class Processors: # Отправялем await self._send(writer, packet) + + async def process_login(self, payload, seq, writer): + """Обработчик авторизации клиента на сервере""" + # Валидируем данные пакета + try: + LoginPayloadModel.model_validate(payload) + except pydantic.ValidationError as error: + self.logger.error(f"Возникли ошибки при валидации пакета: {error}") + await self._send_error(seq, self.proto.LOGIN, self.error_types.INVALID_PAYLOAD, writer) + return + + # Получаем данные из пакета + token = payload.get("token") + + # Хешируем токен + hashed_token = hashlib.sha256(token.encode()).hexdigest() + + # Ищем токен в бд + async with self.db_pool.acquire() as conn: + async with conn.cursor() as cursor: + await cursor.execute("SELECT * FROM tokens WHERE token_hash = %s", (hashed_token,)) + token_data = await cursor.fetchone() + + # Если токен не найден, отправляем ошибку + if token_data is None: + await self._send_error(seq, self.proto.VERIFY_CODE, self.error_types.INVALID_TOKEN, writer) + return + + # Ищем аккаунт пользователя в бд + await cursor.execute("SELECT * FROM users WHERE phone = %s", (token_data.get("phone"),)) + user = await cursor.fetchone() + + # Ищем данные пользователя в бд + await cursor.execute("SELECT * FROM user_data WHERE phone = %s", (token_data.get("phone"),)) + user_data = await cursor.fetchone() + + # Аватарка с биографией + photo_id = None if not user.get("avatar_id") else int(user.get("avatar_id")) + avatar_url = None if not photo_id else self.config.avatar_base_url + photo_id + description = None if not user.get("description") else user.get("description") + + # Генерируем профиль + profile = self.tools.generate_profile_tt( + id=user.get("id"), + phone=int(user.get("phone")), + avatarUrl=avatar_url, + photoId=photo_id, + updateTime=int(user.get("updatetime")), + firstName=user.get("firstname"), + lastName=user.get("lastname"), + options=json.loads(user.get("options")), + description=description, + username=user.get("username") + ) + + chats = await self.tools.generate_chats( + json.loads(user_data.get("chats")), + self.db_pool, user.get("id") + ) + + # Формируем данные пакета + payload = { + "profile": profile, + "chats": chats, + "chatMarker": 0, + "messages": {}, + "contacts": [], + "presence": {}, + "config": { + "hash": "0", + "server": {}, + "user": json.loads(user_data.get("user_config")), + "chatFolders": { + "FOLDERS": [], + "ALL_FILTER_EXCLUDE": [] + } + }, + "token": token, + "calls": [], + "videoChatHistory": False, + "drafts": { + "chats": { + "discarded": {}, + "saved": {} + }, + "users": { + "discarded": {}, + "saved": {} + } + }, + "time": int(time.time() * 1000) + } + + # Собираем пакет + packet = self.proto.pack_packet( + cmd=self.proto.CMD_OK, seq=seq, opcode=self.proto.LOGIN, payload=payload + ) + + # Отправляем + await self._send(writer, packet) + return int(user.get("phone")), int(user.get("id")), hashed_token \ No newline at end of file diff --git a/src/tamtam_tcp/server.py b/src/tamtam_tcp/server.py index 1fd1814..1c2d171 100644 --- a/src/tamtam_tcp/server.py +++ b/src/tamtam_tcp/server.py @@ -86,6 +86,14 @@ class TTMobileServer: await self.processors._send_error(seq, self.proto.FINAL_AUTH, self.processors.error_types.RATE_LIMITED, writer) else: await self.processors.process_final_auth(payload, seq, writer, deviceType, deviceName) + case self.proto.LOGIN: + if not self.auth_rate_limiter.is_allowed(address[0]): + await self.processors._send_error(seq, self.proto.LOGIN, self.processors.error_types.RATE_LIMITED, writer) + else: + userPhone, userId, hashedToken = await self.processors.process_login(payload, seq, writer) + + if userPhone: + await self._finish_auth(writer, address, userPhone, userId) case _: self.logger.warning(f"Неизвестный опкод {opcode}") except Exception as e: @@ -95,6 +103,50 @@ class TTMobileServer: writer.close() self.logger.info(f"Прекратил работать работать с клиентом {address[0]}:{address[1]}") + async def _finish_auth(self, writer, addr, phone, id): + """Завершение открытия сессии""" + # Ищем пользователя в словаре + user = self.clients.get(id) + + # Добавляем новое подключение в словарь + if user: + user["clients"].append( + { + "writer": writer, + "ip": addr[0], + "port": addr[1], + "protocol": "oneme_mobile" + } + ) + else: + self.clients[id] = { + "phone": phone, + "id": id, + "clients": [ + { + "writer": writer, + "ip": addr[0], + "port": addr[1], + "protocol": "oneme_mobile" + } + ] + } + + async def _end_session(self, id, ip, port): + """Завершение сессии""" + # Получаем пользователя в списке + user = self.clients.get(id) + if not user: + return + + # Получаем подключения пользователя + clients = user.get("clients", []) + + # Удаляем нужное подключение из словаря + for i, client in enumerate(clients): + if (client.get("ip"), client.get("port")) == (ip, port): + clients.pop(i) + async def start(self): """Функция для запуска сервера""" self.server = await asyncio.start_server(