Compare commits

...

5 Commits

Author SHA1 Message Date
Alexey Polyakov
810d480dbd MAX: фикс версий < 25.8.0 2026-04-24 21:30:02 +03:00
Alexey Polyakov
227f90c3c3 MAX: Рефактор папок 2026-04-24 20:54:28 +03:00
Alexey Polyakov
56133416e3 MAX: пуши через firebase (особо не тестил, вроде работает) 2026-04-24 19:46:08 +03:00
Alexey Polyakov
35a4101608 MAX: обновление настроек приватности 2026-04-24 17:17:33 +03:00
Alexey Polyakov
9fcba1af86 MAX: Рабочие баннеры 2026-04-24 15:51:01 +03:00
18 changed files with 419 additions and 72 deletions

View File

@@ -26,4 +26,5 @@ 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" origins="http://127.0.0.1,https://web.openmax.su"
sms_gateway_url = "http://127.0.0.1:8100/sms-gateway" sms_gateway_url = "http://127.0.0.1:8100/sms-gateway"
firebase_credentials_path = ""

3
.gitignore vendored
View File

@@ -2,4 +2,5 @@ __pycache__
.env .env
*.pem *.pem
*.sqlite *.sqlite
*.crt *.crt
*-adminsdk-*.json

View File

@@ -22,3 +22,26 @@
2. Открываем консоль в той же директории и производим декомпиляцию: `apktool d <имя apk> -o max` 2. Открываем консоль в той же директории и производим декомпиляцию: `apktool d <имя apk> -o max`
3. Заходим в папку проекта и заменяем во всех классах "api.oneme.ru" на свой адрес сервера 3. Заходим в папку проекта и заменяем во всех классах "api.oneme.ru" на свой адрес сервера
4. Производим повторную сборку с помощью команды: `apktool b max -o max_modified.apk` 4. Производим повторную сборку с помощью команды: `apktool b max -o max_modified.apk`
---
# Патчинг Firebase для push-уведомлений
> [!Important]
> Без замены Firebase-конфига пуши от вашего сервера не будут работать.
1. Создайте проект в [Firebase Console](https://console.firebase.google.com/) и добавьте Android-приложение с пакетом `ru.oneme.app`
2. Скачайте `google-services.json`
3. В декомпилированном APK откройте `res/values/strings.xml` и замените следующие строки на значения из вашего `google-services.json`:
| Строка | Оригинал | Откуда взять |
|---|---|---|
| `google_api_key` | `AIzaSyABuDYeeDXIOrKTXLkUj30Ii143ofPe63Q` | `client[0].api_key[0].current_key` |
| `google_app_id` | `1:659634599081:android:9605285443b661167225b8` | `client[0].client_info.mobilesdk_app_id` |
| `gcm_defaultSenderId` | `659634599081` | `project_info.project_number` |
| `project_id` | `max-messenger-app` | `project_info.project_id` |
| `google_crash_reporting_api_key` | `AIzaSyABuDYeeDXIOrKTXLkUj30Ii143ofPe63Q` | `client[0].api_key[0].current_key` |
| `google_storage_bucket` | `max-messenger-app.firebasestorage.app` | `project_info.storage_bucket` |
4. Соберите и подпишите APK
5. В настройках проекта Firebase создайте сервисный аккаунт и укажите путь в `.env`

View File

@@ -7,4 +7,5 @@ pydantic
aiosqlite aiosqlite
aiohttp aiohttp
python-dotenv python-dotenv
cryptography cryptography
firebase-admin

View File

@@ -51,3 +51,6 @@ class ServerConfig:
### sms шлюз ### sms шлюз
sms_gateway_url = os.getenv("sms_gateway_url", "") sms_gateway_url = os.getenv("sms_gateway_url", "")
### Firebase
firebase_credentials_path = os.getenv("firebase_credentials_path", "")

92
src/common/push.py Normal file
View File

@@ -0,0 +1,92 @@
import asyncio
import logging
import time
import firebase_admin
from firebase_admin import credentials, messaging
class PushService:
def __init__(self, credentials_path):
self.logger = logging.getLogger(__name__)
if not credentials_path:
self.logger.warning("Огненная база сегодня не работает, укажите путь к файлу с ключами")
self.enabled = False
return
cred = credentials.Certificate(credentials_path)
firebase_admin.initialize_app(cred)
self.enabled = True
self.logger.info("Огненная база инициализирована")
async def send(self, push_token, data):
"""Отправка пуша"""
if not self.enabled:
return None
str_data = {k: str(v) for k, v in data.items() if v is not None}
message = messaging.Message(
data=str_data,
token=push_token,
android=messaging.AndroidConfig(
priority="high",
),
)
try:
loop = asyncio.get_event_loop()
response = await loop.run_in_executor(None, messaging.send, message)
self.logger.debug(f"Отправил пуш: {response}")
return response
except messaging.UnregisteredError:
self.logger.warning(f"Пуш-токен не зарегистрирован: {push_token}")
return None
except Exception as e:
self.logger.error(f"Не удалось отправить пуш: {e}")
return None
async def send_to_user(self, db_pool, phone, sender_id=None, msg_id=None,
chat_id=None, text="", is_group=False):
"""Отправка пушей на все устройства пользователя"""
if not self.enabled:
return
# Получаем имя отправителя
user_name = ""
if sender_id:
async with db_pool.acquire() as conn:
async with conn.cursor() as cursor:
await cursor.execute(
"SELECT firstname, lastname FROM users WHERE id = %s",
(sender_id,)
)
sender = await cursor.fetchone()
if sender:
firstname = sender.get("firstname", "")
lastname = sender.get("lastname", "")
user_name = f"{firstname} {lastname}".strip()
now_ms = str(int(time.time() * 1000))
msg_type = "ChatMessage" if is_group else "Message"
data = {
"type": msg_type,
"msgid": str(msg_id) if msg_id else "0",
"suid": str(sender_id) if sender_id else None,
"mc": str(chat_id) if chat_id else None,
"msg": text,
"userName": user_name,
"ttime": now_ms,
"ctime": now_ms,
}
# Получаем все пуш-токены пользователя
async with db_pool.acquire() as conn:
async with conn.cursor() as cursor:
await cursor.execute(
"SELECT push_token FROM tokens WHERE phone = %s AND push_token IS NOT NULL",
(phone,)
)
rows = await cursor.fetchall()
for row in rows:
await self.send(row["push_token"], data)

View File

@@ -13,6 +13,12 @@ class SQLQueries:
INSERT_USER_DATA = """ INSERT_USER_DATA = """
INSERT INTO user_data INSERT INTO user_data
(phone, folders, user_config, chat_config) (phone, user_config, chat_config)
VALUES (%s, %s, %s, %s) VALUES (%s, %s, %s)
""" """
INSERT_DEFAULT_FOLDER = """
INSERT INTO user_folders
(id, phone, title, sort_order)
VALUES ('all.chat.folder', %s, 'Все', 0)
"""

View File

@@ -196,25 +196,6 @@ class Static:
]}, ]},
] ]
### Заглушка для папок
ALL_CHAT_FOLDER = [{
"id": "all.chat.folder",
"title": "Все",
"filters": [],
"updateTime": 0,
"options": [],
"sourceId": 1
}]
ALL_CHAT_FOLDER_ORDER = ["all.chat.folder"]
### Стандартные папки с настройками пользователя
USER_FOLDERS = {
"folders": [],
"foldersOrder": [],
"allFilterExcludeFolders": []
}
USER_SETTINGS = { USER_SETTINGS = {
"CHATS_PUSH_NOTIFICATION": "ON", "CHATS_PUSH_NOTIFICATION": "ON",
"PUSH_DETAILS": True, "PUSH_DETAILS": True,

View File

@@ -503,3 +503,17 @@ class Tools:
# Возвращаем # Возвращаем
return unique_id return unique_id
async def update_user_config(self, cursor, phone, user_settings, default_settings):
"""Функция для обновления юзер конфига из бд в случае его изменения"""
user_config = json.loads(user_settings)
updated_config = {**default_settings, **user_config}
if updated_config != user_config:
await cursor.execute(
"UPDATE user_data SET user_config = %s WHERE phone = %s",
(json.dumps(updated_config), phone),
)
return updated_config

View File

@@ -4,6 +4,7 @@ import logging
import ssl import ssl
from common.config import ServerConfig from common.config import ServerConfig
from common.push import PushService
from oneme.controller import OnemeController from oneme.controller import OnemeController
from tamtam.controller import TTController from tamtam.controller import TTController
from telegrambot.controller import TelegramBotController from telegrambot.controller import TelegramBotController
@@ -130,14 +131,33 @@ def set_logging():
async def main(): async def main():
"""Запуск сервера""" """Запуск сервера"""
async def api_event(target, eventData):
for client in api.get("clients", {}).get(target, {}).get("clients", {}):
await controllers[client["protocol"]].event(target, client, eventData)
set_logging() set_logging()
db = await init_db() db = await init_db()
ssl_context = init_ssl() ssl_context = init_ssl()
clients = {} clients = {}
push_service = PushService(server_config.firebase_credentials_path)
async def api_event(target, eventData):
target_clients = api.get("clients", {}).get(target, {}).get("clients", [])
for client in target_clients:
await controllers[client["protocol"]].event(target, client, eventData)
# Если у пользователя нет активных подключений
# и это новое сообщение - отсылаем пуш
if not target_clients and eventData.get("eventType") == "new_msg":
message = eventData.get("message", {})
sender_id = message.get("sender")
text = message.get("text", "")
chat_id = eventData.get("chatId", "")
msg_id = message.get("id", 0)
await push_service.send_to_user(
db, target,
sender_id=sender_id,
msg_id=msg_id,
chat_id=chat_id,
text=text,
)
api = { api = {
"db": db, "db": db,

View File

@@ -16,7 +16,6 @@ from oneme.models import (
VerifyCodePayloadModel, VerifyCodePayloadModel,
) )
class AuthProcessors(BaseProcessor): class AuthProcessors(BaseProcessor):
def __init__( def __init__(
self, self,
@@ -30,17 +29,60 @@ class AuthProcessors(BaseProcessor):
self.server_config = OnemeConfig().SERVER_CONFIG self.server_config = OnemeConfig().SERVER_CONFIG
self.telegram_bot = telegram_bot self.telegram_bot = telegram_bot
def _check_legacy_version(self, app_version):
"""
Функция определения легаси версий клиентов,
для которых потребуются некоторые корректировки ответов сервера
Сейчас данная функция используется для форматирования ответов
под версии ниже 25.8.0
Функция вернет True, если версия слишком старая,
или False в противном случае
[fun fact] С 25.8.0, похоже, начали корректировать протокол, поскольку это
самая старая версия, которая работала без всяких корректировок сервера
"""
return tuple(int(v) for v in app_version.split(".")) < (25, 8, 0)
async def _send_banners(self, writer): async def _send_banners(self, writer):
"""Функция отправки баннеров клиенту""" """Функция отправки баннеров клиенту"""
# Итоговый список баннеров для отдачи клиенту
banners = []
async with self.db_pool.acquire() as conn:
async with conn.cursor() as cursor:
# Собираем все баннеры, которые есть в бд
await cursor.execute(
"SELECT * FROM banners WHERE enabled = TRUE"
)
rows = await cursor.fetchall()
# Добавляем каждый баннер в лист
for row in rows:
banner = {
"description": row.get("description"),
"title": row.get("title"),
"priority": row.get("priority"),
"type": row.get("type"),
"hideCloseButton": bool(row.get("hide_close_button")),
"rerun": row.get("rerun"),
"url": row.get("url"),
"animojiId": row.get("animoji_id"),
"repeat": row.get("repeat"),
"hideOnClick": bool(row.get("hide_on_click")),
"id": row.get("id"),
"isTitleAnimated": bool(row.get("is_title_animated")),
}
banners.append(banner)
# Собираем данные пакета
payload = { payload = {
"showTime": 86400000, # Сколько будет показываться баннер, тут сутки в миллисекундах "showTime": 86400000,
# можно в будущем переделать, и сделать выбор в конфигурации
# думаю, было бы прикольно
"updateTime": int(time.time() * 1000), "updateTime": int(time.time() * 1000),
"banners": [ "banners": banners
# TODO: разобраться как работают баннеры и их реализовать
# думаю админам инстансов было бы прикольно, и нам
]
} }
# Собираем пакет # Собираем пакет
@@ -155,7 +197,7 @@ class AuthProcessors(BaseProcessor):
await self._send(writer, packet) await self._send(writer, packet)
self.logger.debug(f"Код для {phone}: {code} (существующий={user_exists})") self.logger.debug(f"Код для {phone}: {code} (существующий={user_exists})")
async def auth(self, payload, seq, writer, deviceType, deviceName): async def auth(self, payload, seq, writer, deviceType, deviceName, appVersion):
"""Обработчик проверки кода""" """Обработчик проверки кода"""
try: try:
VerifyCodePayloadModel.model_validate(payload) VerifyCodePayloadModel.model_validate(payload)
@@ -258,6 +300,11 @@ class AuthProcessors(BaseProcessor):
None if not account.get("description") else account.get("description") None if not account.get("description") else account.get("description")
) )
if self._check_legacy_version(appVersion):
include_profile_options = False
else:
include_profile_options = True
# Собираем данные пакета # Собираем данные пакета
payload = { payload = {
"tokenAttrs": {"LOGIN": {"token": login}}, "tokenAttrs": {"LOGIN": {"token": login}},
@@ -273,7 +320,7 @@ class AuthProcessors(BaseProcessor):
description=description, description=description,
accountStatus=int(account.get("accountstatus")), accountStatus=int(account.get("accountstatus")),
profileOptions=json.loads(account.get("profileoptions")), profileOptions=json.loads(account.get("profileoptions")),
includeProfileOptions=True, includeProfileOptions=include_profile_options,
username=account.get("username"), username=account.get("username"),
), ),
} }
@@ -286,7 +333,7 @@ class AuthProcessors(BaseProcessor):
# Отправляем # Отправляем
await self._send(writer, packet) await self._send(writer, packet)
async def auth_confirm(self, payload, seq, writer, deviceType, deviceName): async def auth_confirm(self, payload, seq, writer, deviceType, deviceName, appVersion):
"""Обработчик подтверждения регистрации нового пользователя""" """Обработчик подтверждения регистрации нового пользователя"""
# Валидируем данные пакета # Валидируем данные пакета
try: try:
@@ -377,17 +424,26 @@ class AuthProcessors(BaseProcessor):
await cursor.execute( await cursor.execute(
""" """
INSERT INTO user_data INSERT INTO user_data
(phone, folders, user_config, chat_config) (phone, user_config, chat_config)
VALUES (%s, %s, %s, %s) VALUES (%s, %s, %s)
""", """,
( (
phone, phone,
json.dumps(self.static.USER_FOLDERS),
json.dumps(self.static.USER_SETTINGS), json.dumps(self.static.USER_SETTINGS),
json.dumps({}), json.dumps({}),
), ),
) )
# Добавляем дефолтную папку
await cursor.execute(
"""
INSERT INTO user_folders
(id, phone, title, sort_order)
VALUES ('all.chat.folder', %s, 'Все', 0)
""",
(phone,),
)
# Удаляем токен # Удаляем токен
await cursor.execute( await cursor.execute(
"DELETE FROM auth_tokens WHERE token_hash = %s", (hashed_token,) "DELETE FROM auth_tokens WHERE token_hash = %s", (hashed_token,)
@@ -406,6 +462,11 @@ class AuthProcessors(BaseProcessor):
), ),
) )
if self._check_legacy_version(appVersion):
include_profile_options = False
else:
include_profile_options = True
# Генерируем профиль # Генерируем профиль
profile = self.tools.generate_profile( profile = self.tools.generate_profile(
id=user_id, id=user_id,
@@ -419,7 +480,7 @@ class AuthProcessors(BaseProcessor):
description=None, description=None,
accountStatus=0, accountStatus=0,
profileOptions=[], profileOptions=[],
includeProfileOptions=True, includeProfileOptions=include_profile_options,
username=None, username=None,
) )
@@ -445,7 +506,7 @@ class AuthProcessors(BaseProcessor):
f"Новый пользователь зарегистрирован: phone={phone} id={user_id} name={first_name} {last_name}" f"Новый пользователь зарегистрирован: phone={phone} id={user_id} name={first_name} {last_name}"
) )
async def login(self, payload, seq, writer): async def login(self, payload, seq, writer, appVersion):
"""Обработчик авторизации клиента на сервере""" """Обработчик авторизации клиента на сервере"""
# Валидируем данные пакета # Валидируем данные пакета
try: try:
@@ -504,11 +565,22 @@ class AuthProcessors(BaseProcessor):
for chat in user_chats: for chat in user_chats:
chats.append(chat.get("chat_id")) chats.append(chat.get("chat_id"))
# Обновляем юзер конфиг
updated_user_config = await self.tools.update_user_config(
cursor, token_data.get("phone"),
user_data.get("user_config"), self.static.USER_SETTINGS
)
# Аватарка с биографией # Аватарка с биографией
photoId = None if not user.get("avatar_id") else int(user.get("avatar_id")) 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 + photoId avatar_url = None if not photoId else self.config.avatar_base_url + photoId
description = None if not user.get("description") else user.get("description") description = None if not user.get("description") else user.get("description")
if self._check_legacy_version(appVersion):
include_profile_options = False
else:
include_profile_options = True
# Генерируем профиль # Генерируем профиль
profile = self.tools.generate_profile( profile = self.tools.generate_profile(
id=user.get("id"), id=user.get("id"),
@@ -522,7 +594,7 @@ class AuthProcessors(BaseProcessor):
description=description, description=description,
accountStatus=int(user.get("accountstatus")), accountStatus=int(user.get("accountstatus")),
profileOptions=json.loads(user.get("profileoptions")), profileOptions=json.loads(user.get("profileoptions")),
includeProfileOptions=True, includeProfileOptions=include_profile_options,
username=user.get("username"), username=user.get("username"),
) )
@@ -546,7 +618,7 @@ class AuthProcessors(BaseProcessor):
"presence": {}, "presence": {},
"config": { "config": {
"server": self.server_config, "server": self.server_config,
"user": json.loads(user_data.get("user_config")), "user": updated_user_config,
}, },
"token": token, "token": token,
"videoChatHistory": False, "videoChatHistory": False,
@@ -558,10 +630,6 @@ class AuthProcessors(BaseProcessor):
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.LOGIN, payload=payload cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.LOGIN, payload=payload
) )
# print(
# json.dumps(payload, indent=4)
# )
# Отправляем # Отправляем
await self._send(writer, packet) await self._send(writer, packet)

View File

@@ -18,16 +18,31 @@ class FoldersProcessors(BaseProcessor):
# Ищем папки в бд # Ищем папки в бд
async with self.db_pool.acquire() as conn: async with self.db_pool.acquire() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute("SELECT folders FROM user_data WHERE phone = %s", (int(senderPhone),)) await cursor.execute(
result_folders = await cursor.fetchone() "SELECT id, title, filters, options, update_time, source_id "
user_folders = json.loads(result_folders.get("folders")) "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"]),
"updateTime": folder["update_time"],
"options": json.loads(folder["options"]),
"sourceId": folder["source_id"],
}
for folder in result_folders
]
# Создаем данные пакета # Создаем данные пакета
payload = { payload = {
"folderSync": int(time.time() * 1000), "folderSync": int(time.time() * 1000),
"folders": self.static.ALL_CHAT_FOLDER + user_folders.get("folders"), "folders": folders,
"foldersOrder": self.static.ALL_CHAT_FOLDER_ORDER + user_folders.get("foldersOrder"), "foldersOrder": [folder["id"] for folder in result_folders],
"allFilterExcludeFolders": user_folders.get("allFilterExcludeFolders") "allFilterExcludeFolders": []
} }
# Собираем пакет # Собираем пакет

View File

@@ -20,11 +20,13 @@ class MainProcessors(BaseProcessor):
except pydantic.ValidationError as error: except pydantic.ValidationError as error:
self.logger.error(f"Возникли ошибки при валидации пакета: {error}") self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
await self._send_error(seq, self.opcodes.SESSION_INIT, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(seq, self.opcodes.SESSION_INIT, self.error_types.INVALID_PAYLOAD, writer)
return None, None return None, None, None
# Получаем данные из пакета # Получаем данные из пакета
deviceType = payload.get("userAgent").get("deviceType") userAgent = payload.get("userAgent")
deviceName = payload.get("userAgent").get("deviceName") deviceType = userAgent.get("deviceType")
deviceName = userAgent.get("deviceName")
appVersion = userAgent.get("appVersion")
# Данные пакета # Данные пакета
payload = { payload = {
@@ -43,7 +45,7 @@ class MainProcessors(BaseProcessor):
# Отправляем # Отправляем
await self._send(writer, packet) await self._send(writer, packet)
return deviceType, deviceName return deviceType, deviceName, appVersion
async def ping(self, payload, seq, writer): async def ping(self, payload, seq, writer):
"""Обработчик пинга""" """Обработчик пинга"""
@@ -127,5 +129,62 @@ class MainProcessors(BaseProcessor):
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.PROFILE, payload=payload 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):
"""
Обработчик 22 опкода (config)
Он отвечает за обновление настроек приватности
и пуш токена для пушей
"""
# Пейлоад, который отдадим клиенту
# а отдавать его нужно только при изменении настроек приватности
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) await self._send(writer, response)

View File

@@ -45,6 +45,7 @@ class OnemeMobile:
deviceType = None deviceType = None
deviceName = None deviceName = None
appVersion = None
userPhone = None userPhone = None
userId = None userId = None
@@ -89,7 +90,7 @@ class OnemeMobile:
match opcode: match opcode:
case self.opcodes.SESSION_INIT: case self.opcodes.SESSION_INIT:
deviceType, deviceName = await self.processors.session_init( deviceType, deviceName, appVersion = await self.processors.session_init(
payload, seq, writer payload, seq, writer
) )
case self.opcodes.AUTH_REQUEST: case self.opcodes.AUTH_REQUEST:
@@ -112,7 +113,7 @@ class OnemeMobile:
) )
else: else:
await self.processors.auth( await self.processors.auth(
payload, seq, writer, deviceType, deviceName payload, seq, writer, deviceType, deviceName, appVersion
) )
case self.opcodes.AUTH_CONFIRM: case self.opcodes.AUTH_CONFIRM:
if not self.auth_rate_limiter.is_allowed(address[0]): if not self.auth_rate_limiter.is_allowed(address[0]):
@@ -124,7 +125,7 @@ class OnemeMobile:
) )
elif payload and payload.get("tokenType") == "REGISTER": elif payload and payload.get("tokenType") == "REGISTER":
await self.processors.auth_confirm( await self.processors.auth_confirm(
payload, seq, writer, deviceType, deviceName payload, seq, writer, deviceType, deviceName, appVersion
) )
else: else:
self.logger.warning( self.logger.warning(
@@ -143,7 +144,7 @@ class OnemeMobile:
userPhone, userPhone,
userId, userId,
hashedToken, hashedToken,
) = await self.processors.login(payload, seq, writer) ) = await self.processors.login(payload, seq, writer, appVersion)
if userPhone: if userPhone:
await self._finish_auth( await self._finish_auth(
@@ -272,6 +273,16 @@ class OnemeMobile:
seq, seq,
writer, writer,
) )
case self.opcodes.CONFIG:
await self.auth_required(
userPhone,
self.processors.update_config,
payload,
seq,
writer,
userPhone,
hashedToken,
)
case _: case _:
self.logger.warning(f"Неизвестный опкод {opcode}") self.logger.warning(f"Неизвестный опкод {opcode}")
except Exception as e: except Exception as e:

View File

@@ -37,6 +37,7 @@ class OnemeWS:
deviceType = None deviceType = None
deviceName = None deviceName = None
appVersion = None
userPhone = None userPhone = None
userId = None userId = None
@@ -63,7 +64,7 @@ class OnemeWS:
match opcode: match opcode:
case self.opcodes.SESSION_INIT: case self.opcodes.SESSION_INIT:
deviceType, deviceName = await self.processors.session_init( deviceType, deviceName, appVersion = await self.processors.session_init(
payload, seq, websocket payload, seq, websocket
) )
case self.opcodes.AUTH_REQUEST: case self.opcodes.AUTH_REQUEST:
@@ -86,7 +87,7 @@ class OnemeWS:
) )
else: else:
await self.processors.auth( await self.processors.auth(
payload, seq, websocket, deviceType, deviceName payload, seq, websocket, deviceType, deviceName, appVersion
) )
case self.opcodes.AUTH_CONFIRM: case self.opcodes.AUTH_CONFIRM:
if not self.auth_rate_limiter.is_allowed(address[0]): if not self.auth_rate_limiter.is_allowed(address[0]):
@@ -98,7 +99,7 @@ class OnemeWS:
) )
elif payload and payload.get("tokenType") == "REGISTER": elif payload and payload.get("tokenType") == "REGISTER":
await self.processors.auth_confirm( await self.processors.auth_confirm(
payload, seq, websocket, deviceType, deviceName payload, seq, websocket, deviceType, deviceName, appVersion
) )
else: else:
self.logger.warning( self.logger.warning(
@@ -117,7 +118,7 @@ class OnemeWS:
userPhone, userPhone,
userId, userId,
hashedToken, hashedToken,
) = await self.processors.login(payload, seq, websocket) ) = await self.processors.login(payload, seq, websocket, deviceType, appVersion)
if userPhone: if userPhone:
await self._finish_auth( await self._finish_auth(
@@ -246,6 +247,16 @@ class OnemeWS:
seq, seq,
websocket, websocket,
) )
case self.opcodes.CONFIG:
await self.auth_required(
userPhone,
self.processors.update_config,
payload,
seq,
websocket,
userPhone,
hashedToken,
)
case _: case _:
self.logger.warning(f"Неизвестный опкод {opcode}") self.logger.warning(f"Неизвестный опкод {opcode}")
except websockets.exceptions.ConnectionClosed: except websockets.exceptions.ConnectionClosed:

View File

@@ -300,6 +300,12 @@ class AuthProcessors(BaseProcessor):
chat.get("chat_id") chat.get("chat_id")
) )
# Обновляем юзер конфиг
updated_user_config = await self.tools.update_user_config(
cursor, token_data.get("phone"),
user_data.get("user_config"), self.static.USER_SETTINGS
)
# Аватарка с биографией # Аватарка с биографией
photo_id = None if not user.get("avatar_id") else int(user.get("avatar_id")) 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 + str(photo_id) avatar_url = None if not photo_id else self.config.avatar_base_url + str(photo_id)
@@ -335,7 +341,7 @@ class AuthProcessors(BaseProcessor):
"config": { "config": {
"hash": "e5903aa8-0000000000000000-80000106-0000000000000001-00000001-0000000000000000-00000000-2-00000001-0000019c9559d057", "hash": "e5903aa8-0000000000000000-80000106-0000000000000001-00000001-0000000000000000-00000000-2-00000001-0000019c9559d057",
"server": self.server_config, "server": self.server_config,
"user": json.loads(user_data.get("user_config")), "user": updated_user_config,
"chatFolders": { "chatFolders": {
"FOLDERS": [], "FOLDERS": [],
"ALL_FILTER_EXCLUDE": [] "ALL_FILTER_EXCLUDE": []

View File

@@ -105,12 +105,17 @@ class TelegramBot:
self.sql_queries.INSERT_USER_DATA, self.sql_queries.INSERT_USER_DATA,
( (
new_phone, # phone new_phone, # phone
json.dumps(self.static.USER_FOLDERS), # folders
json.dumps(self.static.USER_SETTINGS), # user settings json.dumps(self.static.USER_SETTINGS), # user settings
json.dumps({}), # chat_config json.dumps({}), # chat_config
), ),
) )
# Добавляем дефолтную папку
await cursor.execute(
self.sql_queries.INSERT_DEFAULT_FOLDER,
(new_phone,),
)
await message.answer( await message.answer(
self.get_bot_message( self.get_bot_message(
self.msg_types.REGISTRATION_SUCCESS self.msg_types.REGISTRATION_SUCCESS

View File

@@ -23,6 +23,7 @@ CREATE TABLE `tokens` (
`device_name` VARCHAR(256) NOT NULL, `device_name` VARCHAR(256) NOT NULL,
`location` VARCHAR(256) NOT NULL, `location` VARCHAR(256) NOT NULL,
`time` VARCHAR(16) NOT NULL, `time` VARCHAR(16) NOT NULL,
`push_token` VARCHAR(512) DEFAULT NULL,
PRIMARY KEY (`phone`, `token_hash`) PRIMARY KEY (`phone`, `token_hash`)
); );
@@ -37,7 +38,6 @@ CREATE TABLE `auth_tokens` (
CREATE TABLE `user_data` ( CREATE TABLE `user_data` (
`phone` VARCHAR(20) NOT NULL UNIQUE, `phone` VARCHAR(20) NOT NULL UNIQUE,
`folders` JSON NOT NULL,
`user_config` JSON NOT NULL, `user_config` JSON NOT NULL,
`chat_config` JSON NOT NULL, `chat_config` JSON NOT NULL,
PRIMARY KEY (`phone`) PRIMARY KEY (`phone`)
@@ -78,3 +78,33 @@ CREATE TABLE `contacts` (
`is_blocked` BOOLEAN NOT NULL DEFAULT FALSE, `is_blocked` BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (`owner_id`, `contact_id`) PRIMARY KEY (`owner_id`, `contact_id`)
); );
CREATE TABLE `banners` (
`id` VARCHAR(64) NOT NULL,
`title` VARCHAR(256) NOT NULL,
`description` VARCHAR(512) NOT NULL,
`url` VARCHAR(512) NOT NULL,
`type` INT NOT NULL DEFAULT 1,
`priority` INT NOT NULL DEFAULT 0,
`animoji_id` INT NOT NULL DEFAULT 0,
`repeat` INT NOT NULL DEFAULT 1,
`rerun` BIGINT NOT NULL DEFAULT 0,
`hide_close_button` BOOLEAN NOT NULL DEFAULT FALSE,
`hide_on_click` BOOLEAN NOT NULL DEFAULT FALSE,
`is_title_animated` BOOLEAN NOT NULL DEFAULT FALSE,
`enabled` BOOLEAN NOT NULL DEFAULT TRUE,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
CREATE TABLE `user_folders` (
`id` VARCHAR(64) NOT NULL,
`phone` VARCHAR(20) NOT NULL,
`title` VARCHAR(128) NOT NULL,
`filters` JSON NOT NULL DEFAULT ('[]'),
`options` JSON NOT NULL DEFAULT ('[]'),
`source_id` INT NOT NULL DEFAULT 1,
`update_time` BIGINT NOT NULL DEFAULT 0,
`sort_order` INT NOT NULL DEFAULT 0,
PRIMARY KEY (`id`, `phone`)
);