diff --git a/backup.py b/backup.py
index 196e024a..51c6636b 100644
--- a/backup.py
+++ b/backup.py
@@ -2,7 +2,6 @@ import os
import subprocess
from datetime import datetime
-import aiofiles
from aiogram.types import BufferedInputFile
from config import ADMIN_ID, BACK_DIR, DB_NAME, DB_PASSWORD, DB_USER, PG_HOST, PG_PORT
@@ -99,9 +98,11 @@ async def create_backup_and_send_to_admins(xui) -> None:
async def _send_backup_to_admins(backup_file_path: str) -> None:
try:
from bot import bot
+ import aiofiles
- with open(backup_file_path, "rb") as backup_file:
- backup_input_file = BufferedInputFile(file=backup_file.read(), filename=os.path.basename(backup_file_path))
+ async with aiofiles.open(backup_file_path, "rb") as backup_file:
+ backup_data = await backup_file.read()
+ backup_input_file = BufferedInputFile(file=backup_data, filename=os.path.basename(backup_file_path))
admin_ids = ADMIN_ID if isinstance(ADMIN_ID, list) else [ADMIN_ID]
for admin_id in admin_ids:
await bot.send_document(chat_id=admin_id, document=backup_input_file)
diff --git a/client.py b/client.py
index 8c4d452c..984a662a 100644
--- a/client.py
+++ b/client.py
@@ -1,5 +1,6 @@
from dataclasses import dataclass
-from typing import Optional, Dict, Any, List
+from typing import Any
+
import py3xui
from config import LIMIT_IP, SUPERNODE
@@ -22,7 +23,7 @@ class ClientConfig:
sub_id: str
-async def add_client(xui: py3xui.API, config: ClientConfig) -> Dict[str, Any]:
+async def add_client(xui: py3xui.API, config: ClientConfig) -> dict[str, Any]:
"""
Добавляет клиента на сервер через 3x-ui.
@@ -66,7 +67,7 @@ async def add_client(xui: py3xui.API, config: ClientConfig) -> Dict[str, Any]:
async def extend_client_key(
xui: py3xui.API, inbound_id: int, email: str, new_expiry_time: int, client_id: str, total_gb: int, sub_id: str
-) -> Optional[bool]:
+) -> bool | None:
"""
Обновляет срок действия ключа клиента.
diff --git a/database.py b/database.py
index b526d510..2492ddc6 100644
--- a/database.py
+++ b/database.py
@@ -1567,1447 +1567,4 @@ async def get_all_keys(session: Any = None):
raise
finally:
if conn is not None and session is None:
- await conn.close()
-
-
-import json
-from datetime import datetime
-from typing import Any
-
-import asyncpg
-import pytz
-
-from config import DATABASE_URL, REFERRAL_BONUS_PERCENTAGES
-from logger import logger
-
-
-async def create_temporary_data(session, tg_id: int, state: str, data: dict):
- """Сохраняет временные данные пользователя."""
- await session.execute(
- """
- INSERT INTO temporary_data (tg_id, state, data, updated_at)
- VALUES ($1, $2, $3, $4)
- ON CONFLICT (tg_id)
- DO UPDATE SET state = $2, data = $3, updated_at = $4
- """,
- tg_id,
- state,
- json.dumps(data),
- datetime.utcnow(),
- )
-
-
-async def get_temporary_data(session, tg_id: int) -> dict | None:
- """Извлекает временные данные пользователя."""
- result = await session.fetchrow("SELECT state, data FROM temporary_data WHERE tg_id = $1", tg_id)
- if result:
- return {"state": result["state"], "data": json.loads(result["data"])}
- return None
-
-
-async def clear_temporary_data(session, tg_id: int):
- # TODO rename delete_temporary_data
- await session.execute("DELETE FROM temporary_data WHERE tg_id = $1", tg_id)
-
-
-async def create_blocked_user(tg_id: int, conn: asyncpg.Connection):
- await conn.execute(
- "INSERT INTO blocked_users (tg_id) VALUES ($1) ON CONFLICT (tg_id) DO NOTHING",
- tg_id,
- )
-
-
-async def delete_blocked_user(tg_id: int | list[int], conn: asyncpg.Connection):
- """
- Удаляет пользователя или список пользователей из списка заблокированных.
-
- :param tg_id: ID пользователя Telegram или список ID
- :param conn: Подключение к базе данных
- """
- if isinstance(tg_id, list):
- await conn.execute("DELETE FROM blocked_users WHERE tg_id = ANY($1)", tg_id)
- else:
- await conn.execute("DELETE FROM blocked_users WHERE tg_id = $1", tg_id)
-
-
-async def init_db(file_path: str = "assets/schema.sql"):
- with open(file_path) as file:
- sql_content = file.read()
-
- statements = [stmt.strip() for stmt in sql_content.split(";") if stmt.strip()]
- conn = await asyncpg.connect(DATABASE_URL)
-
- try:
- for statement in statements:
- await conn.execute(statement)
- except Exception as e:
- logger.error(f"Error while executing SQL statement: {e}")
- finally:
- logger.info("Tables created successfully")
- await conn.close()
-
-
-async def check_unique_server_name(server_name: str, session: Any, cluster_name: str | None = None) -> bool:
- """
- Проверяет уникальность имени сервера.
-
- :param server_name: Имя сервера.
- :param session: Сессия базы данных.
- :param cluster_name: Имя кластера (опционально).
- :return: True, если имя сервера уникально, False, если уже существует.
- """
- if cluster_name:
- result = await session.fetchrow(
- "SELECT 1 FROM servers WHERE server_name = $1 AND cluster_name = $2 LIMIT 1", server_name, cluster_name
- )
- else:
- result = await session.fetchrow("SELECT 1 FROM servers WHERE server_name = $1 LIMIT 1", server_name)
-
- return result is None
-
-
-async def create_coupon(coupon_code: str, amount: float, usage_limit: int, session: Any):
- """
- Создает новый купон в базе данных.
-
- Args:
- coupon_code (str): Уникальный код купона.
- amount (float): Сумма, которую дает купон.
- usage_limit (int): Максимальное количество использований купона.
- session (Any): Сессия базы данных для выполнения запроса.
-
- Raises:
- Exception: В случае ошибки при создании купона.
-
- Example:
- await create_coupon('SALE50', 50.0, 5, session)
- """
- try:
- await session.execute(
- """
- INSERT INTO coupons (code, amount, usage_limit, usage_count, is_used)
- VALUES ($1, $2, $3, 0, FALSE)
- """,
- coupon_code,
- amount,
- usage_limit,
- )
- logger.info(f"Успешно создан купон с кодом {coupon_code} на сумму {amount}")
- except Exception as e:
- logger.error(f"Ошибка при создании купона {coupon_code}: {e}")
- raise
-
-
-async def get_coupon_by_code(coupon_code: str, session: Any) -> dict | None:
- """
- Получает информацию о купоне по его коду.
-
- Args:
- coupon_code (str): Код купона для поиска
- session (Any): Сессия базы данных
-
- Returns:
- dict | None: Словарь с информацией о купоне или None, если купон не найден
- - id (int): ID купона
- - usage_limit (int): Лимит использований
- - usage_count (int): Текущее количество использований
- - is_used (bool): Флаг использования
- - amount (float): Сумма купона
-
- Raises:
- Exception: В случае ошибки при выполнении запроса
- """
- try:
- result = await session.fetchrow(
- """
- SELECT id, usage_limit, usage_count, is_used, amount
- FROM coupons
- WHERE code = $1 AND (usage_count < usage_limit OR usage_limit = 0) AND is_used = FALSE
- """,
- coupon_code,
- )
- return dict(result) if result else None
- except Exception as e:
- logger.error(f"Ошибка при получении купона {coupon_code}: {e}")
- raise
-
-
-async def get_all_coupons(session: Any, page: int = 1, per_page: int = 10):
- """
- Получает список купонов из базы данных с пагинацией.
-
- Args:
- session (Any): Сессия базы данных для выполнения запроса
- page (int): Номер страницы (по умолчанию 1)
- per_page (int): Количество купонов на странице (по умолчанию 10)
-
- Returns:
- dict: Словарь с информацией о купонах и пагинации:
- - coupons (list): Список словарей с информацией о купонах
- - total (int): Общее количество купонов
- - pages (int): Общее количество страниц
- - current_page (int): Текущая страница
-
- Raises:
- Exception: В случае ошибки при получении данных из базы
- """
- try:
- offset = (page - 1) * per_page
- coupons = await session.fetch(
- """
- SELECT code, amount, usage_limit, usage_count
- FROM coupons
- ORDER BY id
- LIMIT $1 OFFSET $2
- """,
- per_page,
- offset,
- )
-
- total_count = await session.fetchval("SELECT COUNT(*) FROM coupons")
- total_pages = -(-total_count // per_page) # Округление вверх
-
- logger.info(f"Успешно получено {len(coupons)} купонов из базы данных (страница {page})")
-
- return {"coupons": coupons, "total": total_count, "pages": total_pages, "current_page": page}
- except Exception as e:
- logger.error(f"Критическая ошибка при получении списка купонов: {e}")
- logger.exception("Трассировка стека ошибки получения купонов")
- return {"coupons": [], "total": 0, "pages": 0, "current_page": page}
-
-
-async def delete_coupon(coupon_code: str, session: Any):
- """
- Удаляет купон из базы данных по его коду.
-
- Args:
- coupon_code (str): Код купона для удаления
- session (Any): Сессия базы данных для выполнения запроса
-
- Returns:
- bool: True, если купон успешно удален, False если купон не найден или произошла ошибка
-
- Raises:
- Exception: В случае ошибки при выполнении запроса к базе данных
-
- Example:
- result = await delete_coupon('SALE50', session)
- """
- try:
- coupon_record = await session.fetchrow(
- """
- SELECT id FROM coupons WHERE code = $1
- """,
- coupon_code,
- )
-
- if not coupon_record:
- logger.info(f"Купон {coupon_code} не найден в базе данных")
- return False
-
- await session.execute(
- """
- DELETE FROM coupons WHERE code = $1
- """,
- coupon_code,
- )
-
- logger.info(f"Купон {coupon_code} успешно удален из базы данных")
- return True
-
- except Exception as e:
- logger.error(f"Произошла ошибка при удалении купона {coupon_code}: {e}")
- return False
-
-
-async def set_trial(tg_id: int, status: int, session: Any):
- """
- Устанавливает статус триального периода для пользователя.
-
- Args:
- tg_id (int): Telegram ID пользователя
- status (int): Статус триального периода (0 - доступен, 1 - использован)
- session (Any): Сессия базы данных
-
- Returns:
- bool: True, если статус успешно установлен, False в случае ошибки
- """
- try:
- await session.execute(
- """
- INSERT INTO connections (tg_id, trial)
- VALUES ($1, $2)
- ON CONFLICT (tg_id)
- DO UPDATE SET trial = $2
- """,
- tg_id,
- status,
- )
- status_text = "восстановлен" if status == 0 else "использован"
- logger.info(f"Триальный период успешно {status_text} для пользователя {tg_id}")
- return True
- except Exception as e:
- logger.error(f"Ошибка при установке статуса триального периода для пользователя {tg_id}: {e}")
- return False
-
-
-async def add_connection(tg_id: int, balance: float = 0.0, trial: int = 0, session: Any = None):
- """
- Добавляет новое подключение для пользователя в базу данных.
-
- Args:
- tg_id (int): Telegram ID пользователя
- balance (float, optional): Начальный баланс пользователя. По умолчанию 0.0.
- trial (int, optional): Статус триального периода. По умолчанию 0.
- session (Any, optional): Сессия базы данных.
-
- Raises:
- Exception: Если возникает ошибка при добавлении подключения в базу данных.
- """
- try:
- await session.execute(
- """
- INSERT INTO connections (tg_id, balance, trial)
- VALUES ($1, $2, $3)
- """,
- tg_id,
- balance,
- trial,
- )
- logger.info(
- f"Успешно добавлено новое подключение для пользователя {tg_id} с балансом {balance} и статусом триала {trial}"
- )
- except Exception as e:
- logger.error(f"Не удалось добавить подключение для пользователя {tg_id}. Причина: {e}")
- raise
-
-
-async def check_connection_exists(tg_id: int):
- """
- Проверяет существование подключения для указанного пользователя в базе данных.
-
- Args:
- tg_id (int): Telegram ID пользователя для проверки.
-
- Returns:
- bool: True, если подключение существует, иначе False.
-
- Raises:
- Exception: В случае ошибки при подключении к базе данных.
- """
- try:
- conn = await asyncpg.connect(DATABASE_URL)
- exists = await conn.fetchval(
- """
- SELECT EXISTS(SELECT 1 FROM connections WHERE tg_id = $1)
- """,
- tg_id,
- )
- logger.info(
- f"Проверка существования подключения для пользователя {tg_id}: {'найдено' if exists else 'не найдено'}"
- )
- return exists
- except Exception as e:
- logger.error(f"Ошибка при проверке подключения для пользователя {tg_id}: {e}")
- raise
- finally:
- if conn:
- await conn.close()
-
-
-async def store_key(
- tg_id: int,
- client_id: str,
- email: str,
- expiry_time: int,
- key: str,
- server_id: str,
- session: Any,
-):
- """
- Сохраняет информацию о ключе в базу данных.
-
- Args:
- tg_id (int): Telegram ID пользователя
- client_id (str): Уникальный идентификатор клиента
- email (str): Электронная почта или имя устройства
- expiry_time (int): Время истечения ключа в миллисекундах
- key (str): Ключ доступа
- server_id (str): Идентификатор сервера
-
- Raises:
- Exception: Если возникает ошибка при сохранении ключа в базу данных
- """
- try:
- await session.execute(
- """
- INSERT INTO keys (tg_id, client_id, email, created_at, expiry_time, key, server_id)
- VALUES ($1, $2, $3, $4, $5, $6, $7)
- """,
- tg_id,
- client_id,
- email,
- int(datetime.utcnow().timestamp() * 1000),
- expiry_time,
- key,
- server_id,
- )
- logger.info(f"Ключ успешно сохранен для пользователя {tg_id} на сервере {server_id}")
- except Exception as e:
- logger.error(f"Ошибка при сохранении ключа для пользователя {tg_id}: {e}")
- raise
-
-
-async def get_keys(tg_id: int, session: Any):
- """
- Получает список ключей для указанного пользователя.
-
- Args:
- tg_id (int): Telegram ID пользователя
-
- Returns:
- list: Список записей ключей с информацией о клиенте, электронной почте, времени создания и ключе
-
- Raises:
- Exception: В случае ошибки при подключении к базе данных или выполнении запроса
- """
- try:
- records = await session.fetch(
- """
- SELECT client_id, email, created_at, key
- FROM keys
- WHERE tg_id = $1
- """,
- tg_id,
- )
- logger.info(f"Успешно получено {len(records)} ключей для пользователя {tg_id}")
- return records
- except Exception as e:
- logger.error(f"Ошибка при получении ключей для пользователя {tg_id}: {e}")
- raise
-
-
-async def get_keys_by_server(tg_id: int | None, server_id: str, session: Any):
- """
- Получает список ключей на определенном сервере. Если tg_id=None, возвращает все ключи на сервере.
-
- Args:
- tg_id (int | None): Telegram ID пользователя или None для всех пользователей
- server_id (str): Идентификатор сервера
-
- Returns:
- list: Список записей ключей с информацией о клиенте, электронной почте, времени создания и ключе
-
- Raises:
- Exception: В случае ошибки при подключении к базе данных или выполнении запроса
- """
- try:
- if tg_id is not None:
- records = await session.fetch(
- """
- SELECT *
- FROM keys
- WHERE tg_id = $1 AND server_id = $2
- """,
- tg_id,
- server_id,
- )
- logger.info(f"Успешно получено {len(records)} ключей для пользователя {tg_id} на сервере {server_id}")
- else:
- records = await session.fetch(
- """
- SELECT *
- FROM keys
- WHERE server_id = $1
- """,
- server_id,
- )
- logger.info(f"Успешно получено {len(records)} ключей на сервере {server_id}")
-
- return records
- except Exception as e:
- error_msg = f"Ошибка при получении ключей на сервере {server_id}"
- if tg_id is not None:
- error_msg += f" для пользователя {tg_id}"
- logger.error(f"{error_msg}: {e}")
- raise
-
-
-async def get_balance(tg_id: int) -> float:
- """
- Получает баланс пользователя из базы данных.
-
- Args:
- tg_id (int): Telegram ID пользователя
-
- Returns:
- float: Баланс пользователя, 0.0 если баланс не найден
-
- Raises:
- Exception: В случае ошибки при подключении к базе данных или выполнении запроса
- """
- conn = None
- try:
- conn = await asyncpg.connect(DATABASE_URL)
- balance = await conn.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id)
- logger.info(f"Получен баланс для пользователя {tg_id}: {balance}")
- return round(balance, 1) if balance is not None else 0.0
- except Exception as e:
- logger.error(f"Ошибка при получении баланса для пользователя {tg_id}: {e}")
- return 0.0
- finally:
- if conn:
- await conn.close()
-
-
-async def update_balance(tg_id: int, amount: float, session: Any = None):
- """
- Обновляет баланс пользователя в базе данных.
-
- Args:
- tg_id (int): Telegram ID пользователя
- amount (float): Сумма для обновления баланса
- session (Any, optional): Сессия базы данных. Если не передана, создается новая.
-
- Raises:
- Exception: В случае ошибки при подключении к базе данных или обновлении баланса
- """
- conn = None
- try:
- if session is None:
- conn = await asyncpg.connect(DATABASE_URL)
- session = conn
-
- await session.execute(
- """
- UPDATE connections
- SET balance = balance + $1
- WHERE tg_id = $2
- """,
- amount,
- tg_id,
- )
- logger.info(f"Баланс пользователя {tg_id} обновлен на сумму {amount}")
-
- await handle_referral_on_balance_update(tg_id, amount)
-
- except Exception as e:
- logger.error(f"Ошибка при обновлении баланса для пользователя {tg_id}: {e}")
- raise
- finally:
- if conn is not None:
- await conn.close()
-
-
-async def get_trial(tg_id: int, session: Any) -> int:
- """
- Получает статус триала для пользователя из базы данных.
-
- Args:
- tg_id (int): Telegram ID пользователя
- session (Any): Сессия базы данных
-
- Returns:
- int: Статус триала (0 - не использован, 1 - использован)
- """
- try:
- trial = await session.fetchval("SELECT trial FROM connections WHERE tg_id = $1", tg_id)
- logger.info(f"Получен статус триала для пользователя {tg_id}: {trial}")
- return trial if trial is not None else 0
- except Exception as e:
- logger.error(f"Ошибка при получении статуса триала для пользователя {tg_id}: {e}")
- return 0
-
-
-async def get_key_count(tg_id: int) -> int:
- """
- Получает количество ключей для указанного пользователя.
-
- Args:
- tg_id (int): Telegram ID пользователя
-
- Returns:
- int: Количество ключей пользователя, 0 если ключей нет
-
- Raises:
- Exception: В случае ошибки при подключении к базе данных
- """
- conn = None
- try:
- conn = await asyncpg.connect(DATABASE_URL)
- count = await conn.fetchval("SELECT COUNT(*) FROM keys WHERE tg_id = $1", tg_id)
- logger.info(f"Получено количество ключей для пользователя {tg_id}: {count}")
- return count if count is not None else 0
- except Exception as e:
- logger.error(f"Ошибка при получении количества ключей для пользователя {tg_id}: {e}")
- return 0
- finally:
- if conn:
- await conn.close()
-
-
-async def add_referral(referred_tg_id: int, referrer_tg_id: int, session: Any):
- try:
- if referred_tg_id == referrer_tg_id:
- logger.warning(f"Пользователь {referred_tg_id} попытался использовать свою собственную реферальную ссылку.")
- return
-
- await session.execute(
- """
- INSERT INTO referrals (referred_tg_id, referrer_tg_id)
- VALUES ($1, $2)
- """,
- referred_tg_id,
- referrer_tg_id,
- )
- logger.info(f"Добавлена реферальная связь: приглашенный {referred_tg_id}, пригласивший {referrer_tg_id}")
- except Exception as e:
- logger.error(f"Ошибка при добавлении реферала: {e}")
- raise
-
-
-async def handle_referral_on_balance_update(tg_id: int, amount: float):
- """
- Обработка многоуровневой реферальной системы при обновлении баланса пользователя.
-
- Метод анализирует цепочку рефералов для указанного пользователя и начисляет
- бонусы реферерам на разных уровнях согласно настроенным процентам.
-
- Args:
- tg_id (int): Идентификатор Telegram пользователя, пополнившего баланс
- amount (float): Сумма пополнения баланса
- """
- conn = None
- try:
- conn = await asyncpg.connect(DATABASE_URL)
- logger.info(f"Начало обработки реферальной системы для пользователя {tg_id}")
-
- MAX_REFERRAL_LEVELS = len(REFERRAL_BONUS_PERCENTAGES.keys())
- if MAX_REFERRAL_LEVELS == 0:
- logger.warning("Реферальные бонусы отключены.")
- return
-
- visited_tg_ids = set()
- current_tg_id = tg_id
- referral_chain = []
-
- for level in range(1, MAX_REFERRAL_LEVELS + 1):
- if current_tg_id in visited_tg_ids:
- logger.warning(f"Обнаружен цикл в реферальной цепочке для пользователя {current_tg_id}. Прекращение.")
- break
-
- visited_tg_ids.add(current_tg_id)
-
- referral = await conn.fetchrow(
- """
- SELECT referrer_tg_id
- FROM referrals
- WHERE referred_tg_id = $1
- """,
- current_tg_id,
- )
-
- if not referral:
- logger.info(f"Цепочка рефералов завершена на уровне {level}.")
- break
-
- referrer_tg_id = referral["referrer_tg_id"]
-
- if referrer_tg_id in visited_tg_ids:
- logger.warning(f"Реферер {referrer_tg_id} уже обработан. Пропуск.")
- break
-
- referral_chain.append({"tg_id": referrer_tg_id, "level": level})
- current_tg_id = referrer_tg_id
-
- for referral in referral_chain:
- referrer_tg_id = referral["tg_id"]
- level = referral["level"]
-
- bonus_percent = REFERRAL_BONUS_PERCENTAGES.get(level, 0)
- if bonus_percent <= 0:
- logger.warning(f"Процент бонуса для уровня {level} равен 0. Пропуск.")
- continue
-
- bonus = round(amount * bonus_percent, 2)
-
- if bonus > 0:
- logger.info(f"Начисление бонуса {bonus} рублей рефереру {referrer_tg_id} на уровне {level}.")
- await update_balance(referrer_tg_id, bonus)
-
- except Exception as e:
- logger.error(f"Ошибка при обработке многоуровневой реферальной системы для {tg_id}: {e}")
- finally:
- if conn:
- await conn.close()
-
-
-async def get_referral_stats(referrer_tg_id: int):
- conn = None
- try:
- conn = await asyncpg.connect(DATABASE_URL)
- logger.info(
- f"Установлено подключение к базе данных для получения статистики рефералов пользователя {referrer_tg_id}"
- )
-
- total_referrals = await conn.fetchval(
- """
- SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1
- """,
- referrer_tg_id,
- )
- logger.debug(f"Получено общее количество рефералов: {total_referrals}")
-
- active_referrals = await conn.fetchval(
- """
- SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1 AND reward_issued = TRUE
- """,
- referrer_tg_id,
- )
- logger.debug(f"Получено количество активных рефералов: {active_referrals}")
-
- MAX_REFERRAL_LEVELS = len(REFERRAL_BONUS_PERCENTAGES.keys())
-
- referrals_by_level_records = await conn.fetch(
- f"""
- WITH RECURSIVE referral_levels AS (
- SELECT referred_tg_id, referrer_tg_id, 1 AS level
- FROM referrals
- WHERE referrer_tg_id = $1
-
- UNION
-
- SELECT r.referred_tg_id, r.referrer_tg_id, rl.level + 1
- FROM referrals r
- JOIN referral_levels rl ON r.referrer_tg_id = rl.referred_tg_id
- WHERE rl.level < {MAX_REFERRAL_LEVELS}
- )
- SELECT level,
- COUNT(*) AS level_count,
- COUNT(CASE WHEN reward_issued = TRUE THEN 1 END) AS active_level_count
- FROM referral_levels rl
- JOIN referrals r ON rl.referred_tg_id = r.referred_tg_id
- GROUP BY level
- ORDER BY level
- """,
- referrer_tg_id,
- )
-
- referrals_by_level = {
- record["level"]: {
- "total": record["level_count"],
- "active": record["active_level_count"],
- }
- for record in referrals_by_level_records
- }
- logger.debug(f"Получена статистика рефералов по уровням: {referrals_by_level}")
-
- total_referral_bonus = await conn.fetchval(
- f"""
- WITH RECURSIVE referral_levels AS (
- SELECT
- referred_tg_id,
- referrer_tg_id,
- 1 AS level
- FROM referrals
- WHERE referrer_tg_id = $1
-
- UNION
-
- SELECT
- r.referred_tg_id,
- r.referrer_tg_id,
- rl.level + 1
- FROM referrals r
- JOIN referral_levels rl ON r.referrer_tg_id = rl.referred_tg_id
- WHERE rl.level < {MAX_REFERRAL_LEVELS}
- )
- SELECT
- COALESCE(SUM(p.amount * (
- CASE
- {" ".join([f"WHEN rl.level = {level} THEN {REFERRAL_BONUS_PERCENTAGES[level]}" for level in REFERRAL_BONUS_PERCENTAGES])}
- ELSE 0
- END)), 0) AS total_bonus
- FROM referral_levels rl
- JOIN payments p ON rl.referred_tg_id = p.tg_id
- WHERE p.status = 'success' AND rl.level <= {MAX_REFERRAL_LEVELS}
- """,
- referrer_tg_id,
- )
-
- logger.debug(f"Получена общая сумма бонусов от рефералов: {total_referral_bonus}")
-
- return {
- "total_referrals": total_referrals,
- "active_referrals": active_referrals,
- "referrals_by_level": referrals_by_level,
- "total_referral_bonus": total_referral_bonus,
- }
-
- except Exception as e:
- logger.error(f"Ошибка при получении статистики рефералов для пользователя {referrer_tg_id}: {e}")
- raise
- finally:
- if conn:
- await conn.close()
- logger.info("Закрытие подключения к базе данных")
-
-
-async def update_key_expiry(client_id: str, new_expiry_time: int, session: Any):
- """
- Обновление времени истечения ключа для указанного клиента.
-
- Args:
- client_id (str): Уникальный идентификатор клиента
- new_expiry_time (int): Новое время истечения ключа
-
- Raises:
- Exception: В случае ошибки при подключении к базе данных или обновлении ключа
- """
- try:
- await session.execute(
- """
- UPDATE keys
- SET expiry_time = $1, notified = FALSE, notified_24h = FALSE
- WHERE client_id = $2
- """,
- new_expiry_time,
- client_id,
- )
- logger.info(f"Успешно обновлено время истечения ключа для клиента {client_id}")
-
- except Exception as e:
- logger.error(f"Ошибка при обновлении времени истечения ключа для клиента {client_id}: {e}")
- raise
-
-
-async def add_balance_to_client(client_id: str, amount: float):
- """
- Добавление баланса клиенту по его идентификатору Telegram.
-
- Args:
- client_id (str): Идентификатор клиента в Telegram
- amount (float): Сумма для пополнения баланса
-
- Raises:
- Exception: В случае ошибки при подключении к базе данных или обновлении баланса
- """
- conn = None
- try:
- conn = await asyncpg.connect(DATABASE_URL)
- logger.info(f"Установлено подключение к базе данных для пополнения баланса клиента {client_id}")
-
- await conn.execute(
- """
- UPDATE connections
- SET balance = balance + $1
- WHERE tg_id = $2
- """,
- amount,
- client_id,
- )
- logger.info(f"Успешно пополнен баланс клиента {client_id} на сумму {amount}")
-
- except Exception as e:
- logger.error(f"Ошибка при пополнении баланса для клиента {client_id}: {e}")
- raise
- finally:
- if conn:
- await conn.close()
- logger.info("Закрытие подключения к базе данных")
-
-
-async def get_client_id_by_email(email: str):
- """
- Получение идентификатора клиента по электронной почте.
-
- Args:
- email (str): Электронная почта клиента
-
- Returns:
- str: Идентификатор клиента или None, если клиент не найден
-
- Raises:
- Exception: В случае ошибки при подключении к базе данных или выполнении запроса
- """
- conn = None
- try:
- conn = await asyncpg.connect(DATABASE_URL)
- logger.info(f"Установлено подключение к базе данных для поиска client_id по email: {email}")
-
- client_id = await conn.fetchval(
- """
- SELECT client_id FROM keys WHERE email = $1
- """,
- email,
- )
-
- if client_id:
- logger.info(f"Найден client_id для email: {email}")
- else:
- logger.warning(f"Не найден client_id для email: {email}")
-
- return client_id
-
- except Exception as e:
- logger.error(f"Ошибка при получении client_id для email {email}: {e}")
- raise
- finally:
- if conn:
- await conn.close()
- logger.info("Закрытие подключения к базе данных")
-
-
-async def get_tg_id_by_client_id(client_id: str):
- """
- Получение Telegram ID по идентификатору клиента.
-
- Args:
- client_id (str): Идентификатор клиента
-
- Returns:
- int или None: Telegram ID клиента, если найден, иначе None
-
- Raises:
- Exception: В случае ошибки при подключении к базе данных или выполнении запроса
- """
- conn = None
- try:
- conn = await asyncpg.connect(DATABASE_URL)
- logger.info(f"Установлено подключение к базе данных для поиска Telegram ID по client_id: {client_id}")
-
- result = await conn.fetchrow("SELECT tg_id FROM keys WHERE client_id = $1", client_id)
-
- if result:
- logger.info(f"Найден Telegram ID для client_id: {client_id}")
- return result["tg_id"]
- else:
- logger.warning(f"Не найден Telegram ID для client_id: {client_id}")
- return None
-
- except Exception as e:
- logger.error(f"Ошибка при получении Telegram ID для client_id {client_id}: {e}")
- raise
- finally:
- if conn:
- await conn.close()
- logger.info("Закрытие подключения к базе данных")
-
-
-async def upsert_user(
- tg_id: int,
- username: str = None,
- first_name: str = None,
- last_name: str = None,
- language_code: str = None,
- is_bot: bool = False,
-):
- """
- Обновляет или вставляет информацию о пользователе в базу данных.
-
- Args:
- tg_id (int): Идентификатор пользователя в Telegram
- username (str, optional): Имя пользователя в Telegram
- first_name (str, optional): Имя пользователя
- last_name (str, optional): Фамилия пользователя
- language_code (str, optional): Код языка пользователя
- is_bot (bool, optional): Флаг, указывающий является ли пользователь ботом
-
- Raises:
- Exception: В случае ошибки при работе с базой данных
- """
- conn = None
- try:
- conn = await asyncpg.connect(DATABASE_URL)
- logger.info(f"Установлено подключение к базе данных для обновления пользователя {tg_id}")
-
- await conn.execute(
- """
- INSERT INTO users (tg_id, username, first_name, last_name, language_code, is_bot, created_at, updated_at)
- VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
- ON CONFLICT (tg_id) DO UPDATE
- SET
- username = COALESCE(EXCLUDED.username, users.username),
- first_name = COALESCE(EXCLUDED.first_name, users.first_name),
- last_name = COALESCE(EXCLUDED.last_name, users.last_name),
- language_code = COALESCE(EXCLUDED.language_code, users.language_code),
- is_bot = EXCLUDED.is_bot,
- updated_at = CURRENT_TIMESTAMP
- """,
- tg_id,
- username,
- first_name,
- last_name,
- language_code,
- is_bot,
- )
- logger.info(f"Успешно обновлена информация о пользователе {tg_id}")
- except Exception as e:
- logger.error(f"Ошибка при обновлении информации о пользователе {tg_id}: {e}")
- raise
- finally:
- if conn:
- await conn.close()
- logger.info("Закрытие подключения к базе данных")
-
-
-async def add_payment(tg_id: int, amount: float, payment_system: str):
- """
- Добавляет информацию о платеже в базу данных.
-
- Args:
- tg_id (int): Идентификатор пользователя в Telegram
- amount (float): Сумма платежа
- payment_system (str): Система оплаты
-
- Raises:
- Exception: В случае ошибки при добавлении платежа
- """
- conn = None
- try:
- conn = await asyncpg.connect(DATABASE_URL)
- logger.info(f"Установлено подключение к базе данных для добавления платежа пользователя {tg_id}")
-
- await conn.execute(
- """
- INSERT INTO payments (tg_id, amount, payment_system, status)
- VALUES ($1, $2, $3, 'success')
- """,
- tg_id,
- amount,
- payment_system,
- )
- logger.info(f"Успешно добавлен платеж для пользователя {tg_id} на сумму {amount}")
- except Exception as e:
- logger.error(f"Ошибка при добавлении платежа для пользователя {tg_id}: {e}")
- raise
- finally:
- if conn:
- await conn.close()
- logger.info("Закрытие подключения к базе данных после добавления платежа")
-
-
-async def add_notification(tg_id: int, notification_type: str, session: Any):
- """
- Добавляет запись о notification в базу данных.
-
- Args:
- tg_id (int): Идентификатор пользователя в Telegram
- notification_type (str): Тип уведомления
- session (Any): Сессия базы данных для выполнения запроса
-
- Raises:
- Exception: В случае ошибки при добавлении notification
- """
- try:
- await session.execute(
- """
- INSERT INTO notifications (tg_id, notification_type)
- VALUES ($1, $2)
- ON CONFLICT (tg_id, notification_type)
- DO UPDATE SET last_notification_time = NOW()
- """,
- tg_id,
- notification_type,
- )
- logger.info(f"Успешно добавлено уведомление типа {notification_type} для пользователя {tg_id}")
- except Exception as e:
- logger.error(f"Ошибка при добавлении notification для пользователя {tg_id}: {e}")
- raise
-
-
-async def check_notification_time(tg_id: int, notification_type: str, hours: int = 12, session: Any = None) -> bool:
- """
- Проверяет, прошло ли указанное количество часов с момента последнего уведомления.
-
- Args:
- tg_id (int): Идентификатор пользователя в Telegram
- notification_type (str): Тип уведомления
- hours (int, optional): Количество часов для проверки. По умолчанию 12.
- session (Any): Сессия базы данных для выполнения запроса
-
- Returns:
- bool: True, если с момента последнего уведомления прошло больше указанного времени, иначе False
-
- Raises:
- Exception: В случае ошибки при проверке времени уведомления
- """
- conn = None
- try:
- conn = session if session is not None else await asyncpg.connect(DATABASE_URL)
-
- result = await conn.fetchval(
- """
- SELECT
- CASE
- WHEN MAX(last_notification_time) IS NULL THEN TRUE
- WHEN NOW() - MAX(last_notification_time) > ($1 * INTERVAL '1 hour') THEN TRUE
- ELSE FALSE
- END AS can_notify
- FROM notifications
- WHERE tg_id = $2 AND notification_type = $3
- """,
- hours,
- tg_id,
- notification_type,
- )
-
- can_notify = result if result is not None else True
-
- logger.info(
- f"Проверка уведомления типа {notification_type} для пользователя {tg_id}: {'можно отправить' if can_notify else 'слишком рано'}"
- )
-
- return can_notify
-
- except Exception as e:
- logger.error(f"Ошибка при проверке времени уведомления для пользователя {tg_id}: {e}")
- return False
-
- finally:
- if conn is not None and session is None:
- await conn.close()
-
-
-async def get_servers(session: Any = None):
- conn = None
- try:
- conn = session if session is not None else await asyncpg.connect(DATABASE_URL)
-
- result = await conn.fetch(
- """
- SELECT cluster_name, server_name, api_url, subscription_url, inbound_id
- FROM servers
- """
- )
- servers = {}
- for row in result:
- cluster_name = row["cluster_name"]
- if cluster_name not in servers:
- servers[cluster_name] = []
-
- servers[cluster_name].append(
- {
- "server_name": row["server_name"],
- "api_url": row["api_url"],
- "subscription_url": row["subscription_url"],
- "inbound_id": row["inbound_id"],
- }
- )
-
- return servers
-
- finally:
- if conn is not None and session is None:
- await conn.close()
-
-
-async def delete_user_data(session: Any, tg_id: int):
- try:
- await session.execute("DELETE FROM gifts WHERE sender_tg_id = $1 OR recipient_tg_id = $1", tg_id)
- except Exception as e:
- logger.warning(f"У Вас версия без подарков для {tg_id}: {e}")
- await session.execute("DELETE FROM payments WHERE tg_id = $1", tg_id)
- await session.execute("DELETE FROM users WHERE tg_id = $1", tg_id)
- await session.execute("DELETE FROM connections WHERE tg_id = $1", tg_id)
- await delete_key(tg_id, session)
- await session.execute("DELETE FROM referrals WHERE referrer_tg_id = $1", tg_id)
-
-
-async def store_gift_link(
- gift_id: str,
- sender_tg_id: int,
- selected_months: int,
- expiry_time: datetime,
- gift_link: str,
- session: Any = None,
-):
- """
- Добавляет информацию о подарке в базу данных.
-
- Args:
- gift_id (str): Уникальный идентификатор подарка
- sender_tg_id (int): Идентификатор пользователя, который отправил подарок
- selected_months (int): Количество месяцев подписки
- expiry_time (datetime): Время окончания подписки
- gift_link (str): Ссылка для активации подарка
- session (Any): Сессия базы данных для выполнения запроса
-
- Returns:
- bool: True, если информация о подарке успешно добавлена, иначе False
-
- Raises:
- Exception: В случае ошибки при сохранении информации о подарке
- """
- conn = None
- try:
- conn = session if session is not None else await asyncpg.connect(DATABASE_URL)
-
- result = await conn.execute(
- """
- INSERT INTO gifts (gift_id, sender_tg_id, recipient_tg_id, selected_months, expiry_time, gift_link, created_at, is_used)
- VALUES ($1, $2, NULL, $3, $4, $5, $6, FALSE)
- """,
- gift_id,
- sender_tg_id,
- selected_months,
- expiry_time,
- gift_link,
- datetime.utcnow(),
- )
-
- if result:
- logger.info(f"Подарок с ID {gift_id} успешно добавлен в базу данных.")
- return True
- else:
- logger.error(f"Не удалось добавить подарок с ID {gift_id} в базу данных.")
- return False
- except Exception as e:
- logger.error(f"Ошибка при сохранении подарка с ID {gift_id} в базе данных: {e}")
- return False
-
- finally:
- if conn is not None and session is None:
- await conn.close()
-
-
-async def get_key_details(email, session):
- record = await session.fetchrow(
- """
- SELECT k.key, k.email, k.expiry_time, k.server_id,k, k.client_id, k.created_at, c.tg_id, c.balance
- FROM keys k
- JOIN connections c ON k.tg_id = c.tg_id
- WHERE k.email = $1
- """,
- email,
- )
-
- if not record:
- return None
-
- cluster_name = record["server_id"]
-
- moscow_tz = pytz.timezone("Europe/Moscow")
- expiry_date = datetime.fromtimestamp(record["expiry_time"] / 1000, tz=moscow_tz)
- current_date = datetime.now(moscow_tz)
- time_left = expiry_date - current_date
-
- if time_left.total_seconds() <= 0:
- days_left_message = "Ключ истек."
- elif time_left.days > 0:
- days_left_message = f"Осталось дней: {time_left.days}"
- else:
- hours_left = time_left.seconds // 3600
- days_left_message = f"Осталось часов: {hours_left}"
-
- return {
- "key": record["key"],
- "created_at": record["created_at"],
- "expiry_time": record["expiry_time"],
- "client_id": record["client_id"],
- "expiry_date": expiry_date.strftime("%d %B %Y года %H:%M"),
- "days_left_message": days_left_message,
- "server_name": cluster_name,
- "balance": record["balance"],
- "tg_id": record["tg_id"],
- "email": record["email"],
- }
-
-
-async def delete_key(identifier, session):
- """
- Удаляет ключ из базы данных по client_id или tg_id
-
- Args:
- identifier (str): client_id или tg_id для удаления
- session: Сессия базы данных
-
- Raises:
- Exception: В случае ошибки при удалении ключа
- """
- try:
- # Проверяем, является ли идентификатор числом (tg_id) или строкой (client_id)
- if identifier.isdigit():
- query = "DELETE FROM keys WHERE tg_id = $1"
- else:
- query = "DELETE FROM keys WHERE client_id = $1"
-
- await session.execute(query, identifier)
- logger.info(f"Ключ с идентификатором {identifier} успешно удалён")
- except Exception as e:
- logger.error(f"Ошибка при удалении ключа с идентификатором {identifier} из базы данных: {e}")
-
-
-async def create_server(
- cluster_name: str, server_name: str, api_url: str, subscription_url: str, inbound_id: int, session: Any
-):
- """
- Добавляет новый сервер в базу данных.
-
- Args:
- cluster_name (str): Название кластера
- server_name (str): Название сервера
- api_url (str): URL API сервера
- subscription_url (str): URL подписки
- inbound_id (int): ID входящего подключения
- session (Any): Сессия базы данных
-
- Raises:
- Exception: В случае ошибки при добавлении сервера
- """
- try:
- await session.execute(
- """
- INSERT INTO servers (cluster_name, server_name, api_url, subscription_url, inbound_id)
- VALUES ($1, $2, $3, $4, $5)
- """,
- cluster_name,
- server_name,
- api_url,
- subscription_url,
- inbound_id,
- )
- logger.info(f"Сервер {server_name} успешно добавлен в кластер {cluster_name}")
- except Exception as e:
- logger.error(f"Ошибка при добавлении сервера {server_name} в кластер {cluster_name}: {e}")
- raise
-
-
-async def delete_server(server_name: str, session: Any):
- """
- Удаляет сервер из базы данных по его названию.
-
- Args:
- server_name (str): Название сервера для удаления
- session (Any): Сессия базы данных
-
- Raises:
- Exception: В случае ошибки при удалении сервера
- """
- try:
- await session.execute(
- """
- DELETE FROM servers WHERE server_name = $1
- """,
- server_name,
- )
- logger.info(f"Сервер {server_name} успешно удалён из базы данных")
- except Exception as e:
- logger.error(f"Ошибка при удалении сервера {server_name} из базы данных: {e}")
- raise
-
-
-async def create_coupon_usage(coupon_id: int, user_id: int, session: Any):
- """
- Создаёт запись об использовании купона в базе данных.
-
- Args:
- coupon_id (int): ID купона
- user_id (int): ID пользователя
- session (Any): Сессия базы данных
-
- Raises:
- Exception: В случае ошибки при создании записи
- """
- try:
- await session.execute(
- """
- INSERT INTO coupon_usages (coupon_id, user_id, used_at)
- VALUES ($1, $2, $3)
- """,
- coupon_id,
- user_id,
- datetime.utcnow(),
- )
- logger.info(f"Создана запись об использовании купона {coupon_id} пользователем {user_id}")
- except Exception as e:
- logger.error(f"Ошибка при создании записи об использовании купона {coupon_id} пользователем {user_id}: {e}")
- raise
-
-
-async def check_coupon_usage(coupon_id: int, user_id: int, session: Any) -> bool:
- """
- Проверяет, использовал ли пользователь данный купон.
-
- Args:
- coupon_id (int): ID купона для проверки
- user_id (int): ID пользователя для проверки
- session (Any): Сессия базы данных
-
- Returns:
- bool: True если купон уже использован, False если нет
-
- Raises:
- Exception: В случае ошибки при выполнении запроса
- """
- try:
- result = await session.fetchrow(
- """
- SELECT 1 FROM coupon_usages WHERE coupon_id = $1 AND user_id = $2
- """,
- coupon_id,
- user_id,
- )
- return result is not None
- except Exception as e:
- logger.error(f"Ошибка при проверке использования купона {coupon_id} пользователем {user_id}: {e}")
- raise
-
-
-async def update_coupon_usage_count(coupon_id: int, session: Any):
- """
- Обновляет счетчик использования купона и его статус.
-
- Args:
- coupon_id (int): ID купона для обновления
- session (Any): Сессия базы данных
-
- Raises:
- Exception: В случае ошибки при обновлении данных купона
- """
- try:
- await session.execute(
- """
- UPDATE coupons
- SET usage_count = usage_count + 1,
- is_used = CASE WHEN usage_count + 1 >= usage_limit AND usage_limit > 0 THEN TRUE ELSE FALSE END
- WHERE id = $1
- """,
- coupon_id,
- )
- logger.info(f"Успешно обновлен счетчик использования купона {coupon_id}")
- except Exception as e:
- logger.error(f"Ошибка при обновлении счетчика использования купона {coupon_id}: {e}")
- raise
-
-
-async def get_last_payments(tg_id: int, session: Any):
- """
- Получает последние 3 платежа пользователя.
-
- Args:
- tg_id (int): Telegram ID пользователя
- session (Any): Сессия базы данных
-
- Returns:
- list: Список последних платежей пользователя
-
- Raises:
- Exception: В случае ошибки при выполнении запроса
- """
- try:
- records = await session.fetch(
- """
- SELECT amount, payment_system, status, created_at
- FROM payments
- WHERE tg_id = $1
- ORDER BY created_at DESC
- LIMIT 3
- """,
- tg_id,
- )
- logger.info(f"Успешно получены последние платежи для пользователя {tg_id}")
- return records
- except Exception as e:
- logger.error(f"Ошибка при получении последних платежей для пользователя {tg_id}: {e}")
- raise
+ await conn.close()
\ No newline at end of file
diff --git a/handlers/admin/admin_bans.py b/handlers/admin/admin_bans.py
index 9ab8e7cf..833f3084 100644
--- a/handlers/admin/admin_bans.py
+++ b/handlers/admin/admin_bans.py
@@ -1,7 +1,7 @@
from typing import Any
from aiogram import F, Router
-from aiogram.types import CallbackQuery, BufferedInputFile
+from aiogram.types import BufferedInputFile, CallbackQuery
from database import delete_user_data
from filters.admin import IsAdminFilter
diff --git a/handlers/admin/admin_coupons.py b/handlers/admin/admin_coupons.py
index b65ea5e4..52c10a4e 100644
--- a/handlers/admin/admin_coupons.py
+++ b/handlers/admin/admin_coupons.py
@@ -4,9 +4,9 @@ from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
-from database import delete_coupon, get_all_coupons, create_coupon
+from database import create_coupon, delete_coupon, get_all_coupons
from filters.admin import IsAdminFilter
-from keyboards.admin.coupons_kb import build_coupons_kb, build_coupons_list_kb, AdminCouponDeleteCallback
+from keyboards.admin.coupons_kb import AdminCouponDeleteCallback, build_coupons_kb, build_coupons_list_kb
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb
from logger import logger
diff --git a/handlers/admin/admin_panel.py b/handlers/admin/admin_panel.py
index eb9eb744..f3d76f87 100644
--- a/handlers/admin/admin_panel.py
+++ b/handlers/admin/admin_panel.py
@@ -5,7 +5,7 @@ from aiogram.types import CallbackQuery
from bot import version
from filters.admin import IsAdminFilter
-from keyboards.admin.panel_kb import build_panel_kb, AdminPanelCallback, build_management_kb
+from keyboards.admin.panel_kb import AdminPanelCallback, build_management_kb, build_panel_kb
router = Router()
diff --git a/handlers/admin/admin_restart.py b/handlers/admin/admin_restart.py
index 58c1a115..d2501758 100644
--- a/handlers/admin/admin_restart.py
+++ b/handlers/admin/admin_restart.py
@@ -4,7 +4,7 @@ from aiogram import F, Router
from aiogram.types import CallbackQuery
from filters.admin import IsAdminFilter
-from keyboards.admin.panel_kb import build_restart_kb, AdminPanelCallback, build_admin_back_kb
+from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb, build_restart_kb
router = Router()
diff --git a/handlers/admin/admin_sender.py b/handlers/admin/admin_sender.py
index 70563437..0be8e6e8 100644
--- a/handlers/admin/admin_sender.py
+++ b/handlers/admin/admin_sender.py
@@ -8,7 +8,7 @@ from aiogram.types import CallbackQuery
from filters.admin import IsAdminFilter
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb
-from keyboards.admin.sender_kb import build_sender_kb, AdminSenderCallback
+from keyboards.admin.sender_kb import AdminSenderCallback, build_sender_kb
from logger import logger
router = Router()
diff --git a/handlers/admin/admin_servers.py b/handlers/admin/admin_servers.py
index 51257e9a..2969e83d 100644
--- a/handlers/admin/admin_servers.py
+++ b/handlers/admin/admin_servers.py
@@ -6,15 +6,15 @@ from py3xui import AsyncApi
from backup import create_backup_and_send_to_admins
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
-from database import create_server, check_unique_server_name, delete_server, get_keys_by_server, get_servers
+from database import check_unique_server_name, delete_server, get_servers
from filters.admin import IsAdminFilter
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb
from keyboards.admin.servers_kb import (
- build_manage_server_kb,
+ AdminServerEditorCallback,
+ build_clusters_editor_kb,
build_delete_server_kb,
build_manage_cluster_kb,
- build_clusters_editor_kb,
- AdminServerEditorCallback,
+ build_manage_server_kb,
)
router = Router()
diff --git a/handlers/admin/admin_user_editor.py b/handlers/admin/admin_user_editor.py
index 2d470b56..0538d245 100644
--- a/handlers/admin/admin_user_editor.py
+++ b/handlers/admin/admin_user_editor.py
@@ -16,8 +16,8 @@ from database import (
get_key_details,
get_keys,
get_servers,
- update_trial,
update_key_expiry,
+ update_trial,
)
from filters.admin import IsAdminFilter
from handlers.keys.key_utils import (
diff --git a/handlers/admin/admin_users.py b/handlers/admin/admin_users.py
index 4601d646..fe76bec5 100644
--- a/handlers/admin/admin_users.py
+++ b/handlers/admin/admin_users.py
@@ -21,17 +21,17 @@ from handlers.keys.key_utils import (
from handlers.utils import sanitize_key_name
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb
from keyboards.admin.users_kb import (
- build_user_edit_kb,
- build_key_edit_kb,
- build_key_delete_kb,
- build_user_delete_kb,
AdminUserEditorCallback,
- build_editor_kb,
- build_users_balance_kb,
- build_users_balance_change_kb,
- build_user_key_kb,
- build_users_key_expiry_kb,
AdminUserKeyEditorCallback,
+ build_editor_kb,
+ build_key_delete_kb,
+ build_key_edit_kb,
+ build_user_delete_kb,
+ build_user_edit_kb,
+ build_user_key_kb,
+ build_users_balance_change_kb,
+ build_users_balance_kb,
+ build_users_key_expiry_kb,
build_users_key_show_kb,
)
from logger import logger
@@ -428,7 +428,7 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext, se
text = f"✅ Время действия ключа изменено на {message.text}"
await change_expiry_time(expiry_time, email, session)
except ValueError:
- text = f"🚫 Пожалуйста, используйте корректный формат даты!"
+ text = "🚫 Пожалуйста, используйте корректный формат даты!"
except Exception as e:
text = f"❗ Произошла ошибка во время изменения времени действия ключа: {e}"
diff --git a/handlers/coupons.py b/handlers/coupons.py
index b652cf1c..0a125da2 100644
--- a/handlers/coupons.py
+++ b/handlers/coupons.py
@@ -1,4 +1,3 @@
-from datetime import datetime
from typing import Any
from aiogram import F, Router, types
diff --git a/handlers/keys/key_management.py b/handlers/keys/key_management.py
index 8264ee79..3330f2da 100644
--- a/handlers/keys/key_management.py
+++ b/handlers/keys/key_management.py
@@ -24,10 +24,10 @@ from config import (
USE_NEW_PAYMENT_FLOW,
)
from database import (
+ create_temporary_data,
get_balance,
get_key_details,
get_trial,
- create_temporary_data,
store_key,
update_balance,
)
diff --git a/handlers/keys/key_utils.py b/handlers/keys/key_utils.py
index 80461348..8de0861b 100644
--- a/handlers/keys/key_utils.py
+++ b/handlers/keys/key_utils.py
@@ -4,7 +4,7 @@ from typing import Any
from py3xui import AsyncApi
from client import ClientConfig, add_client, delete_client, extend_client_key
-from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, SUPERNODE, TOTAL_GB, PUBLIC_LINK
+from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, PUBLIC_LINK, SUPERNODE, TOTAL_GB
from database import get_servers, store_key
from handlers.utils import get_least_loaded_cluster
from logger import logger
diff --git a/handlers/keys/keys.py b/handlers/keys/keys.py
index 7bbf3eb8..5586c164 100644
--- a/handlers/keys/keys.py
+++ b/handlers/keys/keys.py
@@ -10,7 +10,6 @@ import pytz
from aiogram import F, Router, types
from aiogram.types import BufferedInputFile, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
-from handlers.payments.yookassa_pay import process_custom_amount_input
from bot import bot
from config import (
@@ -29,14 +28,13 @@ from config import (
)
from database import (
check_server_name_by_cluster,
+ create_temporary_data,
delete_key,
get_balance,
get_key_details,
get_keys,
get_keys_by_server,
get_servers,
- create_temporary_data,
- store_key,
update_balance,
update_key_expiry,
)
@@ -55,6 +53,7 @@ from handlers.keys.key_utils import (
update_subscription,
)
from handlers.payments.robokassa_pay import handle_custom_amount_input
+from handlers.payments.yookassa_pay import process_custom_amount_input
from handlers.texts import (
DISCOUNTS,
KEY_NOT_FOUND_MSG,
diff --git a/handlers/notifications.py b/handlers/notifications.py
index 35c7c2c6..614ee497 100644
--- a/handlers/notifications.py
+++ b/handlers/notifications.py
@@ -23,9 +23,9 @@ from config import (
TRIAL_TIME,
)
from database import (
- create_blocked_user,
add_notification,
check_notification_time,
+ create_blocked_user,
delete_key,
get_balance,
get_servers,
diff --git a/keyboards/admin/bans_kb.py b/keyboards/admin/bans_kb.py
index 0c5554d4..3c5c5145 100644
--- a/keyboards/admin/bans_kb.py
+++ b/keyboards/admin/bans_kb.py
@@ -1,7 +1,7 @@
from aiogram.types import InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
-from keyboards.admin.panel_kb import build_admin_back_btn, AdminPanelCallback
+from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_btn
def build_bans_kb() -> InlineKeyboardMarkup:
diff --git a/keyboards/admin/panel_kb.py b/keyboards/admin/panel_kb.py
index 6b16151f..2c23381d 100644
--- a/keyboards/admin/panel_kb.py
+++ b/keyboards/admin/panel_kb.py
@@ -1,5 +1,5 @@
from aiogram.filters.callback_data import CallbackData
-from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
+from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
diff --git a/keyboards/admin/stats_kb.py b/keyboards/admin/stats_kb.py
index 3a6e4092..b8046593 100644
--- a/keyboards/admin/stats_kb.py
+++ b/keyboards/admin/stats_kb.py
@@ -1,7 +1,7 @@
from aiogram.types import InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
-from keyboards.admin.panel_kb import build_admin_back_btn, AdminPanelCallback
+from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_btn
def build_stats_kb() -> InlineKeyboardMarkup:
diff --git a/keyboards/admin/users_kb.py b/keyboards/admin/users_kb.py
index 78378f9d..54d056a9 100644
--- a/keyboards/admin/users_kb.py
+++ b/keyboards/admin/users_kb.py
@@ -143,7 +143,7 @@ def build_user_delete_kb(tg_id: int):
def build_user_key_kb(tg_id: int, email: str) -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
builder.button(
- text=f"🔙 Назад", callback_data=AdminUserEditorCallback(action="users_key_edit", tg_id=tg_id, data=email).pack()
+ text="🔙 Назад", callback_data=AdminUserEditorCallback(action="users_key_edit", tg_id=tg_id, data=email).pack()
)
builder.adjust(1)
return builder.as_markup()
diff --git a/servers.py b/servers.py
index 521b55f1..6d29431c 100644
--- a/servers.py
+++ b/servers.py
@@ -9,7 +9,7 @@ from ping3 import ping
from bot import bot
from config import ADMIN_ID, DATABASE_URL, PING_TIME
-from database import create_server, check_unique_server_name, get_servers
+from database import check_unique_server_name, create_server, get_servers
from logger import logger
try: