mirror of
https://github.com/openmax-server/server.git
synced 2026-07-10 22:10:12 +03:00
fc6ec5eec5
* Различные фиксы (#14)
* Исключаем только ошибку валидации
* Небольшие правки вебсокета тамтама
* Теперь номера брутить чутка сложнее
* Авторизация теперь проверяется для некоторых команд
* Теперь проверяется orign у вебсокета тамтама
* Дополнил пример env
* Починил немного сокет тамтама
* Теперь все должно работать исправно
* Документация: описал про SESSION_INIT
* Update deviceId description in tamtam_ws.md
* feat: 16 опкод для обновления профиля
* feat: 23 опкод для регистрации, смс шлюз, докер
* SQLite: Ошибка
И вообще не юзайте sqlite пожалуйста, он баганный
* Серверный конфиг снова сломался
* Починил изменение профиля
* История сообщений (вроде как даже работает)
* Дополнил немного 48 опкод, и починил отдачу истории в избранном (избранное все равно не работает)
* Доделал авторизацию в ТамТаме
* Швырнул архитектуру, чтобы позже объединить контроллеры веба и сокета в одно, а также разделить процессоры
* Удалил tamtam.proto, поскольку нигде больше не используется
* Поделил процессоры в таме
* Коды стран
* Генерируем айди, за вместо того, чтобы писать его попорядку
* Вынес список участников чата в отдельную таблицу
* Теперь мы не должны отправлять 128 опкод самому себе. вроде
* ТамТам: реализация 32 опкода. Надеюсь, будет работать
* ТамТам: обработчик 5 и 1 опкода
* ТамТам: починил 32 опкод и добавил серверный конфиг. Избранное больше не добавляется в список чатов
MAX: починил 32 опкод
* TT: oops
* MAX: заглушка в 158 опкоде
* Common: поправил 1 прикол
* TamTam: websocket transport for web version
* TamTam && MAX: история (в мохе она вроде теперь получше работает)
* Правка контроллеров небольшая
* MAX: Разделил процессоры
* TG Bot: переписал на aiogram
* Common: фикс года
* Начальная реализация транспорта ws для max web и прочие улучшения
* Common: добавил сжатие lz4
* Исправление конфликтов
* TT: фикс вебсокета
* Fix OpenMAX mobile compatibility and Telegram auth fallback (#30)
* Fix OpenMAX mobile compatibility and Telegram auth fallback
* Common: Убрал скобку в конфиге
---------
Co-authored-by: Alexey Polyakov <starwear3000@mail.ru>
* MAX: заглушка для баннеров, правка пакета со списком жалоб, отдача контактов и прочие улучшения
* oops
* MAX: Рабочие баннеры
* MAX: обновление настроек приватности
* MAX: пуши через firebase (особо не тестил, вроде работает)
* MAX: Рефактор папок
* MAX: фикс версий < 25.8.0
* MAX: отдача черного списка
* Теперь ID пользователей идут по порядку, как и раньше
* MAX: создание папок
* MAX: oops
* MAX: добавление/удаление контактов, статусы
* MAX & TT: теперь в качестве страны локации используется настоящая страна пользователя, а также зафиксировал версии библиотек в зависимостях
* TT: ну вроде шире поддержка, а вообще обратная совместимость с максом клас
* oops
* TG Bot: возможность включения белого списка
* Решение проблемы обработки sigterm
* nothing
* fix
* TG Bot: fix
* TG Bot: fix username
* MAX && TT: общение в таме, и корректировки под веб морду
* MAX: Почистил серверный конфиг
* nothing
* MAX: web fix
* MAX: web fix 2
* oops
* nothing
* MAX: теперь для избранного не сравниваем айди с нулём
* Вынес sqlite в отдельный модуль
* MAX: блокировка и разблокировка контакта
* oops
* TT: тоже самое что в макс
* MAX && TT: обновление контакта
* MAX: добавление контакта по номеру телефона
* MAX: исправление уязвимости с избранными
* Генерируем айди пользователей рандомно (опять, да)
* update sql scheme
* Фикс сборки избранного
* Попытка починить историю (спойлер, нихуя не получилось)
* MAX: история таки заработала — cid/link/reactionInfo обязательны в схеме
Десктопный MAX подключается через TCP (mobile-протокол) и парсит
msgpack по фиксированной схеме. Если в сообщении выпадает любое из
полей — клиент молча обрывает соединение. После 87cfc19 как раз
такие условные `if elements: ...` / `if link: ...` (а link и
reaction_info там всегда были `{}`, то есть falsy) вырезали поля
из ответа CHAT_HISTORY и MSG_SEND, чем и сломали историю.
- src/common/tools.py: новый build_message_dict() — единая сборка
тела сообщения, где все поля (id, cid, time, type, sender, text,
attaches, elements, reactionInfo, link) присутствуют ВСЕГДА.
get_last_message переписан через него.
- src/oneme/processors/history.py: chat_history использует
build_message_dict вместо ручной логики с условными if-ками.
- src/oneme/processors/messages.py: msg_send.bodyMessage теперь
отдает cid / reactionInfo / link даже пустыми и приводит id
к int для mobile, str для web.
Цепная польза: auth.py LOGIN bootstrap (через generate_chats →
get_last_message) и search.py тоже теперь шлют корректную схему.
* MAX: bootstrap-история в LOGIN — клиент перестал думать что всё уже синканулось
В ответе LOGIN сервер слал messages: {} и chatMarker: 0. Десктопный
клиент в этом случае считает, что локальная история уже синхронизирована
со старого запуска, и НЕ отправляет CHAT_HISTORY (49) при открытии чата.
В окне видно только lastMessage из chats[], а вся реальная переписка —
ничерта.
- src/common/tools.py: collect_bootstrap_history(chatIds, ...) —
собирает карту {chatId: [последние N сообщений]}, в т.ч. избранное
под клиентским id = senderId ^ senderId.
- src/oneme/processors/auth.py: подсовываем эту карту в
payload.messages, chatMarker = текущее время вместо 0.
* fix(chat history): фикс
* fix(chat history): фикс
* fix(chat history): фикс
* fix(chat history): фикс
* fix(chat history): фикс
* fix(chat history): фикс
* fix(chat history): фикс
* MAX & TT: теперь полноценный чёрный список
* TT: регистрация через клиент
* Вроде как нормальные заглушки под ассеты
* maybe fixes
---------
Co-authored-by: WowInceptionGood <143893762+WowInceptionGood@users.noreply.github.com>
Co-authored-by: zavolo <me@kirillka.ru>
Co-authored-by: Aleksandr Kosachev <38947781+gohryt@users.noreply.github.com>
167 lines
6.8 KiB
Python
167 lines
6.8 KiB
Python
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):
|
|
"""Обработчик приветствия"""
|
|
# Валидируем данные пакета
|
|
try:
|
|
HelloPayloadModel.model_validate(payload)
|
|
except pydantic.ValidationError as error:
|
|
await self._send_error(seq, self.opcodes.SESSION_INIT,
|
|
self.error_types.INVALID_PAYLOAD, writer)
|
|
return None, None
|
|
|
|
# Получаем данные из пакета
|
|
device_type = payload.get("userAgent").get("deviceType")
|
|
device_name = payload.get("userAgent").get("deviceName")
|
|
|
|
# Данные пакета
|
|
payload = {
|
|
"proxy": "",
|
|
"logs-enabled": False,
|
|
"proxy-domains": [],
|
|
"location": "RU",
|
|
"libh-enabled": False,
|
|
"phone-auto-complete-enabled": False
|
|
}
|
|
|
|
# Собираем пакет
|
|
packet = self.proto.pack_packet(
|
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.SESSION_INIT, payload=payload
|
|
)
|
|
|
|
# Отправляем
|
|
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):
|
|
"""Обработчик пинга"""
|
|
# Валидируем данные пакета
|
|
try:
|
|
PingPayloadModel.model_validate(payload)
|
|
except pydantic.ValidationError as error:
|
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
|
await self._send_error(seq, self.opcodes.PING, self.error_types.INVALID_PAYLOAD, writer)
|
|
return
|
|
|
|
# Собираем пакет
|
|
packet = self.proto.pack_packet(
|
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.PING, payload=None
|
|
)
|
|
|
|
# Отправляем
|
|
await self._send(writer, packet)
|
|
|
|
async def log(self, payload, seq, writer):
|
|
"""Обработчик лога"""
|
|
# Собираем пакет
|
|
packet = self.proto.pack_packet(
|
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.LOG, payload=None
|
|
)
|
|
|
|
# Отправляем
|
|
await self._send(writer, packet) |