diff --git a/src/common/tools.py b/src/common/tools.py index 75a535a..bf51f78 100644 --- a/src/common/tools.py +++ b/src/common/tools.py @@ -419,6 +419,7 @@ class Tools: "attaches": json.loads(row.get("attaches")), "elements": json.loads(row.get("elements")), "reactionInfo": {}, + "link": {} } # Возвращаем diff --git a/src/oneme/processors/history.py b/src/oneme/processors/history.py index d1a540e..970df29 100644 --- a/src/oneme/processors/history.py +++ b/src/oneme/processors/history.py @@ -64,11 +64,13 @@ class HistoryProcessors(BaseProcessor): "time": int(row.get("time")), "type": row.get("type"), "sender": row.get("sender"), + "cid": int(row.get("cid")), "text": row.get("text"), "attaches": json.loads(row.get("attaches")), "elements": json.loads(row.get("elements")), "reactionInfo": {}, - "options": 1, + "link": {}, + #"options": 1, }) if forward > 0: @@ -81,14 +83,17 @@ class HistoryProcessors(BaseProcessor): for row in result: messages.append({ - "id": row.get("id"), + "id": row.get("id") if self.type == 'mobile' else str(row.get('id')), "time": int(row.get("time")), "type": row.get("type"), "sender": row.get("sender"), + "cid": int(row.get("cid")), "text": row.get("text"), "attaches": json.loads(row.get("attaches")), "elements": json.loads(row.get("elements")), - "reactionInfo": {} + "reactionInfo": {}, + "link": {} + #"options": 1, }) # Сортируем сообщения по времени diff --git a/src/tamtam/config.py b/src/tamtam/config.py index 9e4efde..84b78ff 100644 --- a/src/tamtam/config.py +++ b/src/tamtam/config.py @@ -9,319 +9,12 @@ class TTConfig: "googNoiseSuppression": "true", "googHighpassFilter": "false", "googTypingNoiseDetection": "false", - "googAudioNetworkAdaptorConfig": "ChyyARkNCtcjPBUK1yM8GKjDASCw6gEomHUwoJwBCgfKAQQIABAACgvCAQgIqMMBELiRAgosqgEpChEIuBcVzcxMPhjogQIlCtejOxIRCOgHFc3MTD4YsOoBJQrXozsYyAEKC7oBCAiw6gEQoJwB" + "googAudioNetworkAdaptorConfig": "" }, "a-lte": 24, "a-wifi": 34, "account-removal-enabled": False, - "animated-emojis": { - "❤️": { - "reactionAction": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/r/03.json" - }, - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/04.json" - } - }, - "👍": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-29lottie/e/16.json" - }, - "reactionAction": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/r/01_m.json" - } - }, - "👎": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/17.json" - }, - "reactionAction": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/r/02.json" - } - }, - "🙏": { - "reactionAction": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/r/04.json" - }, - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-29lottie/e/30_ng.json" - } - }, - "😘": { - "reactionAction": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/r/05.json" - }, - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/03.json" - } - }, - "🔥": { - "reactionAction": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/r/06.json" - }, - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/10.json" - } - }, - "😂": { - "reactionAction": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/r/07.json" - } - }, - "👏": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-08-14animoji/56.json" - } - }, - "😮": { - "reactionAction": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/r/09.json" - } - }, - "💋": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/13_v02.json" - }, - "reactionAction": { - "url": "https://st.okcdn.ru/static/messages/2023-01-18lottie/r/kissing2.json" - } - }, - "🥂": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/20.json" - }, - "reactionAction": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/r/20.json" - } - }, - "😳": { - "reactionAction": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/r/09.json" - }, - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/02.json" - } - }, - "😔": { - "reactionAction": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/r/11.json" - }, - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/05.json" - } - }, - "😍": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/07.json" - } - }, - "😯": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/08.json" - } - }, - "😉": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/09.json" - } - }, - "🌺": { - "reactionAction": { - "url": "https://st.okcdn.ru/static/messages/2023-03-06lottie/flower.json" - }, - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/11.json" - } - }, - "🎂": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/14.json" - } - }, - "💩": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/15.json" - }, - "reactionAction": { - "url": "https://st.okcdn.ru/static/messages/2023-01-18lottie/r/shit_1.json" - } - }, - "🐰": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/19.json" - } - }, - "🎅": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/21.json" - } - }, - "🎄": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/23.json" - } - }, - "🎆": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/22.json" - } - }, - "❄️": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/25.json" - } - }, - "🎉": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-28lottie/e/12.json" - } - }, - "🥗": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2022-12-29lottie/e/28.json" - } - }, - "🧡": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-07-20animojie/31.json" - } - }, - "💔": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-07-20animojie/32.json" - } - }, - "🎁": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-07-20animojie/34.json" - } - }, - "🌹": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-07-20animojie/35.json" - } - }, - "🌸": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-07-20animojie/36.json" - } - }, - "🍒": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-07-20animojie/37.json" - } - }, - "🥕": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-07-20animojie/39.json" - } - }, - "🍑": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-07-20animojie/40.json" - } - }, - "🍋": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-07-20animojie/41.json" - } - }, - "🍃": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-07-20animojie/42.json" - } - }, - "😺": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-07-20animojie/43.json" - } - }, - "🐶": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-07-20animojie/44.json" - } - }, - "🐽": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-07-20animojie/45.json" - } - }, - "💐": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-08-14animoji/46.json" - } - }, - "🎈": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-08-14animoji/47.json" - } - }, - "🍾": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-08-14animoji/48.json" - } - }, - "⚡": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-08-14animoji/49.json" - } - }, - "⭐": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-08-14animoji/50.json" - } - }, - "✨": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-08-14animoji/51.json" - } - }, - "💃": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-08-14animoji/52.json" - } - }, - "☀️": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-08-14animoji/53.json" - } - }, - "👋": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-08-14animoji/54.json" - } - }, - "☕": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-08-14animoji/57.json" - } - }, - "🙂": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-08-14animoji/58.json" - } - }, - "🤩": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-08-15animoji/59.json" - } - }, - "😇": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-08-14animoji/60.json" - } - }, - "😎": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-08-14animoji/61.json" - } - }, - "🍎": { - "emoji": { - "url": "https://st.okcdn.ru/static/messages/2023-08-14animoji/62.json" - } - } - }, + "animated-emojis": {}, "animated-emojis-limits": { "low": 5, "average": 10, @@ -383,10 +76,10 @@ class TTConfig: "image-quality": 0.800000011920929, "image-size": 40000000, "image-width": 1680, - "invite-header": "Приглашение в ТамТам", - "invite-link": "https://tt.me/starwear", - "invite-long": "Я общаюсь в ТамТам, присоединяйся https://tt.me/starwear", - "invite-short": "Привет! Ставь ТамТам! Жду ответа! https://tt.me/starwear", + "invite-header": "", + "invite-link": "", + "invite-long": "", + "invite-short": "", "keep-connection": 2, "l10n": False, "live-location-enabled": True, @@ -453,25 +146,12 @@ class TTConfig: "profiling-enabled": False, "progress-diff-for-notify": 1, "promo-contact-id": 0, - "promo-recent-contacts": True, - "promo_contact_label": "Белый Маг", - "proxy": "msgproxy.okcdn.ru", - "proxy-domains": [ - "okcdn.ru", - "mycdn.me", - "ok.ru", - "odnoklassniki.ru", - "odkl.ru", - "vk.com", - "userapi.com", - "vkuser.net", - "vkusercdn.ru" - ], - "proxy-exclude": [ - "r.mradx.net", - "ad.mail.ru" - ], - "proxy-rotation": True, + "promo-recent-contacts": False, + "promo_contact_label": "", + "proxy": "", + "proxy-domains": [], + "proxy-exclude": [], + "proxy-rotation": False, "push-alert-timeout": 604800, "push-tracking-enabled": True, "quick-forward-cases": [], @@ -519,24 +199,24 @@ class TTConfig: "TOP" ], "stickers-suggestion-keywords-inline": False, - "support-account": "tt.me/support", + "support-account": "", "support-button-enable": False, "t-ice-reconnect": 15, "t-incoming-call": 40, "t-start-connect": 20, - "tam-emoji-font-url": "https://st.okcdn.ru/static/messages/2022-08-25noto/TamNotoColorEmojiCompat.ttf", + "tam-emoji-font-url": "", "tcp-candidates": False, - "tracer-crash-report-enabled": True, - "tracer-crash-report-host": "https://api-hprof.odkl.ru", - "tracer-crash-send-asap-enabled": True, - "tracer-crash-send-logs-enabled": True, - "tracer-crash-send-threads-dump-enabled": True, + "tracer-crash-report-enabled": False, + "tracer-crash-report-host": "", + "tracer-crash-send-asap-enabled": False, + "tracer-crash-send-logs-enabled": False, + "tracer-crash-send-threads-dump-enabled": False, "tracer-disk-overflow-report-threshold": 3000000000, "tracer-disk-usage-probability": 500, - "tracer-enabled": True, - "tracer-host": "https://api-hprof.odkl.ru", + "tracer-enabled": False, + "tracer-host": "", "tracer-hprof-probability": -1, - "tracer-sampled-conditions": "tag=app_start_ui_freeze_2k;probability=100000;startEvent=app_first_activity_created;interestingEvent=app_freeze;interestingDuration=2000", + "tracer-sampled-conditions": "", "tracer-sampled-duration": 20000, "tracer-systrace-duration": 20000, "tracer-systrace-interesting-duration": 10000, @@ -569,4 +249,4 @@ class TTConfig: "iceServers": [], "has-phone": True, "promo-constructors": [] - } \ No newline at end of file + } diff --git a/src/tamtam/models.py b/src/tamtam/models.py index 63eefdf..1453a12 100644 --- a/src/tamtam/models.py +++ b/src/tamtam/models.py @@ -3,24 +3,24 @@ import pydantic class UserAgentModel(pydantic.BaseModel): deviceType: str appVersion: str - osVersion: str - timezone: str - screen: str + osVersion: str = None + timezone: str = None + screen: str = None pushDeviceType: str = None - locale: str + locale: str = None deviceName: str - deviceLocale: str + deviceLocale: str = None class HelloPayloadModel(pydantic.BaseModel): userAgent: UserAgentModel - deviceId: str + deviceId: str = None class RequestCodePayloadModel(pydantic.BaseModel): phone: str class VerifyCodePayloadModel(pydantic.BaseModel): verifyCode: str - authTokenType: str + authTokenType: str = None token: str class FinalAuthPayloadModel(pydantic.BaseModel): @@ -30,7 +30,7 @@ class FinalAuthPayloadModel(pydantic.BaseModel): token: str class LoginPayloadModel(pydantic.BaseModel): - interactive: bool + interactive: bool = None token: str class SearchUsersPayloadModel(pydantic.BaseModel): @@ -41,4 +41,57 @@ class PingPayloadModel(pydantic.BaseModel): class ChatHistoryPayloadModel(pydantic.BaseModel): chatId: int - backward: int \ No newline at end of file + backward: int + +class UpdateProfilePayloadModel(pydantic.BaseModel): + pass + +class SearchChatsPayloadModel(pydantic.BaseModel): + chatIds: list + +class AssetsPayloadModel(pydantic.BaseModel): + sync: int + type: str = None + userId: int = None + +class GetCallTokenPayloadModel(pydantic.BaseModel): + userId: int + value: str + +class GetCallHistoryPayloadModel(pydantic.BaseModel): + forward: bool + count: int + +class ChatSubscribePayloadModel(pydantic.BaseModel): + chatId: int + subscribe: bool + +class ContactListPayloadModel(pydantic.BaseModel): + status: str + count: int = None + +class ContactPresencePayloadModel(pydantic.BaseModel): + contactIds: list + +class ContactUpdatePayloadModel(pydantic.BaseModel): + action: str + contactId: int + firstName: str + lastName: str = None + +class TypingPayloadModel(pydantic.BaseModel): + chatId: int + type: str = None + +class MessageModel(pydantic.BaseModel): + isLive: bool = None + detectShare: bool = None + elements: list = None + attaches: list = None + cid: int = None + text: str = None + +class SendMessagePayloadModel(pydantic.BaseModel): + userId: int = None + chatId: int = None + message: MessageModel \ No newline at end of file diff --git a/src/tamtam/processors/__init__.py b/src/tamtam/processors/__init__.py index de52007..96432ff 100644 --- a/src/tamtam/processors/__init__.py +++ b/src/tamtam/processors/__init__.py @@ -2,9 +2,21 @@ from .main import MainProcessors from .auth import AuthProcessors from .search import SearchProcessors from .history import HistoryProcessors +from .assets import AssetsProcessors +from .calls import CallsProcessors +from .chats import ChatsProcessors +from .contacts import ContactsProcessors +from .messages import MessagesProcessors +from .sessions import SessionsProcessors class Processors(MainProcessors, AuthProcessors, SearchProcessors, - HistoryProcessors): - pass \ No newline at end of file + HistoryProcessors, + AssetsProcessors, + CallsProcessors, + ChatsProcessors, + ContactsProcessors, + MessagesProcessors, + SessionsProcessors): + pass diff --git a/src/tamtam/processors/assets.py b/src/tamtam/processors/assets.py new file mode 100644 index 0000000..dcce8e1 --- /dev/null +++ b/src/tamtam/processors/assets.py @@ -0,0 +1,34 @@ +import pydantic +import time +from classes.baseprocessor import BaseProcessor +from tamtam.models import AssetsPayloadModel + +class AssetsProcessors(BaseProcessor): + async def assets_update(self, payload, seq, writer): + """Обработчик запроса ассетов клиента на сервере""" + # Валидируем данные пакета + try: + AssetsPayloadModel.model_validate(payload) + except pydantic.ValidationError as error: + self.logger.error(f"Возникли ошибки при валидации пакета: {error}") + await self._send_error(seq, self.opcodes.ASSETS_UPDATE, self.error_types.INVALID_PAYLOAD, writer) + return + + # TODO: сейчас это заглушка, а попозже нужно сделать полноценную реализацию + + # Данные пакета + payload = { + "sync": int(time.time() * 1000), + "stickerSetsUpdates": {}, + "stickersUpdates": {}, + "sections": [], + "stickersOrder": [] + } + + # Собираем пакет + packet = self.proto.pack_packet( + cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.ASSETS_UPDATE, payload=payload + ) + + # Отправляем + await self._send(writer, packet) \ No newline at end of file diff --git a/src/tamtam/processors/auth.py b/src/tamtam/processors/auth.py index aabca77..b93e75c 100644 --- a/src/tamtam/processors/auth.py +++ b/src/tamtam/processors/auth.py @@ -174,6 +174,9 @@ class AuthProcessors(BaseProcessor): if not deviceType: deviceType = payload.get("deviceType") + if not deviceName: + deviceName = "Unknown device" + # Хешируем токен hashed_token = hashlib.sha256(token.encode()).hexdigest() @@ -231,7 +234,9 @@ class AuthProcessors(BaseProcessor): # Собираем данные пакета payload = { - "userToken": "0", # Пока как заглушка + # Я хз че сюда вставлять) + # ребята из одноклассников, может быть вы подскажете? + "userToken": str(account.get("id")), "profile": self.tools.generate_profile_tt( id=account.get("id"), phone=int(account.get("phone")), @@ -338,16 +343,25 @@ class AuthProcessors(BaseProcessor): include_favourites=False ) + # Генерируем список контактов + contacts = await self.tools.collect_user_contacts( + user.get("id"), self.db_pool, self.config.avatar_base_url + ) + + # Собираем статусы контактов + contact_ids = [c.get("id") for c in contacts if c.get("id") is not None] + presence = await self.tools.collect_presence(contact_ids, self.clients, self.db_pool) + # Формируем данные пакета payload = { "profile": profile, "chats": chats, "chatMarker": 0, "messages": {}, - "contacts": [], - "presence": {}, + "contacts": contacts, + "presence": presence, "config": { - "hash": "e5903aa8-0000000000000000-80000106-0000000000000001-00000001-0000000000000000-00000000-2-00000001-0000019c9559d057", + "hash": "0", "server": self.server_config, "user": updated_user_config, "chatFolders": { @@ -371,6 +385,10 @@ class AuthProcessors(BaseProcessor): "time": int(time.time() * 1000) } + # print( + # json.dumps(payload, indent=4) + # ) + # Собираем пакет packet = self.proto.pack_packet( cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.LOGIN, payload=payload @@ -378,4 +396,21 @@ class AuthProcessors(BaseProcessor): # Отправляем await self._send(writer, packet) - return int(user.get("phone")), int(user.get("id")), hashed_token \ No newline at end of file + return int(user.get("phone")), int(user.get("id")), hashed_token + + async def logout(self, seq, writer, hashedToken): + """Обработчик завершения сессии""" + # Удаляем токен из бд + async with self.db_pool.acquire() as conn: + async with conn.cursor() as cursor: + await cursor.execute( + "DELETE FROM tokens WHERE token_hash = %s", (hashedToken,) + ) + + # Создаем пакет + response = self.proto.pack_packet( + cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.LOGOUT, payload=None + ) + + # Отправляем + await self._send(writer, response) \ No newline at end of file diff --git a/src/tamtam/processors/chats.py b/src/tamtam/processors/chats.py new file mode 100644 index 0000000..7a17a6b --- /dev/null +++ b/src/tamtam/processors/chats.py @@ -0,0 +1,20 @@ +import pydantic +from classes.baseprocessor import BaseProcessor +from tamtam.models import ChatSubscribePayloadModel + +class ChatsProcessors(BaseProcessor): + 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) diff --git a/src/tamtam/processors/contacts.py b/src/tamtam/processors/contacts.py new file mode 100644 index 0000000..94b7709 --- /dev/null +++ b/src/tamtam/processors/contacts.py @@ -0,0 +1,188 @@ +import pydantic +import json +import time +from classes.baseprocessor import BaseProcessor +from tamtam.models import ContactListPayloadModel, ContactPresencePayloadModel, ContactUpdatePayloadModel + +class ContactsProcessors(BaseProcessor): + async def contact_list(self, payload, seq, writer, userId): + """Обработчик получения контактов""" + # Валидируем данные пакета + try: + ContactListPayloadModel.model_validate(payload) + except pydantic.ValidationError as error: + self.logger.error(f"Возникли ошибки при валидации пакета: {error}") + await self._send_error(seq, self.opcodes.CONTACT_LIST, self.error_types.INVALID_PAYLOAD, writer) + return + + status = payload.get("status") + count = payload.get("count") + + # Итоговый контакт-лист + contact_list = [] + + if status == "BLOCKED": + # Собираем контакты, которые в черном списке + blocked = [] + + async with self.db_pool.acquire() as conn: + async with conn.cursor() as cursor: + if count: + await cursor.execute( + "SELECT * FROM contacts WHERE owner_id = %s AND is_blocked = TRUE LIMIT %s", + (userId, count), + ) + else: + await cursor.execute( + "SELECT * FROM contacts WHERE owner_id = %s AND is_blocked = TRUE", + (userId,), + ) + rows = await cursor.fetchall() + + for row in rows: + blocked.append( + { + "id": int(row.get("contact_id")), + "firstname": row.get("custom_firstname"), + "lastname": row.get("custom_lastname"), + "blocked": True, + } + ) + + # Генерируем контакт-лист + contact_list = await self.tools.generate_contacts( + blocked, self.db_pool, avatar_base_url=self.config.avatar_base_url + ) + + # Собираем данные пакета + response_payload = { + "contacts": contact_list + } + + # Создаем пакет + packet = self.proto.pack_packet( + seq=seq, opcode=self.opcodes.CONTACT_LIST, payload=response_payload + ) + + # Отправляем пакет + await self._send(writer, packet) + + async def contact_update(self, payload, seq, writer, userId): + """ + Обработчик опкода какого-то там + (их хуй запомнишь, даже в мриме команды помню, бля) + + Отвечает за добавку, удаление, блокировку и разблокировку контакта + """ + # Валидируем данные пакета + try: + ContactUpdatePayloadModel.model_validate(payload) + except pydantic.ValidationError as error: + self.logger.error(f"Возникли ошибки при валидации пакета: {error}") + await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.INVALID_PAYLOAD, writer) + return + + action = payload.get("action") + contactId = payload.get("contactId") + firstName = payload.get("firstName") + lastName = payload.get("lastName", "") + + if action == "ADD": + # Проверяем, существует ли пользователь с таким ID + async with self.db_pool.acquire() as conn: + async with conn.cursor() as cursor: + await cursor.execute("SELECT * FROM users WHERE id = %s", (contactId,)) + user = await cursor.fetchone() + + if not user: + await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.USER_NOT_FOUND, writer) + return + + # Проверяем, не добавлен ли уже контакт + await cursor.execute( + "SELECT * FROM contacts WHERE owner_id = %s AND contact_id = %s", + (userId, contactId) + ) + row = await cursor.fetchone() + + # Если контакта не существует, то можем продолжать, + if not row: + # Добавляем контакт + await cursor.execute( + "INSERT INTO contacts (owner_id, contact_id, custom_firstname, custom_lastname, is_blocked) VALUES (%s, %s, %s, %s, FALSE)", + (userId, contactId, firstName, lastName) + ) + # а если уже существует, отправляем ошибку + else: + await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_ALREADY_EXISTS, writer) + return + + # Генерируем профиль + 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 + str(photoId) + + contact = self.tools.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")), + accountStatus=int(user.get("accountstatus")), + includeProfileOptions=False, + custom_firstname=firstName, + custom_lastname=lastName, + ) + + response_payload = { + "contact": contact + } + + packet = self.proto.pack_packet( + cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONTACT_UPDATE, payload=response_payload + ) + + await self._send(writer, packet) + + elif action == "REMOVE": + # Удаляем контакт + async with self.db_pool.acquire() as conn: + async with conn.cursor() as cursor: + await cursor.execute( + "DELETE FROM contacts WHERE owner_id = %s AND contact_id = %s", + (userId, contactId) + ) + + packet = self.proto.pack_packet( + cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONTACT_UPDATE, payload=None + ) + + await self._send(writer, packet) + + async def contact_presence(self, payload, seq, writer): + """Обработчик получения статуса контактов""" + # Валидируем данные пакета + try: + ContactPresencePayloadModel.model_validate(payload) + except pydantic.ValidationError as error: + self.logger.error(f"Возникли ошибки при валидации пакета: {error}") + await self._send_error(seq, self.opcodes.CONTACT_PRESENCE, self.error_types.INVALID_PAYLOAD, writer) + return + + contact_ids = payload.get("contactIds", []) + now_ms = int(time.time() * 1000) + + presence = await self.tools.collect_presence(contact_ids, self.clients, self.db_pool) + + response_payload = { + "presence": presence, + "time": now_ms + } + + packet = self.proto.pack_packet( + cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONTACT_PRESENCE, payload=response_payload + ) + + await self._send(writer, packet) diff --git a/src/tamtam/processors/folders.py b/src/tamtam/processors/folders.py new file mode 100644 index 0000000..146c430 --- /dev/null +++ b/src/tamtam/processors/folders.py @@ -0,0 +1,133 @@ +import pydantic +import json +import time +from classes.baseprocessor import BaseProcessor +from tamtam.models import SyncFoldersPayloadModel, CreateFolderPayloadModel + +class FoldersProcessors(BaseProcessor): + async def folders_get(self, payload, seq, writer, senderPhone): + """Синхронизация папок с сервером""" + # Валидируем данные пакета + try: + SyncFoldersPayloadModel.model_validate(payload) + except pydantic.ValidationError as error: + self.logger.error(f"Возникли ошибки при валидации пакета: {error}") + await self._send_error(seq, self.opcodes.FOLDERS_GET, self.error_types.INVALID_PAYLOAD, writer) + return + + # Ищем папки в бд + async with self.db_pool.acquire() as conn: + async with conn.cursor() as cursor: + await cursor.execute( + "SELECT id, title, filters, `include`, options, update_time, source_id " + "FROM user_folders WHERE phone = %s ORDER BY sort_order", + (int(senderPhone),) + ) + result_folders = await cursor.fetchall() + + folders = [ + { + "id": folder["id"], + "title": folder["title"], + "filters": json.loads(folder["filters"]), + "include": json.loads(folder["include"]), + "updateTime": folder["update_time"], + "options": json.loads(folder["options"]), + "sourceId": folder["source_id"] + } + for folder in result_folders + ] + + # Создаем данные пакета + payload = { + "folderSync": int(time.time() * 1000), + "folders": folders, + "foldersOrder": [folder["id"] for folder in result_folders], + "allFilterExcludeFolders": [] + } + + # Собираем пакет + packet = self.proto.pack_packet( + cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.FOLDERS_GET, payload=payload + ) + + # Отправляем + await self._send(writer, packet) + + async def folders_update(self, payload, seq, writer, senderPhone): + """Создание папки""" + # Валидируем данные пакета + try: + CreateFolderPayloadModel.model_validate(payload) + except pydantic.ValidationError as error: + self.logger.error(f"Возникли ошибки при валидации пакета: {error}") + await self._send_error(seq, self.opcodes.FOLDERS_UPDATE, self.error_types.INVALID_PAYLOAD, writer) + return + + update_time = int(time.time() * 1000) + + async with self.db_pool.acquire() as conn: + async with conn.cursor() as cursor: + await cursor.execute( + "SELECT COALESCE(MAX(sort_order), -1) as max_order FROM user_folders WHERE phone = %s", + (int(senderPhone),) + ) + row = await cursor.fetchone() + next_order = row["max_order"] + 1 + + # Создаем новую папку + await cursor.execute( + "INSERT INTO user_folders (id, phone, title, filters, `include`, options, source_id, update_time, sort_order) " + "VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)", + ( + payload.get("id"), + int(senderPhone), + payload.get("title"), + json.dumps(payload.get("filters")), + json.dumps(payload.get("include", [])), + json.dumps([]), + 1, + update_time, + next_order, + ) + ) + await conn.commit() + + # Получаем обновленный порядок папок + await cursor.execute( + "SELECT id FROM user_folders WHERE phone = %s ORDER BY sort_order", + (int(senderPhone),) + ) + all_folders = await cursor.fetchall() + + folders_order = [f["id"] for f in all_folders] + + # Формируем данные пакета + response_payload = { + "folder": { + "id": payload.get("id"), + "title": payload.get("title"), + "include": payload.get("include"), + "filters": payload.get("filters"), + "updateTime": update_time, + "options": [], + "sourceId": 1, + }, + "folderSync": update_time, + "foldersOrder": folders_order, + } + + # Формируем пакет + packet = self.proto.pack_packet( + cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.FOLDERS_UPDATE, payload=response_payload + ) + + await self._send(writer, packet) + + # Разработчики протокола, объяснитесь, что за хеш !!! а еще подарите нам способ его формирования + notify_about_hash = self.proto.pack_packet( + cmd=0, seq=1, opcode=self.opcodes.NOTIF_CONFIG, + payload={"config": {"hash": "0"}} + ) + + await self._send(writer, notify_about_hash) \ No newline at end of file diff --git a/src/tamtam/processors/main.py b/src/tamtam/processors/main.py index ee8d69b..e26a9b9 100644 --- a/src/tamtam/processors/main.py +++ b/src/tamtam/processors/main.py @@ -1,6 +1,8 @@ +import json import pydantic from classes.baseprocessor import BaseProcessor from tamtam.models import HelloPayloadModel, PingPayloadModel +from tamtam.models import UpdateProfilePayloadModel class MainProcessors(BaseProcessor): async def session_init(self, payload, seq, writer): @@ -35,6 +37,106 @@ class MainProcessors(BaseProcessor): # Отправляем await self._send(writer, packet) return device_type, device_name + + async def profile(self, payload, seq, writer, userId): + """Обработчик получения/обновления профиля""" + # Валидируем входные данные + try: + UpdateProfilePayloadModel.model_validate(payload) + except pydantic.ValidationError as error: + self.logger.error(f"Возникли ошибки при валидации пакета: {error}") + await self._send_error(seq, self.opcodes.PROFILE, self.error_types.INVALID_PAYLOAD, writer) + return + + # Ищем пользователя в бд + async with self.db_pool.acquire() as conn: + async with conn.cursor() as cursor: + await cursor.execute("SELECT * FROM users WHERE id = %s", (userId,)) + user = await cursor.fetchone() + + # Если пользователь не найден + if not user: + await self._send_error(seq, self.opcodes.PROFILE, self.error_types.USER_NOT_FOUND, writer) + return + + # Аватарка с биографией + photo_id = int(user["avatar_id"]) if user.get("avatar_id") else None + avatar_url = f"{self.config.avatar_base_url}{photo_id}" if photo_id else None + description = 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") + ) + + # Создаем данные пакета + payload = { + "profile": profile + } + + # Собираем пакет + response = self.proto.pack_packet( + cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.PROFILE, payload=payload + ) + + # Отправляем + await self._send(writer, response) + + async def update_config(self, payload, seq, writer, userPhone, hashedToken=None): + """Обработчик обновления настроек и пуш-токена""" + result_payload = None + + if payload.get("pushToken"): + push_token = payload.get("pushToken") + async with self.db_pool.acquire() as conn: + async with conn.cursor() as cursor: + await cursor.execute( + "UPDATE tokens SET push_token = %s WHERE phone = %s AND token_hash = %s", + (push_token, str(userPhone), hashedToken) + ) + elif payload.get("settings") and payload.get("settings").get("user"): + new_settings = payload.get("settings").get("user") + + async with self.db_pool.acquire() as conn: + async with conn.cursor() as cursor: + await cursor.execute( + "SELECT user_config FROM user_data WHERE phone = %s", (userPhone,) + ) + row = await cursor.fetchone() + + if row: + current_config = json.loads(row.get("user_config")) + + for key, value in new_settings.items(): + if key in current_config: + current_config[key] = value + + await cursor.execute( + "UPDATE user_data SET user_config = %s WHERE phone = %s", + (json.dumps(current_config), userPhone) + ) + + result_payload = { + "user": current_config, + "hash": "0" + } + + # Собираем пакет + response = self.proto.pack_packet( + cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONFIG, payload=result_payload + ) + + # Отправляем + await self._send(writer, response) async def ping(self, payload, seq, writer): """Обработчик пинга""" diff --git a/src/tamtam/processors/messages.py b/src/tamtam/processors/messages.py new file mode 100644 index 0000000..67bd6a6 --- /dev/null +++ b/src/tamtam/processors/messages.py @@ -0,0 +1,160 @@ +import pydantic +from classes.baseprocessor import BaseProcessor +from tamtam.models import ( + TypingPayloadModel, + SendMessagePayloadModel +) + +class MessagesProcessors(BaseProcessor): + async def msg_typing(self, payload, seq, writer, senderId): + """Обработчик события печатания""" + # Валидируем данные пакета + try: + TypingPayloadModel.model_validate(payload) + except pydantic.ValidationError as error: + self.logger.error(f"Возникли ошибки при валидации пакета: {error}") + await self._send_error(seq, self.opcodes.MSG_TYPING, self.error_types.INVALID_PAYLOAD, writer) + return + + # Извлекаем данные из пакета + chatId = payload.get("chatId") + type = payload.get("type") or "TYPING" + + # Ищем чат в базе данных + async with self.db_pool.acquire() as conn: + async with conn.cursor() as cursor: + await cursor.execute("SELECT * FROM chats WHERE id = %s", (chatId,)) + chat = await cursor.fetchone() + + # Если чат не найден, отправляем ошибку + if not chat: + await self._send_error(seq, self.opcodes.MSG_TYPING, self.error_types.CHAT_NOT_FOUND, writer) + return + + # Участники чата + participants = await self.tools.get_chat_participants(chatId, self.db_pool) + + # Проверяем, является ли отправитель участником чата + if int(senderId) not in participants: + await self._send_error(seq, self.opcodes.MSG_TYPING, self.error_types.CHAT_NOT_ACCESS, writer) + return + + # Рассылаем событие участникам чата + for participant in participants: + if participant != senderId: + # Если участник не является отправителем, отправляем + await self.event( + participant, + { + "eventType": "typing", + "chatId": chatId, + "type": type, + "userId": senderId, + "writer": writer, + } + ) + + # Создаем пакет + packet = self.proto.pack_packet( + seq=seq, opcode=self.opcodes.MSG_TYPING + ) + + # Отправляем пакет + await self._send(writer, packet) + + async def msg_send(self, payload, seq, writer, senderId, db_pool): + """Функция отправки сообщения""" + # Валидируем данные пакета + try: + SendMessagePayloadModel.model_validate(payload) + except pydantic.ValidationError as error: + self.logger.error(f"Возникли ошибки при валидации пакета: {error}") + await self._send_error(seq, self.opcodes.MSG_SEND, self.error_types.INVALID_PAYLOAD, writer) + return + + # Извлекаем данные из пакета + userId = payload.get("userId") + chatId = payload.get("chatId") + message = payload.get("message") + + elements = message.get("elements") or [] + attaches = message.get("attaches") or [] + cid = message.get("cid") or 0 + text = message.get("text") or "" + + # Вычисляем ID чата по ID пользователя и ID отправителя, + # в случае отсутствия ID чата + if chatId is None: + chatId = userId ^ senderId + + async with db_pool.acquire() as db_connection: + async with db_connection.cursor() as cursor: + await cursor.execute("SELECT * FROM chats WHERE id = %s", (chatId,)) + chat = await cursor.fetchone() + + # Если нет такого чата - выбрасываем ошибку + if not chat: + await self._send_error(seq, self.opcodes.MSG_SEND, self.error_types.CHAT_NOT_FOUND, writer) + return + + # Список участников + participants = await self.tools.get_chat_participants(chatId, db_pool) + + # Проверяем, является ли отправитель участником чата + if int(senderId) not in participants: + await self._send_error(seq, self.opcodes.MSG_SEND, self.error_types.CHAT_NOT_ACCESS, writer) + return + + # Добавляем сообщение в историю + messageId, lastMessageId, messageTime = await self.tools.insert_message( + chatId=chatId, + senderId=senderId, + text=text, + attaches=attaches, + elements=elements, + cid=cid, + type="USER", + db_pool=self.db_pool + ) + + # Готовое тело сообщения + bodyMessage = { + "id": messageId, + "time": messageTime, + "type": "USER", + "sender": senderId, + "cid": cid, + "text": text, + "attaches": attaches, + "elements": elements + } + + # Отправляем событие всем участникам чата + for participant in participants: + await self.event( + participant, + { + "eventType": "new_msg", + "chatId": 0 if chatId == senderId else chatId, + "message": bodyMessage, + "prevMessageId": lastMessageId, + "time": messageTime, + "writer": writer + } + ) + + # Данные пакета + payload = { + "chatId": 0 if chatId == senderId else chatId, + "message": bodyMessage, + "unread": 0, + "mark": messageTime + } + + # Собираем пакет + packet = self.proto.pack_packet( + cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.MSG_SEND, payload=payload + ) + + # Отправляем + await self._send(writer, packet) \ No newline at end of file diff --git a/src/tamtam/processors/search.py b/src/tamtam/processors/search.py index 4dd4f0e..386b261 100644 --- a/src/tamtam/processors/search.py +++ b/src/tamtam/processors/search.py @@ -1,6 +1,7 @@ import json, pydantic from classes.baseprocessor import BaseProcessor from tamtam.models import SearchUsersPayloadModel +from tamtam.models import SearchChatsPayloadModel class SearchProcessors(BaseProcessor): async def contact_info(self, payload, seq, writer): @@ -59,5 +60,61 @@ class SearchProcessors(BaseProcessor): cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONTACT_INFO, payload=payload ) + # Отправляем + await self._send(writer, response) + + async def chat_info(self, payload, seq, writer, senderId): + """Поиск чатов по ID""" + # Валидируем данные пакета + try: + SearchChatsPayloadModel.model_validate(payload) + except pydantic.ValidationError as error: + self.logger.error(f"Возникли ошибки при валидации пакета: {error}") + await self._send_error(seq, self.opcodes.CHAT_INFO, self.error_types.INVALID_PAYLOAD, writer) + return + + # Итоговый список чатов + chats = [] + + # ID чатов, которые нам предстоит найти + chatIds = payload.get("chatIds") + + # Ищем чаты в бд + async with self.db_pool.acquire() as conn: + async with conn.cursor() as cursor: + for chatId in chatIds: + await cursor.execute("SELECT * FROM chats WHERE id = %s", (chatId,)) + chat = await cursor.fetchone() + + if chat: + # Проверяем, является ли пользователь участником чата + participants = await self.tools.get_chat_participants(chatId, self.db_pool) + if int(senderId) not in participants: + continue + + # Получаем последнее сообщение из чата + message, messageTime = await self.tools.get_last_message( + chatId, self.db_pool, protocol_type=self.type + ) + + # Добавляем чат в список + chats.append( + self.tools.generate_chat( + chatId, chat.get("owner"), + chat.get("type"), participants, + message, messageTime + ) + ) + + # Создаем данные пакета + payload = { + "chats": chats + } + + # Собираем пакет + response = self.proto.pack_packet( + cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CHAT_INFO, payload=payload + ) + # Отправляем await self._send(writer, response) \ No newline at end of file diff --git a/src/tamtam/processors/sessions.py b/src/tamtam/processors/sessions.py new file mode 100644 index 0000000..66713d2 --- /dev/null +++ b/src/tamtam/processors/sessions.py @@ -0,0 +1,38 @@ +from classes.baseprocessor import BaseProcessor + +class SessionsProcessors(BaseProcessor): + async def sessions_info(self, payload, seq, writer, senderPhone, hashedToken): + """Получение активных сессий на аккаунте""" + # Готовый список сессий + sessions = [] + + # Ищем сессии в бд + async with self.db_pool.acquire() as conn: + async with conn.cursor() as cursor: + await cursor.execute("SELECT * FROM tokens WHERE phone = %s", (str(senderPhone),)) + user_sessions = await cursor.fetchall() + + # Собираем сессии в список + for session in user_sessions: + sessions.append( + { + "time": int(session.get("time")), + "client": f"TamTam {session.get('device_type')}", + "info": session.get("device_name"), + "location": session.get("location"), + "current": True if session.get("token_hash") == hashedToken else False + } + ) + + # Создаем данные пакета + payload = { + "sessions": sessions + } + + # Создаем пакет + response = self.proto.pack_packet( + cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.SESSIONS_INFO, payload=payload + ) + + # Отправляем + await self._send(writer, response) diff --git a/src/tamtam/socket.py b/src/tamtam/socket.py index 9286dc0..42ae779 100644 --- a/src/tamtam/socket.py +++ b/src/tamtam/socket.py @@ -24,7 +24,7 @@ class TamTamMobile: self.auth_required = Tools().auth_required # rate limiter - self.auth_rate_limiter = RateLimiter(max_attempts=5, window_seconds=60) + self.auth_rate_limiter = RateLimiter(max_attempts=15, window_seconds=60) self.read_timeout = 300 # Таймаут чтения из сокета (секунды) self.max_read_size = 65536 # Максимальный размер данных из сокета @@ -105,18 +105,91 @@ class TamTamMobile: if userPhone: await self._finish_auth(writer, address, userPhone, userId) + case self.opcodes.LOGOUT: + await self.processors.logout( + seq, writer, hashedToken=hashedToken + ) + break case self.opcodes.CONTACT_INFO: await self.auth_required( userPhone, self.processors.contact_info, payload, seq, writer ) + case self.opcodes.CHAT_HISTORY: + await self.auth_required( + userPhone, self.processors.chat_history, payload, seq, writer, userId + ) + case self.opcodes.ASSETS_UPDATE: + await self.auth_required( + userPhone, self.processors.assets_update, payload, seq, writer + ) + case self.opcodes.VIDEO_CHAT_HISTORY: + await self.auth_required( + userPhone, self.processors.video_chat_history, payload, seq, writer + ) + case self.opcodes.MSG_SEND: + await self.auth_required( + userPhone, self.processors.msg_send, payload, seq, writer, userId, self.db_pool + ) + case self.opcodes.MSG_TYPING: + await self.auth_required( + userPhone, self.processors.msg_typing, payload, seq, writer, userId + ) + case self.opcodes.FOLDERS_GET: + await self.auth_required( + userPhone, self.processors.folders_get, payload, seq, writer, userPhone + ) + case self.opcodes.FOLDERS_UPDATE: + await self.auth_required( + userPhone, self.processors.folders_update, payload, seq, writer, userPhone + ) + case self.opcodes.SESSIONS_INFO: + await self.auth_required( + userPhone, self.processors.sessions_info, payload, seq, writer, userPhone, hashedToken + ) + case self.opcodes.CHAT_INFO: + await self.auth_required( + userPhone, self.processors.chat_info, payload, seq, writer, userId + ) + case self.opcodes.OK_TOKEN: + await self.auth_required( + userPhone, self.processors.ok_token, payload, seq, writer + ) + case self.opcodes.CONTACT_LIST: + await self.auth_required( + userPhone, self.processors.contact_list, payload, seq, writer, userId + ) + case self.opcodes.PROFILE: + await self.processors.profile( + payload, seq, writer, userId=userId + ) + case self.opcodes.CHAT_SUBSCRIBE: + await self.auth_required( + userPhone, self.processors.chat_subscribe, payload, seq, writer + ) + case self.opcodes.CONFIG: + await self.auth_required( + userPhone, self.processors.update_config, payload, seq, writer, userPhone, hashedToken + ) + case self.opcodes.CONTACT_UPDATE: + await self.auth_required( + userPhone, self.processors.contact_update, payload, seq, writer, userId + ) + case self.opcodes.CONTACT_PRESENCE: + await self.auth_required( + userPhone, self.processors.contact_presence, payload, seq, writer + ) case _: self.logger.warning(f"Неизвестный опкод {opcode}") except Exception as e: self.logger.error(f"Произошла ошибка при работе с клиентом {address[0]}:{address[1]}: {e}") traceback.print_exc() + # Удаляем клиента из словаря при отключении + if userId: + await self._end_session(userId, address[0], address[1]) + writer.close() - self.logger.info(f"Прекратил работать работать с клиентом {address[0]}:{address[1]}") + self.logger.info(f"Прекратил работать с клиентом {address[0]}:{address[1]}") async def _finish_auth(self, writer, addr, phone, id): """Завершение открытия сессии""" diff --git a/src/tamtam/websocket.py b/src/tamtam/websocket.py index 774ec5d..64e742d 100644 --- a/src/tamtam/websocket.py +++ b/src/tamtam/websocket.py @@ -91,6 +91,11 @@ class TamTamWS: if userPhone: await self._finish_auth(websocket, address, userPhone, userId) + case self.opcodes.LOGOUT: + await self.processors.logout( + seq, websocket, hashedToken=hashedToken + ) + break case self.opcodes.CONTACT_INFO: await self.auth_required( userPhone, self.processors.contact_info, payload, seq, websocket @@ -99,6 +104,66 @@ class TamTamWS: await self.auth_required( userPhone, self.processors.chat_history, payload, seq, websocket, userId ) + case self.opcodes.ASSETS_UPDATE: + await self.auth_required( + userPhone, self.processors.assets_update, payload, seq, websocket + ) + case self.opcodes.VIDEO_CHAT_HISTORY: + await self.auth_required( + userPhone, self.processors.video_chat_history, payload, seq, websocket + ) + case self.opcodes.MSG_SEND: + await self.auth_required( + userPhone, self.processors.msg_send, payload, seq, websocket, userId, self.db_pool + ) + case self.opcodes.MSG_TYPING: + await self.auth_required( + userPhone, self.processors.msg_typing, payload, seq, websocket, userId + ) + case self.opcodes.FOLDERS_GET: + await self.auth_required( + userPhone, self.processors.folders_get, payload, seq, websocket, userPhone + ) + case self.opcodes.FOLDERS_UPDATE: + await self.auth_required( + userPhone, self.processors.folders_update, payload, seq, websocket, userPhone + ) + case self.opcodes.SESSIONS_INFO: + await self.auth_required( + userPhone, self.processors.sessions_info, payload, seq, websocket, userPhone, hashedToken + ) + case self.opcodes.CHAT_INFO: + await self.auth_required( + userPhone, self.processors.chat_info, payload, seq, websocket, userId + ) + case self.opcodes.OK_TOKEN: + await self.auth_required( + userPhone, self.processors.ok_token, payload, seq, websocket + ) + case self.opcodes.CONTACT_LIST: + await self.auth_required( + userPhone, self.processors.contact_list, payload, seq, websocket, userId + ) + case self.opcodes.PROFILE: + await self.processors.profile( + payload, seq, websocket, userId=userId + ) + case self.opcodes.CHAT_SUBSCRIBE: + await self.auth_required( + userPhone, self.processors.chat_subscribe, payload, seq, websocket + ) + case self.opcodes.CONFIG: + await self.auth_required( + userPhone, self.processors.update_config, payload, seq, websocket, userPhone, hashedToken + ) + case self.opcodes.CONTACT_UPDATE: + await self.auth_required( + userPhone, self.processors.contact_update, payload, seq, websocket, userId + ) + case self.opcodes.CONTACT_PRESENCE: + await self.auth_required( + userPhone, self.processors.contact_presence, payload, seq, websocket + ) case _: self.logger.warning(f"Неизвестный опкод {opcode}") except websockets.exceptions.ConnectionClosed: