first commit

This commit is contained in:
Alexey Polyakov
2026-03-08 23:36:13 +03:00
commit de07725212
35 changed files with 3409 additions and 0 deletions

0
src/common/__init__.py Normal file
View File

40
src/common/config.py Normal file
View File

@@ -0,0 +1,40 @@
import os
from dotenv import load_dotenv
load_dotenv()
class ServerConfig:
def __init__(self):
pass
### Адрес сервера
host = os.getenv("host") or "0.0.0.0"
### Для мобилок
oneme_tcp_port = int(os.getenv("oneme_tcp_port") or 443)
tamtam_tcp_port = int(os.getenv("tamtam_tcp_port") or 4433)
### Шлюзы для веба
oneme_ws_port = int(os.getenv("oneme_ws_port") or 81)
tamtam_ws_port = int(os.getenv("tamtam_ws_port") or 82)
### Уровень отладки
log_level = os.getenv("log_level") or "debug"
### MySQL
db_host = os.getenv("db_host") or "127.0.0.1"
db_port = int(os.getenv("db_port") or 3306)
db_user = os.getenv("db_user") or "root"
db_password = os.getenv("db_password") or "qwerty"
db_name = os.getenv("db_name") or "openmax"
### SSL
certfile = os.getenv("certfile") or "cert.pem"
keyfile = os.getenv("keyfile") or "key.pem"
### Avatar base url
avatar_base_url = os.getenv("avatar_base_url") or "http://127.0.0.1/avatar/"
### Telegram bot
telegram_bot_token = os.getenv("telegram_bot_token") or "123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ"
telegram_bot_enabled = bool(os.getenv("telegram_bot_enabled")) or True
telegram_whitelist_ids = [x.strip() for x in os.getenv("telegram_whitelist_ids", "").split(",") if x.strip()]

84
src/common/static.py Normal file
View File

@@ -0,0 +1,84 @@
class Static:
"""Тут просто статические константы для их дальнейшего использования"""
def __init__(self):
pass
class ErrorTypes:
NOT_IMPLEMENTED = "not_implemented"
INVALID_PAYLOAD = "invalid_payload"
USER_NOT_FOUND = "user_not_found"
CODE_EXPIRED = "code_expired"
INVALID_CODE = "invalid_code"
INVALID_TOKEN = "invalid_token"
CHAT_NOT_FOUND = "chat_not_found"
CHAT_NOT_ACCESS = "chat_not_access"
class ChatTypes:
DIALOG = "DIALOG"
ERROR_TYPES = {
"not_implemented": {
"localizedMessage": "Не реализовано",
"error": "proto.opcode",
"message": "Not implemented",
"title": "Не реализовано"
},
"invalid_payload": {
"localizedMessage": "Ошибка валидации",
"error": "proto.payload",
"message": "Invalid payload",
"title": "Ошибка валидации"
},
"user_not_found": {
"localizedMessage": "Не нашли этот номер, проверьте цифры",
"error": "error.phone.wrong",
"message": "User not found",
"title": "Не нашли этот номер, проверьте цифры"
},
"code_expired": {
"localizedMessage": "Этот код устарел, запросите новый",
"error": "error.code.expired",
"message": "Code expired",
"title": "Этот код устарел, запросите новый"
},
"invalid_code": {
"localizedMessage": "Неверный код",
"error": "error.code.wrong",
"message": "Invalid code",
"title": "Неверный код"
},
"invalid_token": {
"localizedMessage": "Ошибка входа. Пожалуйста, авторизируйтесь снова",
"error": "login.token",
"message": "Invalid token",
"title": "Ошибка входа. Пожалуйста, авторизируйтесь снова"
},
"chat_not_found": {
"localizedMessage": "Чат не найден",
"error": "chat.not.found",
"message": "Chat not found",
"title": "Чат не найден"
},
"chat_not_access": {
"localizedMessage": "Нет доступа к чату",
"error": "chat.not.access",
"message": "Chat not access",
"title": "Нет доступа к чату"
}
}
COMPLAIN_REASONS = [
# TODO: Было бы очень замечательно заполнить этот лист причинами для жалоб
]
### Заглушка для папок
ALL_CHAT_FOLDER = [{
"id": "all.chat.folder",
"title": "Все",
"filters": [],
"updateTime": 0,
"options": [],
"sourceId": 1
}]
ALL_CHAT_FOLDER_ORDER = ["all.chat.folder"]

194
src/common/tools.py Normal file
View File

@@ -0,0 +1,194 @@
import json, time
class Tools:
def __init__(self):
pass
def generate_profile(
self, id=1, phone=70000000000, avatarUrl=None,
photoId=None, updateTime=0,
firstName="Test", lastName="Account", options=[],
description=None, accountStatus=0, profileOptions=[],
includeProfileOptions=True, username=None
):
contact = {
"id": id,
"updateTime": updateTime,
"phone": phone,
"names": [
{
"name": firstName,
"firstName": firstName,
"lastName": lastName,
"type": "ONEME"
}
],
"options": options,
"accountStatus": accountStatus
}
if avatarUrl:
contact["photoId"] = photoId
contact["baseUrl"] = avatarUrl
contact["baseRawUrl"] = avatarUrl
if description:
contact["description"] = description
if username:
contact["link"] = "https://max.ru/" + username
if includeProfileOptions == True:
return {
"contact": contact,
"profileOptions": profileOptions
}
else:
return contact
def generate_chat(self, id, owner, type, participants, lastMessage, lastEventTime):
"""Генерация чата"""
# Генерируем список участников
result_participants = {
str(participant): 0 for participant in participants
}
result = None
# Генерируем нужный список в зависимости от типа чата
if type == "DIALOG":
result = {
"id": id,
"type": type,
"status": "ACTIVE",
"owner": owner,
"participants": result_participants,
"lastMessage": lastMessage,
"lastEventTime": lastEventTime,
"lastDelayedUpdateTime": 0,
"lastFireDelayedErrorTime": 0,
"created": 1,
"joinTime": 1,
"modified": lastEventTime
}
# Возвращаем
return result
async def generate_chats(self, chatIds, db_pool, senderId):
"""Генерирует чаты для отдачи клиенту"""
# Готовый список с чатами
chats = []
# Формируем список чатов
for chatId in chatIds:
async with db_pool.acquire() as db_connection:
async with db_connection.cursor() as cursor:
# Получаем чат по id
await cursor.execute("SELECT * FROM `chats` WHERE id = %s", (chatId,))
row = await cursor.fetchone()
if row:
# Получаем последнее сообщение из чата
message, messageTime = await self.get_last_message(
chatId, db_pool
)
# Формируем список участников
participants = {
str(participant): 0 for participant in row.get("participants")
}
# Выносим результат в лист
chats.append(
{
"id": row.get("id"),
"type": row.get("type"),
"status": "ACTIVE",
"owner": row.get("owner"),
"participants": participants,
"lastMessage": message,
"lastEventTime": messageTime,
"lastDelayedUpdateTime": 0,
"lastFireDelayedErrorTime": 0,
"created": 1,
"joinTime": 1,
"modified": messageTime
}
)
# Получаем последнее сообщение из избранного
message, messageTime = await self.get_last_message(
senderId, db_pool
)
# Хардкодим в лист чатов избранное
chats.append(
{
"id": 0,
"type": "DIALOG",
"status": "ACTIVE",
"owner": senderId,
"participants": {
str(senderId): 0 # if not messageTime else messageTime
},
"lastMessage": message,
"lastEventTime": messageTime,
"lastDelayedUpdateTime": 0,
"lastFireDelayedErrorTime": 0,
"created": 1,
"joinTime": 1,
"modified": messageTime
}
)
return chats
async def insert_message(self, chatId, senderId, text, attaches, elements, cid, type, db_pool):
"""Добавление сообщения в историю"""
async with db_pool.acquire() as db_connection:
async with db_connection.cursor() as cursor:
# Получаем id последнего сообщения в чате
await cursor.execute("SELECT id FROM `messages` WHERE chat_id = %s ORDER BY time DESC LIMIT 1", (chatId,))
row = await cursor.fetchone() or {}
last_message_id = row.get("id") or 0 # последнее id сообщения в чате
# Вносим новое сообщение в таблицу
await cursor.execute(
"INSERT INTO `messages` (chat_id, sender, time, text, attaches, cid, elements, type) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)",
(chatId, senderId, int(time.time() * 1000), text, json.dumps(attaches), cid, json.dumps(elements), type)
)
message_id = cursor.lastrowid # id сообщения
# Возвращаем айдишки
return int(message_id), int(last_message_id)
async def get_last_message(self, chatId, db_pool):
"""Получение последнего сообщения в чате"""
async with db_pool.acquire() as db_connection:
async with db_connection.cursor() as cursor:
# Получаем id последнего сообщения в чате
await cursor.execute("SELECT * FROM `messages` WHERE chat_id = %s ORDER BY time DESC LIMIT 1", (chatId,))
row = await cursor.fetchone()
# Если нет результатов - возвращаем None
if not row:
return None, None
# Собираем сообщение
message = {
"id": row.get("id"),
"time": int(row.get("time")),
"type": row.get("type"),
"sender": row.get("sender"),
"text": row.get("text"),
"attaches": json.loads(row.get("attaches")),
# "reactionInfo": {}
}
# Возвращаем
return message, int(row.get("time"))