mirror of
https://github.com/openmax-server/server.git
synced 2026-05-22 19:41:41 +03:00
Compare commits
5 Commits
4abe6de885
...
810d480dbd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
810d480dbd | ||
|
|
227f90c3c3 | ||
|
|
56133416e3 | ||
|
|
35a4101608 | ||
|
|
9fcba1af86 |
@@ -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
3
.gitignore
vendored
@@ -2,4 +2,5 @@ __pycache__
|
|||||||
.env
|
.env
|
||||||
*.pem
|
*.pem
|
||||||
*.sqlite
|
*.sqlite
|
||||||
*.crt
|
*.crt
|
||||||
|
*-adminsdk-*.json
|
||||||
@@ -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`
|
||||||
@@ -7,4 +7,5 @@ pydantic
|
|||||||
aiosqlite
|
aiosqlite
|
||||||
aiohttp
|
aiohttp
|
||||||
python-dotenv
|
python-dotenv
|
||||||
cryptography
|
cryptography
|
||||||
|
firebase-admin
|
||||||
@@ -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
92
src/common/push.py
Normal 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)
|
||||||
@@ -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)
|
||||||
|
"""
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
28
src/main.py
28
src/main.py
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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": []
|
||||||
}
|
}
|
||||||
|
|
||||||
# Собираем пакет
|
# Собираем пакет
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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": []
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
32
tables.sql
32
tables.sql
@@ -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`)
|
||||||
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user