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>
This commit is contained in:
Aleksandr Kosachev
2026-04-14 20:05:34 +03:00
committed by GitHub
parent d9cbafc4e3
commit d9798a6fc6
6 changed files with 290 additions and 103 deletions

View File

@@ -1,10 +1,12 @@
import logging import logging
from common.config import ServerConfig from common.config import ServerConfig
from common.static import Static from common.opcodes import Opcodes
from common.tools import Tools
from common.proto_tcp import MobileProto from common.proto_tcp import MobileProto
from common.proto_web import WebProto from common.proto_web import WebProto
from common.opcodes import Opcodes from common.static import Static
from common.tools import Tools
class BaseProcessor: class BaseProcessor:
def __init__(self, db_pool=None, clients=None, send_event=None, type="socket"): def __init__(self, db_pool=None, clients=None, send_event=None, type="socket"):
@@ -21,7 +23,7 @@ class BaseProcessor:
self.event = send_event self.event = send_event
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
self.type = type self.type = "mobile" if type == "socket" else type
if type == "socket": if type == "socket":
self.proto = MobileProto() self.proto = MobileProto()
@@ -31,7 +33,7 @@ class BaseProcessor:
async def _send(self, writer, packet): async def _send(self, writer, packet):
try: try:
# Если объектом является вебсокет, то используем функцию send для отправки # Если объектом является вебсокет, то используем функцию send для отправки
if hasattr(writer, 'send'): if hasattr(writer, "send"):
await writer.send(packet) await writer.send(packet)
else: # В ином случае отправляем как в обычный сокет else: # В ином случае отправляем как в обычный сокет
writer.write(packet) writer.write(packet)
@@ -40,12 +42,15 @@ class BaseProcessor:
pass pass
async def _send_error(self, seq, opcode, error_type, writer): async def _send_error(self, seq, opcode, error_type, writer):
payload = self.static.ERROR_TYPES.get(error_type, { payload = self.static.ERROR_TYPES.get(
error_type,
{
"localizedMessage": "Неизвестная ошибка", "localizedMessage": "Неизвестная ошибка",
"error": "unknown.error", "error": "unknown.error",
"message": "Unknown error", "message": "Unknown error",
"title": "Неизвестная ошибка" "title": "Неизвестная ошибка",
}) },
)
packet = self.proto.pack_packet( packet = self.proto.pack_packet(
cmd=self.proto.CMD_ERR, seq=seq, opcode=opcode, payload=payload cmd=self.proto.CMD_ERR, seq=seq, opcode=opcode, payload=payload

View File

@@ -50,4 +50,4 @@ class ServerConfig:
origins = [x.strip() for x in os.getenv("origins", "").split(",") if x.strip()] if os.getenv("origins") else None origins = [x.strip() for x in os.getenv("origins", "").split(",") if x.strip()] if os.getenv("origins") else None
### sms шлюз ### sms шлюз
sms_gateway_url = os.getenv("sms_gateway_url") or "http://127.0.0.1/sms-gateway" sms_gateway_url = os.getenv("sms_gateway_url", "")

View File

@@ -96,10 +96,12 @@ class Tools:
"""Генерация чата""" """Генерация чата"""
# Генерируем список участников # Генерируем список участников
if isinstance(participants, dict): if isinstance(participants, dict):
result_participants = {str(k): v for k, v in participants.items()} result_participants = {
int(k): int(v) if v is not None else 0 for k, v in participants.items()
}
else: else:
# assume list # assume list
result_participants = {str(participant): 0 for participant in participants} result_participants = {int(participant): 0 for participant in participants}
result = None result = None
@@ -125,7 +127,14 @@ class Tools:
# Возвращаем # Возвращаем
return result return result
async def generate_chats(self, chatIds, db_pool, senderId, include_favourites=True, protocol_type='mobile'): async def generate_chats(
self,
chatIds,
db_pool,
senderId,
include_favourites=True,
protocol_type="mobile",
):
"""Генерирует чаты для отдачи клиенту""" """Генерирует чаты для отдачи клиенту"""
# Готовый список с чатами # Готовый список с чатами
chats = [] chats = []
@@ -188,12 +197,14 @@ class Tools:
) )
# Получаем ID предыдущего сообщения для избранного (чат ID = senderId) # Получаем ID предыдущего сообщения для избранного (чат ID = senderId)
prevMessageId = await self.get_previous_message_id(senderId, db_pool, protocol_type=protocol_type) prevMessageId = await self.get_previous_message_id(
senderId, db_pool, protocol_type=protocol_type
)
# Хардкодим в лист чатов избранное # Хардкодим в лист чатов избранное
chats.append( chats.append(
self.generate_chat( self.generate_chat(
chatId if protocol_type == 'mobile' else str(chatId), chatId if protocol_type == "mobile" else str(chatId),
senderId, senderId,
"DIALOG", "DIALOG",
participants, participants,
@@ -241,7 +252,7 @@ class Tools:
# Возвращаем айдишки # Возвращаем айдишки
return int(message_id), int(last_message_id), message_time return int(message_id), int(last_message_id), message_time
async def get_last_message(self, chatId, db_pool, protocol_type='mobile'): async def get_last_message(self, chatId, db_pool, protocol_type="mobile"):
"""Получение последнего сообщения в чате""" """Получение последнего сообщения в чате"""
async with db_pool.acquire() as db_connection: async with db_pool.acquire() as db_connection:
async with db_connection.cursor() as cursor: async with db_connection.cursor() as cursor:
@@ -259,7 +270,9 @@ class Tools:
# Собираем сообщение # Собираем сообщение
message = { message = {
"id": row.get("id") if protocol_type == 'mobile' else str(row.get('id')), "id": row.get("id")
if protocol_type == "mobile"
else str(row.get("id")),
"time": int(row.get("time")), "time": int(row.get("time")),
"type": row.get("type"), "type": row.get("type"),
"sender": row.get("sender"), "sender": row.get("sender"),
@@ -273,7 +286,7 @@ class Tools:
# Возвращаем # Возвращаем
return message, int(row.get("time")) return message, int(row.get("time"))
async def get_previous_message_id(self, chatId, db_pool, protocol_type='mobile'): async def get_previous_message_id(self, chatId, db_pool, protocol_type="mobile"):
"""Получение ID предыдущего сообщения (второго с конца) в чате.""" """Получение ID предыдущего сообщения (второго с конца) в чате."""
async with db_pool.acquire() as db_connection: async with db_pool.acquire() as db_connection:
async with db_connection.cursor() as cursor: async with db_connection.cursor() as cursor:
@@ -285,10 +298,14 @@ class Tools:
# Если результат есть, возвращаем его # Если результат есть, возвращаем его
if row: if row:
return row.get("id") if protocol_type == 'mobile' else str(row.get('id')) return (
row.get("id")
if protocol_type == "mobile"
else str(row.get("id"))
)
# В ином случае возвращаем 0 # В ином случае возвращаем 0
return 0 if protocol_type == 'mobile' else "0" return 0 if protocol_type == "mobile" else "0"
async def get_participant_last_activity(self, chatId, participant_ids, db_pool): async def get_participant_last_activity(self, chatId, participant_ids, db_pool):
"""Возвращает словарь {participant_id: last_activity_time} для участников чата.""" """Возвращает словарь {participant_id: last_activity_time} для участников чата."""
@@ -310,11 +327,11 @@ class Tools:
rows = await cursor.fetchall() rows = await cursor.fetchall()
# Собираем список участников без времени последней активности в чате # Собираем список участников без времени последней активности в чате
result = {str(pid): 0 for pid in participant_ids} result = {int(pid): 0 for pid in participant_ids}
# Обновляем для каждого участника время последней активности в чате # Обновляем для каждого участника время последней активности в чате
for row in rows: for row in rows:
sender = str(row["sender"]) sender = int(row["sender"])
last_time = row["last_time"] last_time = row["last_time"]
if last_time is not None: if last_time is not None:
result[sender] = int(last_time) result[sender] = int(last_time)
@@ -330,7 +347,7 @@ class Tools:
(chatId,), (chatId,),
) )
rows = await cursor.fetchall() rows = await cursor.fetchall()
return [row["user_id"] for row in rows] return [int(row["user_id"]) for row in rows]
async def auth_required(self, userPhone, coro, *args): async def auth_required(self, userPhone, coro, *args):
if userPhone: if userPhone:

View File

@@ -12,6 +12,70 @@ from telegrambot.controller import TelegramBotController
server_config = ServerConfig() server_config = ServerConfig()
class SQLiteCursorCompat:
def __init__(self, connection):
self.connection = connection
self.cursor = None
async def __aenter__(self):
self.cursor = await self.connection.cursor()
return self
async def __aexit__(self, exc_type, exc, tb):
if self.cursor is not None:
await self.cursor.close()
self.cursor = None
@property
def lastrowid(self):
return None if self.cursor is None else self.cursor.lastrowid
def _normalize_query(self, query):
return query.replace("%s", "?").replace(
"UNIX_TIMESTAMP()", "CAST(strftime('%s','now') AS INTEGER)"
)
async def execute(self, query, params=()):
normalized_query = self._normalize_query(query)
if params is None:
params = ()
elif not isinstance(params, (tuple, list, dict)):
params = (params,)
await self.cursor.execute(normalized_query, params)
async def fetchone(self):
row = await self.cursor.fetchone()
if row is None:
return None
return dict(row)
async def fetchall(self):
rows = await self.cursor.fetchall()
return [dict(row) for row in rows]
class SQLiteConnectionCompat:
def __init__(self, connection):
self.connection = connection
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc, tb):
return False
def cursor(self):
return SQLiteCursorCompat(self.connection)
class SQLitePoolCompat:
def __init__(self, connection):
self.connection = connection
def acquire(self):
return SQLiteConnectionCompat(self.connection)
async def init_db(): async def init_db():
"""Инициализация базы данных""" """Инициализация базы данных"""
@@ -32,8 +96,9 @@ async def init_db():
elif server_config.db_type == "sqlite": elif server_config.db_type == "sqlite":
import aiosqlite import aiosqlite
raw_db = await aiosqlite.connect(server_config.db_file) raw_db = await aiosqlite.connect(server_config.db_file, isolation_level=None)
db["acquire"] = lambda: raw_db raw_db.row_factory = aiosqlite.Row
db = SQLitePoolCompat(raw_db)
# Возвращаем # Возвращаем
return db return db

View File

@@ -1,22 +1,31 @@
import json
import secrets
import hashlib import hashlib
import time import json
import logging import logging
import secrets
import time
import pydantic import pydantic
from classes.baseprocessor import BaseProcessor from classes.baseprocessor import BaseProcessor
from common.sms import send_sms_code
from oneme.config import OnemeConfig
from oneme.models import ( from oneme.models import (
RequestCodePayloadModel,
VerifyCodePayloadModel,
AuthConfirmRegisterPayloadModel, AuthConfirmRegisterPayloadModel,
LoginPayloadModel, LoginPayloadModel,
RequestCodePayloadModel,
VerifyCodePayloadModel,
) )
from oneme.config import OnemeConfig
from common.sms import send_sms_code
class AuthProcessors(BaseProcessor): class AuthProcessors(BaseProcessor):
def __init__(self, db_pool=None, clients=None, send_event=None, telegram_bot=None, type="socket"): def __init__(
self,
db_pool=None,
clients=None,
send_event=None,
telegram_bot=None,
type="socket",
):
super().__init__(db_pool, clients, send_event, type) super().__init__(db_pool, clients, send_event, type)
self.server_config = OnemeConfig().SERVER_CONFIG self.server_config = OnemeConfig().SERVER_CONFIG
self.telegram_bot = telegram_bot self.telegram_bot = telegram_bot
@@ -27,7 +36,9 @@ class AuthProcessors(BaseProcessor):
RequestCodePayloadModel.model_validate(payload) RequestCodePayloadModel.model_validate(payload)
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.AUTH_REQUEST, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(
seq, self.opcodes.AUTH_REQUEST, self.error_types.INVALID_PAYLOAD, writer
)
return return
# Извлекаем телефон из пакета # Извлекаем телефон из пакета
@@ -49,13 +60,16 @@ class AuthProcessors(BaseProcessor):
user = await cursor.fetchone() user = await cursor.fetchone()
# Получаем код через SMS шлюз или генерируем локально (безопасность прежде всего) # Получаем код через SMS шлюз или генерируем локально (безопасность прежде всего)
local_fallback_code = False
if self.config.sms_gateway_url: if self.config.sms_gateway_url:
code = await send_sms_code(self.config.sms_gateway_url, phone) code = await send_sms_code(self.config.sms_gateway_url, phone)
if code is None: if code is None:
code = str(secrets.randbelow(900000) + 100000) code = str(secrets.randbelow(900000) + 100000)
local_fallback_code = True
else: else:
code = str(secrets.randbelow(900000) + 100000) code = str(secrets.randbelow(900000) + 100000)
local_fallback_code = True
# Хешируем # Хешируем
code_hash = hashlib.sha256(code.encode()).hexdigest() code_hash = hashlib.sha256(code.encode()).hexdigest()
@@ -68,18 +82,35 @@ class AuthProcessors(BaseProcessor):
# Сохраняем токен # Сохраняем токен
await cursor.execute( await cursor.execute(
"INSERT INTO auth_tokens (phone, token_hash, code_hash, expires) VALUES (%s, %s, %s, %s)", "INSERT INTO auth_tokens (phone, token_hash, code_hash, expires) VALUES (%s, %s, %s, %s)",
(phone, token_hash, code_hash, expires,) (
phone,
token_hash,
code_hash,
expires,
),
) )
# Если тг бот включен, и тг привязан к аккаунту - отправляем туда сообщение # Если код был сгенерирован локально, а тг привязан к аккаунту - отправляем туда сообщение
if not self.config.sms_gateway_url and self.telegram_bot and user.get("telegram_id"): if (
await self.telegram_bot.send_code(chat_id=int(user.get("telegram_id")), phone=phone, code=code) local_fallback_code
and self.telegram_bot
and user.get("telegram_id")
):
await self.telegram_bot.send_code(
chat_id=int(user.get("telegram_id")), phone=phone, code=code
)
else: else:
# Пользователь не найден - сохраняем токен со state='register' # Пользователь не найден - сохраняем токен со state='register'
# чтобы после верификации кода направить на экран регистрации # чтобы после верификации кода направить на экран регистрации
await cursor.execute( await cursor.execute(
"INSERT INTO auth_tokens (phone, token_hash, code_hash, expires, state) VALUES (%s, %s, %s, %s, %s)", "INSERT INTO auth_tokens (phone, token_hash, code_hash, expires, state) VALUES (%s, %s, %s, %s, %s)",
(phone, token_hash, code_hash, expires, "register",) (
phone,
token_hash,
code_hash,
expires,
"register",
),
) )
# Данные пакета # Данные пакета
@@ -88,12 +119,15 @@ class AuthProcessors(BaseProcessor):
"codeLength": 6, "codeLength": 6,
"requestMaxDuration": 60000, "requestMaxDuration": 60000,
"requestCountLeft": 10, "requestCountLeft": 10,
"altActionDuration": 60000 "altActionDuration": 60000,
} }
# Собираем пакет # Собираем пакет
packet = self.proto.pack_packet( packet = self.proto.pack_packet(
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.AUTH_REQUEST, payload=payload cmd=self.proto.CMD_OK,
seq=seq,
opcode=self.opcodes.AUTH_REQUEST,
payload=payload,
) )
# Отправляем # Отправляем
@@ -106,7 +140,9 @@ class AuthProcessors(BaseProcessor):
VerifyCodePayloadModel.model_validate(payload) VerifyCodePayloadModel.model_validate(payload)
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.AUTH, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(
seq, self.opcodes.AUTH, self.error_types.INVALID_PAYLOAD, writer
)
return return
# Извлекаем данные из пакета # Извлекаем данные из пакета
@@ -127,18 +163,22 @@ class AuthProcessors(BaseProcessor):
# Ищем токен # Ищем токен
await cursor.execute( await cursor.execute(
"SELECT * FROM auth_tokens WHERE token_hash = %s AND expires > UNIX_TIMESTAMP()", "SELECT * FROM auth_tokens WHERE token_hash = %s AND expires > UNIX_TIMESTAMP()",
(hashed_token,) (hashed_token,),
) )
stored_token = await cursor.fetchone() stored_token = await cursor.fetchone()
# Если токен просрочен, или его нет - отправляем ошибку # Если токен просрочен, или его нет - отправляем ошибку
if stored_token is None: if stored_token is None:
await self._send_error(seq, self.opcodes.AUTH, self.error_types.CODE_EXPIRED, writer) await self._send_error(
seq, self.opcodes.AUTH, self.error_types.CODE_EXPIRED, writer
)
return return
# Проверяем код # Проверяем код
if stored_token.get("code_hash") != hashed_code: if stored_token.get("code_hash") != hashed_code:
await self._send_error(seq, self.opcodes.AUTH, self.error_types.INVALID_CODE, writer) await self._send_error(
seq, self.opcodes.AUTH, self.error_types.INVALID_CODE, writer
)
return return
# Если это новый пользователь - переводим токен в state='verified' # Если это новый пользователь - переводим токен в state='verified'
@@ -146,48 +186,60 @@ class AuthProcessors(BaseProcessor):
if stored_token.get("state") == "register": if stored_token.get("state") == "register":
await cursor.execute( await cursor.execute(
"UPDATE auth_tokens SET state = %s WHERE token_hash = %s", "UPDATE auth_tokens SET state = %s WHERE token_hash = %s",
("verified", hashed_token,) (
"verified",
hashed_token,
),
) )
packet = self.proto.pack_packet( packet = self.proto.pack_packet(
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.AUTH, cmd=self.proto.CMD_OK,
seq=seq,
opcode=self.opcodes.AUTH,
payload={ payload={
"tokenAttrs": { "tokenAttrs": {"REGISTER": {"token": token}},
"REGISTER": { "presetAvatars": [],
"token": token
}
}, },
"presetAvatars": []
}
) )
await self._send(writer, packet) await self._send(writer, packet)
return return
# Ищем аккаунт # Ищем аккаунт
await cursor.execute("SELECT * FROM users WHERE phone = %s", (stored_token.get("phone"),)) await cursor.execute(
"SELECT * FROM users WHERE phone = %s", (stored_token.get("phone"),)
)
account = await cursor.fetchone() account = await cursor.fetchone()
# Удаляем токен # Удаляем токен
await cursor.execute("DELETE FROM auth_tokens WHERE token_hash = %s", (hashed_token,)) await cursor.execute(
"DELETE FROM auth_tokens WHERE token_hash = %s", (hashed_token,)
)
# Создаем сессию # Создаем сессию
await cursor.execute( await cursor.execute(
"INSERT INTO tokens (phone, token_hash, device_type, device_name, location, time) VALUES (%s, %s, %s, %s, %s, %s)", "INSERT INTO tokens (phone, token_hash, device_type, device_name, location, time) VALUES (%s, %s, %s, %s, %s, %s)",
(stored_token.get("phone"), hashed_login, deviceType, deviceName, "Little Saint James Island", int(time.time()),) # весь покрытый зеленью, абсолютно весь, остров невезения в океане есть (
stored_token.get("phone"),
hashed_login,
deviceType,
deviceName,
"Little Saint James Island",
int(time.time()),
), # весь покрытый зеленью, абсолютно весь, остров невезения в океане есть
) )
# Генерируем профиль # Генерируем профиль
# Аватарка с биографией # Аватарка с биографией
photoId = None if not account.get("avatar_id") else int(account.get("avatar_id")) photoId = (
None if not account.get("avatar_id") else int(account.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 account.get("description") else account.get("description") description = (
None if not account.get("description") else account.get("description")
)
# Собираем данные пакета # Собираем данные пакета
payload = { payload = {
"tokenAttrs": { "tokenAttrs": {"LOGIN": {"token": login}},
"LOGIN": {
"token": login
}
},
"profile": self.tools.generate_profile( "profile": self.tools.generate_profile(
id=account.get("id"), id=account.get("id"),
phone=int(account.get("phone")), phone=int(account.get("phone")),
@@ -201,8 +253,8 @@ class AuthProcessors(BaseProcessor):
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=True,
username=account.get("username") username=account.get("username"),
) ),
} }
# Создаем пакет # Создаем пакет
@@ -220,7 +272,9 @@ class AuthProcessors(BaseProcessor):
AuthConfirmRegisterPayloadModel.model_validate(payload) AuthConfirmRegisterPayloadModel.model_validate(payload)
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.AUTH_CONFIRM, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(
seq, self.opcodes.AUTH_CONFIRM, self.error_types.INVALID_PAYLOAD, writer
)
return return
# Извлекаем данные из пакета # Извлекаем данные из пакета
@@ -240,13 +294,21 @@ class AuthProcessors(BaseProcessor):
# Ищем токен - он должен быть в state='verified' # Ищем токен - он должен быть в state='verified'
await cursor.execute( await cursor.execute(
"SELECT * FROM auth_tokens WHERE token_hash = %s AND expires > UNIX_TIMESTAMP() AND state = %s", "SELECT * FROM auth_tokens WHERE token_hash = %s AND expires > UNIX_TIMESTAMP() AND state = %s",
(hashed_token, "verified",) (
hashed_token,
"verified",
),
) )
stored_token = await cursor.fetchone() stored_token = await cursor.fetchone()
# Если токен не найден или просрочен - отправляем ошибку # Если токен не найден или просрочен - отправляем ошибку
if stored_token is None: if stored_token is None:
await self._send_error(seq, self.opcodes.AUTH_CONFIRM, self.error_types.CODE_EXPIRED, writer) await self._send_error(
seq,
self.opcodes.AUTH_CONFIRM,
self.error_types.CODE_EXPIRED,
writer,
)
return return
phone = stored_token.get("phone") phone = stored_token.get("phone")
@@ -254,7 +316,12 @@ class AuthProcessors(BaseProcessor):
# Проверяем что пользователь с таким телефоном ещё не существует # Проверяем что пользователь с таким телефоном ещё не существует
await cursor.execute("SELECT id FROM users WHERE phone = %s", (phone,)) await cursor.execute("SELECT id FROM users WHERE phone = %s", (phone,))
if await cursor.fetchone(): if await cursor.fetchone():
await self._send_error(seq, self.opcodes.AUTH_CONFIRM, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(
seq,
self.opcodes.AUTH_CONFIRM,
self.error_types.INVALID_PAYLOAD,
writer,
)
return return
now_ms = int(time.time() * 1000) now_ms = int(time.time() * 1000)
@@ -269,10 +336,18 @@ class AuthProcessors(BaseProcessor):
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""", """,
( (
self.tools.generate_id(), phone, None, first_name, last_name, None, self.tools.generate_id(),
json.dumps([]), json.dumps(["TT", "ONEME"]), phone,
0, str(now_ms), str(now_s), None,
) first_name,
last_name,
None,
json.dumps([]),
json.dumps(["TT", "ONEME"]),
0,
str(now_ms),
str(now_s),
),
) )
user_id = cursor.lastrowid user_id = cursor.lastrowid
@@ -290,16 +365,25 @@ class AuthProcessors(BaseProcessor):
json.dumps(self.static.USER_FOLDERS), 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("DELETE FROM auth_tokens WHERE token_hash = %s", (hashed_token,)) await cursor.execute(
"DELETE FROM auth_tokens WHERE token_hash = %s", (hashed_token,)
)
# Создаем сессию # Создаем сессию
await cursor.execute( await cursor.execute(
"INSERT INTO tokens (phone, token_hash, device_type, device_name, location, time) VALUES (%s, %s, %s, %s, %s, %s)", "INSERT INTO tokens (phone, token_hash, device_type, device_name, location, time) VALUES (%s, %s, %s, %s, %s, %s)",
(phone, hashed_login, deviceType or "ANDROID", deviceName or "Unknown", "Little Saint James Island", now_s,) (
phone,
hashed_login,
deviceType or "ANDROID",
deviceName or "Unknown",
"Little Saint James Island",
now_s,
),
) )
# Генерируем профиль # Генерируем профиль
@@ -316,7 +400,7 @@ class AuthProcessors(BaseProcessor):
accountStatus=0, accountStatus=0,
profileOptions=[], profileOptions=[],
includeProfileOptions=True, includeProfileOptions=True,
username=None username=None,
) )
# Собираем данные пакета # Собираем данные пакета
@@ -324,17 +408,22 @@ class AuthProcessors(BaseProcessor):
"userToken": "0", "userToken": "0",
"profile": profile, "profile": profile,
"tokenType": "LOGIN", "tokenType": "LOGIN",
"token": login "token": login,
} }
# Создаем пакет # Создаем пакет
packet = self.proto.pack_packet( packet = self.proto.pack_packet(
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.AUTH_CONFIRM, payload=payload cmd=self.proto.CMD_OK,
seq=seq,
opcode=self.opcodes.AUTH_CONFIRM,
payload=payload,
) )
# Отправляем # Отправляем
await self._send(writer, packet) await self._send(writer, packet)
self.logger.info(f"Новый пользователь зарегистрирован: phone={phone} id={user_id} name={first_name} {last_name}") self.logger.info(
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):
"""Обработчик авторизации клиента на сервере""" """Обработчик авторизации клиента на сервере"""
@@ -343,7 +432,9 @@ class AuthProcessors(BaseProcessor):
LoginPayloadModel.model_validate(payload) LoginPayloadModel.model_validate(payload)
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.LOGIN, self.error_types.INVALID_PAYLOAD, writer) await self._send_error(
seq, self.opcodes.LOGIN, self.error_types.INVALID_PAYLOAD, writer
)
return return
# Чаты, где состоит пользователь # Чаты, где состоит пользователь
@@ -358,33 +449,40 @@ class AuthProcessors(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 * FROM tokens WHERE token_hash = %s", (hashed_token,)) await cursor.execute(
"SELECT * FROM tokens WHERE token_hash = %s", (hashed_token,)
)
token_data = await cursor.fetchone() token_data = await cursor.fetchone()
# Если токен не найден, отправляем ошибку # Если токен не найден, отправляем ошибку
if token_data is None: if token_data is None:
await self._send_error(seq, self.opcodes.LOGIN, self.error_types.INVALID_TOKEN, writer) await self._send_error(
seq, self.opcodes.LOGIN, self.error_types.INVALID_TOKEN, writer
)
return return
# Ищем аккаунт пользователя в бд # Ищем аккаунт пользователя в бд
await cursor.execute("SELECT * FROM users WHERE phone = %s", (token_data.get("phone"),)) await cursor.execute(
"SELECT * FROM users WHERE phone = %s", (token_data.get("phone"),)
)
user = await cursor.fetchone() user = await cursor.fetchone()
# Ищем данные пользователя в бд # Ищем данные пользователя в бд
await cursor.execute("SELECT * FROM user_data WHERE phone = %s", (token_data.get("phone"),)) await cursor.execute(
"SELECT * FROM user_data WHERE phone = %s",
(token_data.get("phone"),),
)
user_data = await cursor.fetchone() user_data = await cursor.fetchone()
# Ищем все чаты, где состоит пользователь # Ищем все чаты, где состоит пользователь
await cursor.execute( await cursor.execute(
"SELECT * FROM chat_participants WHERE user_id = %s", "SELECT * FROM chat_participants WHERE user_id = %s",
(user.get('id')) (user.get("id")),
) )
user_chats = await cursor.fetchall() user_chats = await cursor.fetchall()
for chat in user_chats: for chat in user_chats:
chats.append( chats.append(chat.get("chat_id"))
chat.get("chat_id")
)
# Аватарка с биографией # Аватарка с биографией
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"))
@@ -405,7 +503,7 @@ class AuthProcessors(BaseProcessor):
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=True,
username=user.get("username") username=user.get("username"),
) )
chats = await self.tools.generate_chats( chats = await self.tools.generate_chats(
@@ -422,11 +520,11 @@ class AuthProcessors(BaseProcessor):
"presence": {}, "presence": {},
"config": { "config": {
"server": self.server_config, "server": self.server_config,
"user": json.loads(user_data.get("user_config")) "user": json.loads(user_data.get("user_config")),
}, },
"token": token, "token": token,
"videoChatHistory": False, "videoChatHistory": False,
"time": int(time.time() * 1000) "time": int(time.time() * 1000),
} }
# Собираем пакет # Собираем пакет
@@ -443,7 +541,9 @@ class AuthProcessors(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("DELETE FROM tokens WHERE token_hash = %s", (hashedToken,)) await cursor.execute(
"DELETE FROM tokens WHERE token_hash = %s", (hashedToken,)
)
# Создаем пакет # Создаем пакет
response = self.proto.pack_packet( response = self.proto.pack_packet(

View File

@@ -60,7 +60,7 @@ class TelegramBot:
tg_id = str(message.from_user.id) tg_id = str(message.from_user.id)
# Проверка ID на наличие в белом списке # Проверка ID на наличие в белом списке
if tg_id not in self.whitelist_ids: if self.whitelist_ids and tg_id not in self.whitelist_ids:
await message.answer( await message.answer(
self.get_bot_message(self.msg_types.ID_NOT_WHITELISTED) self.get_bot_message(self.msg_types.ID_NOT_WHITELISTED)
) )