mirror of
https://github.com/openmax-server/server.git
synced 2026-05-22 19:41:41 +03:00
Compare commits
6 Commits
6bb0d52419
...
ac40cc53c9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac40cc53c9 | ||
|
|
756956d8a0 | ||
|
|
00071c80be | ||
|
|
a045457128 | ||
|
|
4d51c70f8e | ||
|
|
2d3b9285bf |
60
src/common/sqlite.py
Normal file
60
src/common/sqlite.py
Normal file
@@ -0,0 +1,60 @@
|
||||
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)
|
||||
@@ -13,6 +13,8 @@ class Static:
|
||||
CHAT_NOT_FOUND = "chat_not_found"
|
||||
CHAT_NOT_ACCESS = "chat_not_access"
|
||||
RATE_LIMITED = "rate_limited"
|
||||
CONTACT_NOT_FOUND = "contact_not_found"
|
||||
CONTACT_ALREADY_ADDED = "contact_already_added"
|
||||
|
||||
class ChatTypes:
|
||||
DIALOG = "DIALOG"
|
||||
@@ -80,7 +82,19 @@ class Static:
|
||||
"error": "error.rate_limited",
|
||||
"message": "Too many attempts. Please try again later",
|
||||
"title": "Слишком много попыток"
|
||||
}
|
||||
},
|
||||
"contact_not_found": {
|
||||
"localizedMessage": "Контакт не найден",
|
||||
"error": "contact.not.found",
|
||||
"message": "Contact not found",
|
||||
"title": "Контакт не найден"
|
||||
},
|
||||
"contact_already_added": {
|
||||
"localizedMessage": "Контакт уже добавлен",
|
||||
"error": "contact.already.added",
|
||||
"message": "Contact already added",
|
||||
"title": "Контакт уже добавлен"
|
||||
},
|
||||
}
|
||||
|
||||
### Сообщения бота
|
||||
|
||||
77
src/main.py
77
src/main.py
@@ -4,9 +4,11 @@ import logging
|
||||
import signal
|
||||
import ssl
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from common.config import ServerConfig
|
||||
from common.push import PushService
|
||||
from common.sqlite import SQLitePoolCompat
|
||||
from oneme.controller import OnemeController
|
||||
from tamtam.controller import TTController
|
||||
from telegrambot.controller import TelegramBotController
|
||||
@@ -14,71 +16,6 @@ from telegrambot.controller import TelegramBotController
|
||||
# Конфиг сервера
|
||||
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():
|
||||
"""Инициализация базы данных"""
|
||||
|
||||
@@ -116,7 +53,6 @@ def init_ssl():
|
||||
# Возвращаем
|
||||
return ssl_context
|
||||
|
||||
|
||||
def set_logging():
|
||||
"""Настройка уровня логирования"""
|
||||
# Настройка уровня логирования
|
||||
@@ -195,8 +131,13 @@ async def main():
|
||||
# Запускаем контроллеры
|
||||
try:
|
||||
await asyncio.gather(*running_tasks)
|
||||
except (asyncio.CancelledError, Exception):
|
||||
logging.info("Все задачи завершены, выходим")
|
||||
except asyncio.CancelledError:
|
||||
logging.info("Все задачи завершены")
|
||||
except Exception as e:
|
||||
logging.error(
|
||||
f"Произошла неизвестная ошибка: {e}"
|
||||
)
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
if hasattr(db, 'close'):
|
||||
db.close()
|
||||
|
||||
@@ -148,5 +148,5 @@ class ContactPresencePayloadModel(pydantic.BaseModel):
|
||||
class ContactUpdatePayloadModel(pydantic.BaseModel):
|
||||
action: str
|
||||
contactId: int
|
||||
firstName: str
|
||||
firstName: str = None
|
||||
lastName: str = None
|
||||
@@ -95,7 +95,7 @@ class ContactsProcessors(BaseProcessor):
|
||||
user = await cursor.fetchone()
|
||||
|
||||
if not user:
|
||||
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.USER_NOT_FOUND, writer)
|
||||
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_NOT_FOUND, writer)
|
||||
return
|
||||
|
||||
# Проверяем, не добавлен ли уже контакт
|
||||
@@ -114,7 +114,7 @@ class ContactsProcessors(BaseProcessor):
|
||||
)
|
||||
# а если уже существует, отправляем ошибку
|
||||
else:
|
||||
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_ALREADY_EXISTS, writer)
|
||||
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_ALREADY_ADDED, writer)
|
||||
return
|
||||
|
||||
# Генерируем профиль
|
||||
@@ -161,6 +161,122 @@ class ContactsProcessors(BaseProcessor):
|
||||
|
||||
await self._send(writer, packet)
|
||||
|
||||
elif action == "BLOCK":
|
||||
async with self.db_pool.acquire() as conn:
|
||||
async with conn.cursor() as cursor:
|
||||
# Проверяем, существует ли контакт
|
||||
await cursor.execute(
|
||||
"SELECT * FROM contacts WHERE owner_id = %s AND contact_id = %s",
|
||||
(userId, contactId)
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
|
||||
# Обновляем существующий контакт, если такой есть
|
||||
if row:
|
||||
await cursor.execute(
|
||||
"UPDATE contacts SET is_blocked = TRUE WHERE owner_id = %s AND contact_id = %s",
|
||||
(userId, contactId)
|
||||
)
|
||||
else: # В ином случае добавляем новую запись в бд
|
||||
await cursor.execute("SELECT * FROM users WHERE id = %s", (contactId,))
|
||||
user = await cursor.fetchone()
|
||||
if not user:
|
||||
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.USER_NOT_FOUND, writer)
|
||||
return
|
||||
|
||||
await cursor.execute(
|
||||
"INSERT INTO contacts (owner_id, contact_id, custom_firstname, custom_lastname, is_blocked) VALUES (%s, %s, %s, %s, TRUE)",
|
||||
(userId, contactId, firstName, lastName)
|
||||
)
|
||||
|
||||
packet = self.proto.pack_packet(
|
||||
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONTACT_UPDATE, payload=None
|
||||
)
|
||||
|
||||
await self._send(writer, packet)
|
||||
|
||||
elif action == "UNBLOCK":
|
||||
# Разблокируем контакт
|
||||
async with self.db_pool.acquire() as conn:
|
||||
async with conn.cursor() as cursor:
|
||||
# Проверяем, существует ли контакт
|
||||
await cursor.execute(
|
||||
"SELECT * FROM contacts WHERE owner_id = %s AND contact_id = %s",
|
||||
(userId, contactId)
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
|
||||
# Обновляем контакт, если он есть
|
||||
if row:
|
||||
await cursor.execute(
|
||||
"UPDATE contacts SET is_blocked = FALSE WHERE owner_id = %s AND contact_id = %s",
|
||||
(userId, contactId)
|
||||
)
|
||||
else: # В ином случае отправляем ошибку
|
||||
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_NOT_FOUND, writer)
|
||||
return
|
||||
|
||||
packet = self.proto.pack_packet(
|
||||
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONTACT_UPDATE, payload=None
|
||||
)
|
||||
|
||||
await self._send(writer, packet)
|
||||
|
||||
elif action == "UPDATE":
|
||||
async with self.db_pool.acquire() as conn:
|
||||
async with conn.cursor() as cursor:
|
||||
# Проверяем, существует ли контакт
|
||||
await cursor.execute(
|
||||
"SELECT * FROM contacts WHERE owner_id = %s AND contact_id = %s",
|
||||
(userId, contactId)
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
|
||||
# Если контакта нет, отдаем ошибку
|
||||
if not row:
|
||||
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_NOT_FOUND, writer)
|
||||
return
|
||||
|
||||
# Обновляем контакт
|
||||
await cursor.execute(
|
||||
"UPDATE contacts SET custom_firstname = %s, custom_lastname = %s WHERE owner_id = %s AND contact_id = %s",
|
||||
(firstName, lastName, userId, contactId)
|
||||
)
|
||||
|
||||
# Получаем данные пользователя
|
||||
await cursor.execute("SELECT * FROM users WHERE id = %s", (contactId,))
|
||||
user = await cursor.fetchone()
|
||||
|
||||
# Генерируем профиль
|
||||
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 + str(photoId)
|
||||
|
||||
contact = self.tools.generate_profile(
|
||||
id=user.get("id"),
|
||||
phone=int(user.get("phone")),
|
||||
avatarUrl=avatar_url,
|
||||
photoId=photoId,
|
||||
updateTime=int(user.get("updatetime")),
|
||||
firstName=user.get("firstname"),
|
||||
lastName=user.get("lastname"),
|
||||
options=json.loads(user.get("options")),
|
||||
accountStatus=int(user.get("accountstatus")),
|
||||
description=user.get("description"),
|
||||
includeProfileOptions=False,
|
||||
custom_firstname=firstName,
|
||||
custom_lastname=lastName,
|
||||
)
|
||||
|
||||
response_payload = {
|
||||
"contact": contact
|
||||
}
|
||||
|
||||
packet = self.proto.pack_packet(
|
||||
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONTACT_UPDATE, payload=response_payload
|
||||
)
|
||||
|
||||
await self._send(writer, packet)
|
||||
|
||||
async def contact_presence(self, payload, seq, writer):
|
||||
"""Обработчик получения статуса контактов"""
|
||||
# Валидируем данные пакета
|
||||
|
||||
@@ -24,7 +24,7 @@ class HistoryProcessors(BaseProcessor):
|
||||
|
||||
# Если пользователь хочет получить историю из избранного,
|
||||
# то выставляем в качестве ID чата его ID
|
||||
if chatId == 0:
|
||||
if chatId == (senderId ^ senderId):
|
||||
chatId = senderId
|
||||
|
||||
# Проверяем, существует ли чат
|
||||
|
||||
@@ -89,8 +89,7 @@ class MessagesProcessors(BaseProcessor):
|
||||
|
||||
# Если клиент хочет отправить сообщение в избранное,
|
||||
# то выставляем в качестве ID чата ID отправителя
|
||||
# (А ещё используем это, если клиент вообще ничего не указал)
|
||||
if chatId == 0 or not chatId:
|
||||
if chatId == (senderId ^ senderId):
|
||||
chatId = senderId
|
||||
participants = [senderId]
|
||||
else:
|
||||
@@ -144,7 +143,7 @@ class MessagesProcessors(BaseProcessor):
|
||||
participant,
|
||||
{
|
||||
"eventType": "new_msg",
|
||||
"chatId": 0 if chatId == senderId else chatId,
|
||||
"chatId": 0 if chatId == (senderId ^ senderId) else chatId,
|
||||
"message": bodyMessage,
|
||||
"prevMessageId": lastMessageId,
|
||||
"time": messageTime,
|
||||
|
||||
@@ -114,7 +114,7 @@ class ContactsProcessors(BaseProcessor):
|
||||
)
|
||||
# а если уже существует, отправляем ошибку
|
||||
else:
|
||||
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_ALREADY_EXISTS, writer)
|
||||
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_ALREADY_ADDED, writer)
|
||||
return
|
||||
|
||||
# Генерируем профиль
|
||||
@@ -161,6 +161,119 @@ class ContactsProcessors(BaseProcessor):
|
||||
|
||||
await self._send(writer, packet)
|
||||
|
||||
elif action == "BLOCK":
|
||||
async with self.db_pool.acquire() as conn:
|
||||
async with conn.cursor() as cursor:
|
||||
# Проверяем, существует ли контакт
|
||||
await cursor.execute(
|
||||
"SELECT * FROM contacts WHERE owner_id = %s AND contact_id = %s",
|
||||
(userId, contactId)
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
|
||||
# Обновляем существующий контакт, если такой есть
|
||||
if row:
|
||||
await cursor.execute(
|
||||
"UPDATE contacts SET is_blocked = TRUE WHERE owner_id = %s AND contact_id = %s",
|
||||
(userId, contactId)
|
||||
)
|
||||
else: # В ином случае добавляем новую запись в бд
|
||||
await cursor.execute("SELECT * FROM users WHERE id = %s", (contactId,))
|
||||
user = await cursor.fetchone()
|
||||
if not user:
|
||||
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.USER_NOT_FOUND, writer)
|
||||
return
|
||||
|
||||
await cursor.execute(
|
||||
"INSERT INTO contacts (owner_id, contact_id, custom_firstname, custom_lastname, is_blocked) VALUES (%s, %s, %s, %s, TRUE)",
|
||||
(userId, contactId, firstName, lastName)
|
||||
)
|
||||
|
||||
packet = self.proto.pack_packet(
|
||||
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONTACT_UPDATE, payload=None
|
||||
)
|
||||
|
||||
await self._send(writer, packet)
|
||||
|
||||
elif action == "UNBLOCK":
|
||||
# Разблокируем контакт
|
||||
async with self.db_pool.acquire() as conn:
|
||||
async with conn.cursor() as cursor:
|
||||
# Проверяем, существует ли контакт
|
||||
await cursor.execute(
|
||||
"SELECT * FROM contacts WHERE owner_id = %s AND contact_id = %s",
|
||||
(userId, contactId)
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
|
||||
# Обновляем контакт, если он есть
|
||||
if row:
|
||||
await cursor.execute(
|
||||
"UPDATE contacts SET is_blocked = FALSE WHERE owner_id = %s AND contact_id = %s",
|
||||
(userId, contactId)
|
||||
)
|
||||
else: # В ином случае отправляем ошибку
|
||||
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_NOT_FOUND, writer)
|
||||
return
|
||||
|
||||
packet = self.proto.pack_packet(
|
||||
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONTACT_UPDATE, payload=None
|
||||
)
|
||||
|
||||
await self._send(writer, packet)
|
||||
|
||||
elif action == "UPDATE":
|
||||
async with self.db_pool.acquire() as conn:
|
||||
async with conn.cursor() as cursor:
|
||||
# Проверяем, существует ли контакт
|
||||
await cursor.execute(
|
||||
"SELECT * FROM contacts WHERE owner_id = %s AND contact_id = %s",
|
||||
(userId, contactId)
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
|
||||
# Если контакта нет, отдаем ошибку
|
||||
if not row:
|
||||
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_NOT_FOUND, writer)
|
||||
return
|
||||
|
||||
# Обновляем контакт
|
||||
await cursor.execute(
|
||||
"UPDATE contacts SET custom_firstname = %s, custom_lastname = %s WHERE owner_id = %s AND contact_id = %s",
|
||||
(firstName, lastName, userId, contactId)
|
||||
)
|
||||
|
||||
# Получаем данные пользователя
|
||||
await cursor.execute("SELECT * FROM users WHERE id = %s", (contactId,))
|
||||
user = await cursor.fetchone()
|
||||
|
||||
# Генерируем профиль
|
||||
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)
|
||||
|
||||
contact = self.tools.generate_profile_tt(
|
||||
id=user.get("id"),
|
||||
phone=int(user.get("phone")),
|
||||
avatarUrl=avatar_url,
|
||||
photoId=photo_id,
|
||||
updateTime=int(user.get("updatetime")),
|
||||
firstName=user.get("firstname"),
|
||||
lastName=user.get("lastname"),
|
||||
options=json.loads(user.get("options")),
|
||||
description=user.get("description"),
|
||||
username=user.get("username")
|
||||
),
|
||||
|
||||
response_payload = {
|
||||
"contact": contact
|
||||
}
|
||||
|
||||
packet = self.proto.pack_packet(
|
||||
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONTACT_UPDATE, payload=response_payload
|
||||
)
|
||||
|
||||
await self._send(writer, packet)
|
||||
|
||||
async def contact_presence(self, payload, seq, writer):
|
||||
"""Обработчик получения статуса контактов"""
|
||||
# Валидируем данные пакета
|
||||
|
||||
Reference in New Issue
Block a user