Compare commits

..

4 Commits

Author SHA1 Message Date
Alexey Polyakov
9034485408 Различные фиксы (#14)
* Исключаем только ошибку валидации

* Небольшие правки вебсокета тамтама

* Теперь номера брутить чутка сложнее

* Авторизация теперь проверяется для некоторых команд

* Теперь проверяется orign у вебсокета тамтама

* Дополнил пример env

* Починил немного сокет тамтама
2026-03-11 15:21:49 +03:00
Alexey Polyakov
4bd632e6df Мелкие правки 2026-03-10 18:21:59 +03:00
WowInceptionGood
32a88e8b05 Update README 2026-03-10 13:55:50 +00:00
Anatoliy Esherkin
a2d6be7b94 Добавил причины для жалоб (#4)
* Добавил причины для жалоб

* Update static.py

---------

Co-authored-by: WowInceptionGood <143893762+WowInceptionGood@users.noreply.github.com>
2026-03-10 16:37:35 +03:00
13 changed files with 211 additions and 118 deletions

View File

@@ -24,3 +24,4 @@ avatar_base_url = "http://127.0.0.1/avatar/"
telegram_bot_token = "123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ" telegram_bot_token = "123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ"
telegram_bot_enabled = "1" telegram_bot_enabled = "1"
telegram_whitelist_ids = "1,2,3" telegram_whitelist_ids = "1,2,3"
origins="http://127.0.0.1,https://web.openmax.su"

1
docs/proto/oneme_tcp.md Normal file
View File

@@ -0,0 +1 @@
TODO

View File

@@ -1,3 +1,9 @@
> [!Caution]
>
> Проект находится на ранней стадии разработки и вероятно полон багов.
>
> Использование в профессиональных средах не рекомендовано.
# OpenMAX # OpenMAX
Эмулятор сервера MAX и ТамТам Эмулятор сервера MAX и ТамТам
@@ -15,6 +21,8 @@ https://t.me/openmax_alerts
Клиент может быть практически любым, главное условие - чтобы он был совместим с официальным сервером (`api.oneme.ru` / `api.tamtam.chat`). Клиент может быть практически любым, главное условие - чтобы он был совместим с официальным сервером (`api.oneme.ru` / `api.tamtam.chat`).
На данный момент с сервером может работать последняя версия MAX (26.7.1), однако все тесты проходят на версии 26.5.0.
# Установка # Установка
1. Склонируйте репозиторий 1. Склонируйте репозиторий

View File

@@ -44,3 +44,6 @@ class ServerConfig:
telegram_bot_token = os.getenv("telegram_bot_token") or "123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ" telegram_bot_token = os.getenv("telegram_bot_token") or "123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ"
telegram_bot_enabled = bool(os.getenv("telegram_bot_enabled")) or True telegram_bot_enabled = bool(os.getenv("telegram_bot_enabled")) or True
telegram_whitelist_ids = [x.strip() for x in os.getenv("telegram_whitelist_ids", "").split(",") if x.strip()] telegram_whitelist_ids = [x.strip() for x in os.getenv("telegram_whitelist_ids", "").split(",") if x.strip()]
### origins
origins = [x.strip() for x in os.getenv("origins", "").split(",") if x.strip()] if os.getenv("origins") else None

View File

@@ -109,7 +109,15 @@ class Static:
### Причины для жалоб ### Причины для жалоб
COMPLAIN_REASONS = [ COMPLAIN_REASONS = [
# TODO: Было бы очень замечательно заполнить этот лист причинами для жалоб "Порнография или эротика",
"Экстремизм или терроризм",
"Фейк",
"Мошенничество",
"Нарушение авторского права",
"Шокирующий контент",
"Персональные данные",
"Незаконная услуга",
"Это законно, но надо удалить"
] ]
### Заглушка для папок ### Заглушка для папок

View File

@@ -47,6 +47,38 @@ class Tools:
else: else:
return contact return contact
def generate_profile_tt(
self, id=1, phone=70000000000, avatarUrl=None,
photoId=None, updateTime=0,
firstName="Test", lastName="Account", options=[],
description=None, username=None
):
contact = {
"id": id,
"updateTime": updateTime,
"phone": phone,
"names": [
{
"name": f"{firstName} {lastName}",
"type": "TT"
}
],
"options": options
}
if avatarUrl:
contact["photoId"] = photoId
contact["baseUrl"] = avatarUrl
contact["baseRawUrl"] = avatarUrl
if description:
contact["description"] = description
if username:
contact["link"] = "https://tamtam.chat/" + username
return contact
def generate_chat(self, id, owner, type, participants, lastMessage, lastEventTime): def generate_chat(self, id, owner, type, participants, lastMessage, lastEventTime):
"""Генерация чата""" """Генерация чата"""
# Генерируем список участников # Генерируем список участников
@@ -102,20 +134,14 @@ class Tools:
# Выносим результат в лист # Выносим результат в лист
chats.append( chats.append(
{ self.generate_chat(
"id": row.get("id"), row.get("id"),
"type": row.get("type"), row.get("owner"),
"status": "ACTIVE", row.get("type"),
"owner": row.get("owner"), participants,
"participants": participants, message,
"lastMessage": message, messageTime
"lastEventTime": messageTime, )
"lastDelayedUpdateTime": 0,
"lastFireDelayedErrorTime": 0,
"created": 1,
"joinTime": 1,
"modified": messageTime
}
) )
# Получаем последнее сообщение из избранного # Получаем последнее сообщение из избранного
@@ -123,24 +149,19 @@ class Tools:
senderId, db_pool senderId, db_pool
) )
# ID избранного
chatId = senderId ^ senderId
# Хардкодим в лист чатов избранное # Хардкодим в лист чатов избранное
chats.append( chats.append(
{ self.generate_chat(
"id": 0, chatId,
"type": "DIALOG", senderId,
"status": "ACTIVE", "DIALOG",
"owner": senderId, [senderId],
"participants": { message,
str(senderId): 0 # if not messageTime else messageTime messageTime
}, )
"lastMessage": message,
"lastEventTime": messageTime,
"lastDelayedUpdateTime": 0,
"lastFireDelayedErrorTime": 0,
"created": 1,
"joinTime": 1,
"modified": messageTime
}
) )
return chats return chats
@@ -185,10 +206,16 @@ class Tools:
"time": int(row.get("time")), "time": int(row.get("time")),
"type": row.get("type"), "type": row.get("type"),
"sender": row.get("sender"), "sender": row.get("sender"),
"cid": int(row.get("cid")),
"text": row.get("text"), "text": row.get("text"),
"attaches": json.loads(row.get("attaches")), "attaches": json.loads(row.get("attaches")),
# "reactionInfo": {} "elements": json.loads(row.get("elements")),
"reactionInfo": {}
} }
# Возвращаем # Возвращаем
return message, int(row.get("time")) return message, int(row.get("time"))
async def auth_required(self, userPhone, coro, *args):
if userPhone:
await coro(*args)

View File

@@ -68,7 +68,8 @@ async def main():
"db": db, "db": db,
"ssl": ssl_context, "ssl": ssl_context,
"clients": clients, "clients": clients,
"event": api_event "event": api_event,
"origins": server_config.origins
} }
controllers = { controllers = {

View File

@@ -48,8 +48,9 @@ class Processors:
# Валидируем данные пакета # Валидируем данные пакета
try: try:
HelloPayloadModel.model_validate(payload) HelloPayloadModel.model_validate(payload)
except Exception as e: except pydantic.ValidationError as error:
await self._send_error(seq, self.proto.HELLO, self.error_types.INVALID_PAYLOAD, writer) self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
await self._send_error(seq, self.proto.SESSION_INIT, self.error_types.INVALID_PAYLOAD, writer)
return None, None return None, None
# Получаем данные из пакета # Получаем данные из пакета
@@ -86,7 +87,8 @@ class Processors:
# Валидируем данные пакета # Валидируем данные пакета
try: try:
PingPayloadModel.model_validate(payload) PingPayloadModel.model_validate(payload)
except Exception as e: except pydantic.ValidationError as error:
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
await self._send_error(seq, self.proto.PING, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(seq, self.proto.PING, self.error_types.INVALID_PAYLOAD, writer)
return return
@@ -115,7 +117,8 @@ class Processors:
# Валидируем данные пакета # Валидируем данные пакета
try: try:
RequestCodePayloadModel.model_validate(payload) RequestCodePayloadModel.model_validate(payload)
except Exception as e: except pydantic.ValidationError as error:
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
await self._send_error(seq, self.proto.AUTH_REQUEST, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(seq, self.proto.AUTH_REQUEST, self.error_types.INVALID_PAYLOAD, writer)
return return
@@ -139,11 +142,8 @@ class Processors:
await cursor.execute("SELECT * FROM users WHERE phone = %s", (phone,)) await cursor.execute("SELECT * FROM users WHERE phone = %s", (phone,))
user = await cursor.fetchone() user = await cursor.fetchone()
# Если пользователя нет - отдаем ошибку # Если пользователя найден - сохраняем токен и отправляем код
if user is None: if user:
await self._send_error(seq, self.proto.AUTH_REQUEST, self.error_types.USER_NOT_FOUND, writer)
return
# Сохраняем токен # Сохраняем токен
await cursor.execute("INSERT INTO auth_tokens (phone, token_hash, code_hash, expires) VALUES (%s, %s, %s, %s)", (phone, token_hash, code_hash, expires,)) await cursor.execute("INSERT INTO auth_tokens (phone, token_hash, code_hash, expires) VALUES (%s, %s, %s, %s)", (phone, token_hash, code_hash, expires,))
@@ -174,7 +174,8 @@ class Processors:
# Валидируем данные пакета # Валидируем данные пакета
try: try:
VerifyCodePayloadModel.model_validate(payload) VerifyCodePayloadModel.model_validate(payload)
except Exception as e: except pydantic.ValidationError as error:
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
await self._send_error(seq, self.proto.AUTH, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(seq, self.proto.AUTH, self.error_types.INVALID_PAYLOAD, writer)
return return
@@ -263,7 +264,8 @@ class Processors:
# Валидируем данные пакета # Валидируем данные пакета
try: try:
LoginPayloadModel.model_validate(payload) LoginPayloadModel.model_validate(payload)
except Exception as e: except pydantic.ValidationError as error:
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
await self._send_error(seq, self.proto.LOGIN, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(seq, self.proto.LOGIN, self.error_types.INVALID_PAYLOAD, writer)
return return
@@ -365,7 +367,8 @@ class Processors:
# Валидируем данные пакета # Валидируем данные пакета
try: try:
AssetsPayloadModel.model_validate(payload) AssetsPayloadModel.model_validate(payload)
except Exception as e: except pydantic.ValidationError as error:
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
await self._send_error(seq, self.proto.ASSETS_UPDATE, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(seq, self.proto.ASSETS_UPDATE, self.error_types.INVALID_PAYLOAD, writer)
return return
@@ -390,7 +393,8 @@ class Processors:
# Валидируем данные пакета # Валидируем данные пакета
try: try:
GetCallHistoryPayloadModel.model_validate(payload) GetCallHistoryPayloadModel.model_validate(payload)
except Exception as e: except pydantic.ValidationError as error:
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
await self._send_error(seq, self.proto.VIDEO_CHAT_HISTORY, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(seq, self.proto.VIDEO_CHAT_HISTORY, self.error_types.INVALID_PAYLOAD, writer)
return return
@@ -417,7 +421,8 @@ class Processors:
# Валидируем данные пакета # Валидируем данные пакета
try: try:
SendMessagePayloadModel.model_validate(payload) SendMessagePayloadModel.model_validate(payload)
except Exception as e: except pydantic.ValidationError as error:
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
await self._send_error(seq, self.proto.MSG_SEND, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(seq, self.proto.MSG_SEND, self.error_types.INVALID_PAYLOAD, writer)
return return
@@ -445,7 +450,7 @@ class Processors:
chatId = userId ^ senderId chatId = userId ^ senderId
# Если клиент хочет отправить сообщение в избранное, # Если клиент хочет отправить сообщение в избранное,
# то выставляем ID чата 0 # то выставляем в качестве ID чата ID отправителя
# (А ещё используем это, если клиент вообще ничего не указал) # (А ещё используем это, если клиент вообще ничего не указал)
if chatId == 0 or not chatId: if chatId == 0 or not chatId:
chatId = senderId chatId = senderId
@@ -491,7 +496,8 @@ class Processors:
"sender": senderId, "sender": senderId,
"cid": cid, "cid": cid,
"text": text, "text": text,
"attaches": attaches "attaches": attaches,
"elements": elements
} }
# Отправляем событие всем участникам чата # Отправляем событие всем участникам чата
@@ -528,7 +534,8 @@ class Processors:
# Валидируем данные пакета # Валидируем данные пакета
try: try:
SyncFoldersPayloadModel.model_validate(payload) SyncFoldersPayloadModel.model_validate(payload)
except Exception as e: except pydantic.ValidationError as error:
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
await self._send_error(seq, self.proto.FOLDERS_GET, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(seq, self.proto.FOLDERS_GET, self.error_types.INVALID_PAYLOAD, writer)
return return
@@ -596,7 +603,8 @@ class Processors:
# Валидируем данные пакета # Валидируем данные пакета
try: try:
SearchUsersPayloadModel.model_validate(payload) SearchUsersPayloadModel.model_validate(payload)
except Exception as e: except pydantic.ValidationError as error:
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
await self._send_error(seq, self.proto.CONTACT_INFO, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(seq, self.proto.CONTACT_INFO, self.error_types.INVALID_PAYLOAD, writer)
return return
@@ -657,7 +665,8 @@ class Processors:
# Валидируем данные пакета # Валидируем данные пакета
try: try:
SearchChatsPayloadModel.model_validate(payload) SearchChatsPayloadModel.model_validate(payload)
except Exception as e: except pydantic.ValidationError as error:
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
await self._send_error(seq, self.proto.CHAT_INFO, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(seq, self.proto.CHAT_INFO, self.error_types.INVALID_PAYLOAD, writer)
return return
@@ -700,6 +709,9 @@ class Processors:
senderId, self.db_pool senderId, self.db_pool
) )
# ID избранного
chatId = senderId ^ senderId
# Добавляем чат в список # Добавляем чат в список
chats.append( chats.append(
self.tools.generate_chat( self.tools.generate_chat(
@@ -727,7 +739,8 @@ class Processors:
# Валидируем данные пакета # Валидируем данные пакета
try: try:
SearchByPhonePayloadModel.model_validate(payload) SearchByPhonePayloadModel.model_validate(payload)
except Exception as e: except pydantic.ValidationError as error:
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
await self._send_error(seq, self.proto.CONTACT_INFO_BY_PHONE, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(seq, self.proto.CONTACT_INFO_BY_PHONE, self.error_types.INVALID_PAYLOAD, writer)
return return
@@ -796,7 +809,8 @@ class Processors:
# Валидируем данные пакета # Валидируем данные пакета
try: try:
GetCallTokenPayloadModel.model_validate(payload) GetCallTokenPayloadModel.model_validate(payload)
except Exception as e: except pydantic.ValidationError as error:
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
await self._send_error(seq, self.proto.OK_TOKEN, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(seq, self.proto.OK_TOKEN, self.error_types.INVALID_PAYLOAD, writer)
return return
@@ -809,7 +823,8 @@ class Processors:
# Валидируем данные пакета # Валидируем данные пакета
try: try:
TypingPayloadModel.model_validate(payload) TypingPayloadModel.model_validate(payload)
except Exception as e: except pydantic.ValidationError as error:
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
await self._send_error(seq, self.proto.MSG_TYPING, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(seq, self.proto.MSG_TYPING, self.error_types.INVALID_PAYLOAD, writer)
return return
@@ -863,7 +878,8 @@ class Processors:
# Валидируем данные пакета # Валидируем данные пакета
try: try:
ComplainReasonsGetPayloadModel.model_validate(payload) ComplainReasonsGetPayloadModel.model_validate(payload)
except Exception as e: except pydantic.ValidationError as error:
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
await self._send_error(seq, self.proto.COMPLAIN_REASONS_GET, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(seq, self.proto.COMPLAIN_REASONS_GET, self.error_types.INVALID_PAYLOAD, writer)
return return

View File

@@ -1,6 +1,7 @@
import asyncio, logging, traceback import asyncio, logging, traceback
from oneme_tcp.proto import Proto from oneme_tcp.proto import Proto
from oneme_tcp.processors import Processors from oneme_tcp.processors import Processors
from common.tools import Tools
class OnemeMobileServer: class OnemeMobileServer:
def __init__(self, host="0.0.0.0", port=443, ssl_context=None, db_pool=None, clients={}, send_event=None, telegram_bot=None): def __init__(self, host="0.0.0.0", port=443, ssl_context=None, db_pool=None, clients={}, send_event=None, telegram_bot=None):
@@ -13,6 +14,7 @@ class OnemeMobileServer:
self.clients = clients self.clients = clients
self.proto = Proto() self.proto = Proto()
self.auth_required = Tools().auth_required
self.processors = Processors(db_pool=db_pool, clients=clients, send_event=send_event, telegram_bot=telegram_bot) self.processors = Processors(db_pool=db_pool, clients=clients, send_event=send_event, telegram_bot=telegram_bot)
async def handle_client(self, reader, writer): async def handle_client(self, reader, writer):
@@ -54,6 +56,7 @@ class OnemeMobileServer:
case self.proto.LOGIN: case self.proto.LOGIN:
userPhone, userId, hashedToken = await self.processors.process_login(payload, seq, writer) userPhone, userId, hashedToken = await self.processors.process_login(payload, seq, writer)
# Если авторизация на сервере успешная - можем завершить авторизацию
if userPhone: if userPhone:
await self._finish_auth(writer, address, userPhone, userId) await self._finish_auth(writer, address, userPhone, userId)
case self.proto.LOGOUT: case self.proto.LOGOUT:
@@ -64,27 +67,49 @@ class OnemeMobileServer:
case self.proto.LOG: case self.proto.LOG:
await self.processors.process_telemetry(payload, seq, writer) await self.processors.process_telemetry(payload, seq, writer)
case self.proto.ASSETS_UPDATE: case self.proto.ASSETS_UPDATE:
await self.processors.process_get_assets(payload, seq, writer) await self.auth_required(
userPhone, self.processors.process_get_assets, payload, seq, writer
)
case self.proto.VIDEO_CHAT_HISTORY: case self.proto.VIDEO_CHAT_HISTORY:
await self.processors.process_get_call_history(payload, seq, writer) await self.auth_required(
userPhone, self.processors.process_get_call_history, payload, seq, writer
)
case self.proto.MSG_SEND: case self.proto.MSG_SEND:
await self.processors.process_send_message(payload, seq, writer, senderId=userId, db_pool=self.db_pool) await self.auth_required(
userPhone, self.processors.process_send_message, payload, seq, writer, senderId=userId, db_pool=self.db_pool
)
case self.proto.FOLDERS_GET: case self.proto.FOLDERS_GET:
await self.processors.process_get_folders(payload, seq, writer, senderPhone=userPhone) await self.auth_required(
userPhone, self.processors.process_get_folders, payload, seq, writer, senderPhone=userPhone
)
case self.proto.SESSIONS_INFO: case self.proto.SESSIONS_INFO:
await self.processors.process_get_sessions(payload, seq, writer, senderPhone=userPhone, hashedToken=hashedToken) await self.auth_required(
userPhone, self.processors.process_get_sessions, payload, seq, writer, senderPhone=userPhone, hashedToken=hashedToken
)
case self.proto.CHAT_INFO: case self.proto.CHAT_INFO:
await self.processors.process_search_chats(payload, seq, writer, senderId=userId) await self.auth_required(
userPhone, self.processors.process_search_chats, payload, seq, writer, senderId=userId
)
case self.proto.CONTACT_INFO_BY_PHONE: case self.proto.CONTACT_INFO_BY_PHONE:
await self.processors.process_search_by_phone(payload, seq, writer, senderId=userId) await self.auth_required(
userPhone, self.processors.process_search_by_phone, payload, seq, writer, senderId=userId
)
case self.proto.OK_TOKEN: case self.proto.OK_TOKEN:
await self.processors.process_get_call_token(payload, seq, writer) await self.auth_required(
userPhone, self.processors.process_get_call_token, payload, seq, writer
)
case self.proto.MSG_TYPING: case self.proto.MSG_TYPING:
await self.processors.process_typing(payload, seq, writer, senderId=userId) await self.auth_required(
userPhone, self.processors.process_typing, payload, seq, writer, senderId=userId
)
case self.proto.CONTACT_INFO: case self.proto.CONTACT_INFO:
await self.processors.process_search_users(payload, seq, writer) await self.auth_required(
userPhone, self.processors.process_search_users, payload, seq, writer
)
case self.proto.COMPLAIN_REASONS_GET: case self.proto.COMPLAIN_REASONS_GET:
await self.processors.process_complain_reasons_get(payload, seq, writer) await self.auth_required(
userPhone, self.processors.process_complain_reasons_get, 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

@@ -95,10 +95,8 @@ class Processors:
await cursor.execute("SELECT * FROM users WHERE phone = %s", (phone,)) await cursor.execute("SELECT * FROM users WHERE phone = %s", (phone,))
user = await cursor.fetchone() user = await cursor.fetchone()
if user is None: # Если пользователь существует, сохраняем токен
await self._send_error(seq, self.proto.REQUEST_CODE, self.error_types.USER_NOT_FOUND, writer) if user:
return
# Сохраняем токен # Сохраняем токен
await cursor.execute("INSERT INTO auth_tokens (phone, token_hash, code_hash, expires, state) VALUES (%s, %s, %s, %s, %s)", (phone, token_hash, code_hash, expires, "started",)) await cursor.execute("INSERT INTO auth_tokens (phone, token_hash, code_hash, expires, state) VALUES (%s, %s, %s, %s, %s)", (phone, token_hash, code_hash, expires, "started",))
@@ -162,12 +160,6 @@ class Processors:
# Обновляем состояние токена # Обновляем состояние токена
await cursor.execute("UPDATE auth_tokens set state = %s WHERE token_hash = %s", ("verified", hashed_token,)) await cursor.execute("UPDATE auth_tokens set state = %s WHERE token_hash = %s", ("verified", hashed_token,))
# # Создаем сессию
# await cursor.execute(
# "INSERT INTO tokens (phone, token_hash, device_type, device_name, location, time) VALUES (%s, %s, %s, %s, %s, %s)",
# (stored_token.get("phone"), hashed_login, deviceType, deviceName, "Epstein Island", int(time.time()),)
# )
# Генерируем профиль # Генерируем профиль
# Аватарка с биографией # Аватарка с биографией
photoId = None if not account.get("avatar_id") else int(account.get("avatar_id")) photoId = None if not account.get("avatar_id") else int(account.get("avatar_id"))
@@ -176,7 +168,7 @@ class Processors:
# Собираем данные пакета # Собираем данные пакета
payload = { payload = {
"profile": self.tools.generate_profile( "profile": self.tools.generate_profile_tt(
id=account.get("id"), id=account.get("id"),
phone=int(account.get("phone")), phone=int(account.get("phone")),
avatarUrl=avatar_url, avatarUrl=avatar_url,
@@ -186,12 +178,8 @@ class Processors:
lastName=account.get("lastname"), lastName=account.get("lastname"),
options=json.loads(account.get("options")), options=json.loads(account.get("options")),
description=description, description=description,
accountStatus=int(account.get("accountstatus")), username=account.get("username")
profileOptions=json.loads(account.get("profileoptions")), ),
includeProfileOptions=False,
username=account.get("username"),
type="TT"
).get("contact"),
"tokenAttrs": { "tokenAttrs": {
"AUTH": { "AUTH": {
"token": token "token": token
@@ -241,6 +229,7 @@ class Processors:
await self._send_error(seq, self.proto.VERIFY_CODE, self.error_types.INVALID_TOKEN, writer) await self._send_error(seq, self.proto.VERIFY_CODE, self.error_types.INVALID_TOKEN, writer)
return return
# Если авторизация только началась - отдаем ошибку
if stored_token.get("state") == "started": if stored_token.get("state") == "started":
await self._send_error(seq, self.proto.VERIFY_CODE, self.error_types.INVALID_TOKEN, writer) await self._send_error(seq, self.proto.VERIFY_CODE, self.error_types.INVALID_TOKEN, writer)
return return
@@ -249,7 +238,7 @@ class Processors:
await cursor.execute("SELECT * FROM users WHERE phone = %s", (stored_token.get("phone"),)) await cursor.execute("SELECT * FROM users WHERE phone = %s", (stored_token.get("phone"),))
account = await cursor.fetchone() account = await cursor.fetchone()
# Обновляем состояние токена # Удаляем токен
await cursor.execute("DELETE FROM auth_tokens WHERE token_hash = %s", (hashed_token,)) await cursor.execute("DELETE FROM auth_tokens WHERE token_hash = %s", (hashed_token,))
# Создаем сессию # Создаем сессию
@@ -265,8 +254,8 @@ class Processors:
# Собираем данные пакета # Собираем данные пакета
payload = { payload = {
"userToken": "0", "userToken": "0", # Пока как заглушка
"profile": self.tools.generate_profile( "profile": self.tools.generate_profile_tt(
id=account.get("id"), id=account.get("id"),
phone=int(account.get("phone")), phone=int(account.get("phone")),
avatarUrl=avatar_url, avatarUrl=avatar_url,
@@ -276,18 +265,16 @@ class Processors:
lastName=account.get("lastname"), lastName=account.get("lastname"),
options=json.loads(account.get("options")), options=json.loads(account.get("options")),
description=description, description=description,
accountStatus=int(account.get("accountstatus")), username=account.get("username")
profileOptions=json.loads(account.get("profileoptions")), ),
includeProfileOptions=False,
username=account.get("username"),
type="TT"
).get("contact"),
"tokenType": "LOGIN", "tokenType": "LOGIN",
"token": login "token": login
} }
# Создаем пакет
packet = self.proto.pack_packet( packet = self.proto.pack_packet(
cmd=self.proto.CMD_OK, seq=seq, opcode=self.proto.FINAL_AUTH, payload=payload cmd=self.proto.CMD_OK, seq=seq, opcode=self.proto.FINAL_AUTH, payload=payload
) )
# Отправялем
await self._send(writer, packet) await self._send(writer, packet)

View File

@@ -15,7 +15,10 @@ class Processors:
async def _send(self, writer, packet): async def _send(self, writer, packet):
"""Отправка пакета""" """Отправка пакета"""
try:
await writer.send(packet) await writer.send(packet)
except Exception as error:
self.logger.error(f"Ошибка при отправке пакета - {error}")
async def _send_error(self, seq, opcode, type, writer): async def _send_error(self, seq, opcode, type, writer):
payload = self.static.ERROR_TYPES.get(type, { payload = self.static.ERROR_TYPES.get(type, {
@@ -36,7 +39,8 @@ class Processors:
# Валидируем данные пакета # Валидируем данные пакета
try: try:
HelloPayloadModel.model_validate(payload) HelloPayloadModel.model_validate(payload)
except Exception as e: except pydantic.ValidationError as error:
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
await self._send_error(seq, self.proto.SESSION_INIT, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(seq, self.proto.SESSION_INIT, self.error_types.INVALID_PAYLOAD, writer)
return None, None return None, None

View File

@@ -18,7 +18,7 @@ class Proto:
try: try:
parsed_packet = json.loads(packet) parsed_packet = json.loads(packet)
except: except:
return {} return None
return parsed_packet return parsed_packet
# мне кажется долго вручную всё писать # мне кажется долго вручную всё писать
@@ -36,6 +36,14 @@ class Proto:
# мб найдем че. она без обфускации # мб найдем че. она без обфускации
# а ты ее видишь? # а ты ее видишь?
# пошли # пошли
### Констаты протокола
CMD_OK = 1
CMD_NOF = 2
CMD_ERR = 3
PROTO_VER = 10
### Команды
PING = 1 PING = 1
LOG = 5 LOG = 5
SESSION_INIT = 6 SESSION_INIT = 6

View File

@@ -6,12 +6,13 @@ from tamtam_ws.proto import Proto
from tamtam_ws.processors import Processors from tamtam_ws.processors import Processors
class TTWSServer: class TTWSServer:
def __init__(self, host, port, db_pool=None, clients={}, send_event=None): def __init__(self, host, port, db_pool=None, clients={}, send_event=None, origins=None):
self.host = host self.host = host
self.port = port self.port = port
self.proto = Proto() self.proto = Proto()
self.processors = Processors(db_pool=db_pool, clients=clients, send_event=send_event) self.processors = Processors(db_pool=db_pool, clients=clients, send_event=send_event)
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
self.origins = origins
async def handle_client(self, websocket): async def handle_client(self, websocket):
deviceType = None deviceType = None
@@ -21,11 +22,17 @@ class TTWSServer:
# Распаковываем пакет # Распаковываем пакет
packet = self.proto.unpack_packet(message) packet = self.proto.unpack_packet(message)
# Если ничего не извлекли
if packet is None:
self.logger.error(f"Не удалось распаковать пакет - {message}")
return
# Валидируем структуру пакета # Валидируем структуру пакета
try: try:
MessageModel.model_validate(packet) MessageModel.model_validate(packet)
except ValidationError as e: except ValidationError as error:
self.logger.error(e) self.logger.error(f"Произошла ошибка при валидации структуры пакета: {error}")
return
# Извлекаем данные из пакета # Извлекаем данные из пакета
seq = packet['seq'] seq = packet['seq']
@@ -44,12 +51,6 @@ class TTWSServer:
# УДАЛЯЕМ MYTRACKER ИЗ TAMTAM ТАМ ВИРУС # УДАЛЯЕМ MYTRACKER ИЗ TAMTAM ТАМ ВИРУС
# майтрекер отправляет все ваши сообщения на сервер барака обамы. немедленно удаляем!!! # майтрекер отправляет все ваши сообщения на сервер барака обамы. немедленно удаляем!!!
await self.processors.process_telemetry(payload, seq, websocket) await self.processors.process_telemetry(payload, seq, websocket)
# case self.proto.AUTH_REQUEST:
# await self.processors.process_auth_request(payload, seq, websocket)
# case self.proto.VERIFY_CODE:
# await self.processors.process_verify_code(payload, seq, websocket)
# case self.proto.FINAL_AUTH:
# await self.processors.process_final_auth(payload, seq, websocket, deviceType, deviceName)
# лан я пойду. пока # лан я пойду. пока
# а ок # а ок
@@ -57,5 +58,8 @@ class TTWSServer:
async def start(self): async def start(self):
self.logger.info(f"Вебсокет запущен на порту {self.port}") self.logger.info(f"Вебсокет запущен на порту {self.port}")
async with serve(self.handle_client, self.host, self.port): async with serve(handler=self.handle_client,
host=self.host,
port=self.port,
origins=self.origins):
await asyncio.Future() await asyncio.Future()