mirror of
https://github.com/openmax-server/server.git
synced 2026-05-22 19:41:41 +03:00
MAX: пуши через firebase (особо не тестил, вроде работает)
This commit is contained in:
@@ -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)
|
||||||
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,
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class MainProcessors(BaseProcessor):
|
|||||||
# Отправляем
|
# Отправляем
|
||||||
await self._send(writer, response)
|
await self._send(writer, response)
|
||||||
|
|
||||||
async def update_config(self, payload, seq, writer, userPhone):
|
async def update_config(self, payload, seq, writer, userPhone, hashedToken=None):
|
||||||
"""
|
"""
|
||||||
Обработчик 22 опкода (config)
|
Обработчик 22 опкода (config)
|
||||||
Он отвечает за обновление настроек приватности
|
Он отвечает за обновление настроек приватности
|
||||||
@@ -140,9 +140,14 @@ class MainProcessors(BaseProcessor):
|
|||||||
# а отдавать его нужно только при изменении настроек приватности
|
# а отдавать его нужно только при изменении настроек приватности
|
||||||
result_payload = None
|
result_payload = None
|
||||||
|
|
||||||
if payload.get("pushToken") and payload.get("pushOptions"):
|
if payload.get("pushToken"):
|
||||||
# TODO: Когда сядем за пуши, сделать тут обновление пуш токена
|
push_token = payload.get("pushToken")
|
||||||
pass
|
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"):
|
elif payload.get("settings") and payload.get("settings").get("user"):
|
||||||
"""Обновление настроек приватности"""
|
"""Обновление настроек приватности"""
|
||||||
new_settings = payload.get("settings").get("user")
|
new_settings = payload.get("settings").get("user")
|
||||||
|
|||||||
@@ -280,6 +280,7 @@ class OnemeMobile:
|
|||||||
seq,
|
seq,
|
||||||
writer,
|
writer,
|
||||||
userPhone,
|
userPhone,
|
||||||
|
hashedToken,
|
||||||
)
|
)
|
||||||
case _:
|
case _:
|
||||||
self.logger.warning(f"Неизвестный опкод {opcode}")
|
self.logger.warning(f"Неизвестный опкод {opcode}")
|
||||||
|
|||||||
@@ -254,6 +254,7 @@ class OnemeWS:
|
|||||||
seq,
|
seq,
|
||||||
websocket,
|
websocket,
|
||||||
userPhone,
|
userPhone,
|
||||||
|
hashedToken,
|
||||||
)
|
)
|
||||||
case _:
|
case _:
|
||||||
self.logger.warning(f"Неизвестный опкод {opcode}")
|
self.logger.warning(f"Неизвестный опкод {opcode}")
|
||||||
|
|||||||
@@ -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`)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user