diff --git a/backup.py b/backup.py index c0d5ffe0..8550e619 100644 --- a/backup.py +++ b/backup.py @@ -97,9 +97,10 @@ 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 + from bot import bot + 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)) diff --git a/client.py b/client.py index 984a662a..219ccca5 100644 --- a/client.py +++ b/client.py @@ -23,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.AsyncApi, config: ClientConfig) -> dict[str, Any]: """ Добавляет клиента на сервер через 3x-ui. @@ -66,7 +66,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 + xui: py3xui.AsyncApi, inbound_id: int, email: str, new_expiry_time: int, client_id: str, total_gb: int, sub_id: str ) -> bool | None: """ Обновляет срок действия ключа клиента. @@ -117,7 +117,7 @@ async def extend_client_key( async def delete_client( - xui: py3xui.API, + xui: py3xui.AsyncApi, inbound_id: int, email: str, client_id: str, diff --git a/handlers/admin/admin_sender.py b/handlers/admin/admin_sender.py index 0be8e6e8..4e8a92cf 100644 --- a/handlers/admin/admin_sender.py +++ b/handlers/admin/admin_sender.py @@ -85,8 +85,8 @@ async def handle_message_input(message: types.Message, state: FSMContext, sessio try: await message.bot.send_message(chat_id=tg_id, text=text_message) success_count += 1 - except Exception: - pass + except Exception as e: + logger.error(e) text = ( f"📤 Рассылка завершена!" diff --git a/handlers/admin/admin_servers.py b/handlers/admin/admin_servers.py index 2969e83d..9632b424 100644 --- a/handlers/admin/admin_servers.py +++ b/handlers/admin/admin_servers.py @@ -1,3 +1,5 @@ +from typing import Any + import asyncpg from aiogram import F, Router, types from aiogram.fsm.context import FSMContext @@ -33,7 +35,7 @@ class AdminServersEditor(StatesGroup): IsAdminFilter(), ) async def handle_servers(callback_query: types.CallbackQuery): - servers = await get_servers_from_db() + servers = await get_servers() text = ( "🔧 Управление кластерами\n\n" @@ -227,8 +229,7 @@ async def handle_inbound_id_input(message: types.Message, state: FSMContext): @router.callback_query(AdminServerEditorCallback.filter(F.action == "clusters_manage"), IsAdminFilter()) async def handle_clusters_manage( - callback_query: types.CallbackQuery, - callback_data: AdminServerEditorCallback, + callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any ): cluster_name = callback_data.data @@ -242,7 +243,9 @@ async def handle_clusters_manage( @router.callback_query(AdminServerEditorCallback.filter(F.action == "servers_availability"), IsAdminFilter()) -async def handle_servers_availability(callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback): +async def handle_servers_availability( + callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any +): cluster_name = callback_data.data servers = await get_servers(session) @@ -279,7 +282,7 @@ async def handle_servers_availability(callback_query: types.CallbackQuery, callb @router.callback_query(AdminServerEditorCallback.filter(F.action == "servers_manage"), IsAdminFilter()) async def handle_servers_manage(callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback): server_name = callback_data.data - servers = await get_servers_from_db() + servers = await get_servers() cluster_name, server = next( ((c, s) for c, cs in servers.items() for s in cs if s["server_name"] == server_name), (None, None) @@ -316,7 +319,9 @@ async def handle_servers_delete(callback_query: types.CallbackQuery, callback_da @router.callback_query(AdminServerEditorCallback.filter(F.action == "servers_delete_confirm"), IsAdminFilter()) -async def handle_servers_delete_confirm(callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback): +async def handle_servers_delete_confirm( + callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any +): server_name = callback_data.data await delete_server(server_name, session) @@ -350,8 +355,7 @@ async def handle_servers_add( @router.callback_query(AdminServerEditorCallback.filter(F.action == "clusters_backup"), IsAdminFilter()) async def handle_clusters_backup( - callback_query: types.CallbackQuery, - callback_data: AdminServerEditorCallback, + callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any ): cluster_name = callback_data.data diff --git a/handlers/admin/admin_user_editor.py b/handlers/admin/admin_user_editor.py index 0538d245..4d4ff573 100644 --- a/handlers/admin/admin_user_editor.py +++ b/handlers/admin/admin_user_editor.py @@ -11,6 +11,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder from config import TOTAL_GB from database import ( + delete_key, delete_user_data, get_client_id_by_email, get_key_details, @@ -22,7 +23,6 @@ from database import ( from filters.admin import IsAdminFilter from handlers.keys.key_utils import ( delete_key_from_cluster, - delete_key_from_db, renew_key_in_cluster, ) from handlers.utils import sanitize_key_name @@ -415,7 +415,7 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext, se async def update_key_on_all_servers(): tasks = [] for cluster_name, cluster_servers in clusters.items(): - for server in cluster_servers: + for _server in cluster_servers: tasks.append( asyncio.create_task( renew_key_in_cluster( @@ -493,12 +493,12 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery, s async def delete_key_from_servers(email, client_id): tasks = [] for cluster_name, cluster_servers in clusters.items(): - for server in cluster_servers: + for _server in cluster_servers: tasks.append(delete_key_from_cluster(cluster_name, email, client_id)) await asyncio.gather(*tasks) await delete_key_from_servers(email, client_id) - await delete_key_from_db(client_id, session) + await delete_key(client_id, session) await callback_query.message.answer(response_message, reply_markup=builder.as_markup()) else: @@ -570,7 +570,7 @@ async def delete_user(callback_query: types.CallbackQuery, session: Any): tasks = [] for email, client_id in key_records: servers = await get_servers(session) - for cluster_id, cluster in servers.items(): + for cluster_id, _cluster in servers.items(): tasks.append(delete_key_from_cluster(cluster_id, email, client_id)) await asyncio.gather(*tasks) except Exception as e: diff --git a/handlers/admin/admin_users.py b/handlers/admin/admin_users.py index fe76bec5..8ad42bea 100644 --- a/handlers/admin/admin_users.py +++ b/handlers/admin/admin_users.py @@ -10,11 +10,10 @@ from aiogram.fsm.state import State, StatesGroup from aiogram.types import CallbackQuery from config import TOTAL_GB -from database import delete_user_data, get_client_id_by_email, get_servers_from_db, restore_trial, update_key_expiry +from database import delete_key, delete_user_data, get_client_id_by_email, get_servers, update_key_expiry, update_trial from filters.admin import IsAdminFilter from handlers.keys.key_utils import ( delete_key_from_cluster, - delete_key_from_db, renew_key_in_cluster, update_subscription, ) @@ -168,7 +167,7 @@ async def handle_trial_restore( ): tg_id = callback_data.tg_id - await restore_trial(tg_id, session) + await update_trial(tg_id, 0, session) await callback_query.message.edit_text(text="✅ Триал успешно восстановлен!", reply_markup=build_editor_kb(tg_id)) @@ -479,7 +478,7 @@ async def handle_delete_key_confirm( if record: client_id = record["client_id"] - clusters = await get_servers_from_db() + clusters = await get_servers() async def delete_key_from_servers(): tasks = [] @@ -489,7 +488,7 @@ async def handle_delete_key_confirm( await asyncio.gather(*tasks) await delete_key_from_servers() - await delete_key_from_db(client_id, session) + await delete_key(client_id, session) await callback_query.message.edit_text(text="✅ Ключ успешно удален.", reply_markup=kb) else: @@ -515,8 +514,8 @@ async def handle_delete_user_confirm( try: tasks = [] for email, client_id in key_records: - servers = await get_servers_from_db() - for cluster_id, cluster in servers.items(): + servers = await get_servers() + for cluster_id, _cluster in servers.items(): tasks.append(delete_key_from_cluster(cluster_id, email, client_id)) await asyncio.gather(*tasks) except Exception as e: @@ -619,7 +618,7 @@ async def change_expiry_time(expiry_time: int, email: str, session: Any) -> Exce if not server_id: return ValueError(f"User with client_id {server_id} was not found") - clusters = await get_servers_from_db() + clusters = await get_servers() async def update_key_on_all_servers(): tasks = [ diff --git a/handlers/captcha.py b/handlers/captcha.py index fd67d534..5730f45f 100644 --- a/handlers/captcha.py +++ b/handlers/captcha.py @@ -1,4 +1,5 @@ import random +import secrets from typing import Any from aiogram import F, Router @@ -7,7 +8,6 @@ from aiogram.types import CallbackQuery, Message from aiogram.utils.keyboard import InlineKeyboardBuilder from config import CAPTCHA_EMOJIS -from handlers.start import start_command from logger import logger router = Router() @@ -27,7 +27,7 @@ async def generate_captcha(message: Message, state: FSMContext): } """ # Выбираем случайный эмодзи и его описание из конфига - correct_emoji, correct_text = random.choice(list(CAPTCHA_EMOJIS.items())) + correct_emoji, correct_text = secrets.choice(list(CAPTCHA_EMOJIS.items())) # Получаем 3 случайных неправильных эмодзи wrong_emojis = random.sample([e for e in CAPTCHA_EMOJIS.keys() if e != correct_emoji], 3) @@ -54,7 +54,8 @@ async def generate_captcha(message: Message, state: FSMContext): @router.callback_query(F.data.startswith("captcha_")) async def check_captcha(callback: CallbackQuery, state: FSMContext, session: Any, admin: bool): - """Проверяет ответ пользователя на капчу""" + from handlers.start import start_command + selected_emoji = callback.data.split("captcha_")[1] state_data = await state.get_data() correct_emoji = state_data.get("correct_emoji") diff --git a/handlers/instructions/instructions.py b/handlers/instructions/instructions.py index dd373ccb..89053024 100644 --- a/handlers/instructions/instructions.py +++ b/handlers/instructions/instructions.py @@ -57,7 +57,6 @@ async def send_instructions( @router.callback_query(F.data.startswith("connect_pc|")) async def process_connect_pc(callback_query: types.CallbackQuery, session: Any): - tg_id = callback_query.message.chat.id key_name = callback_query.data.split("|")[1] record = await get_key_details(key_name, session) diff --git a/handlers/keys/keys.py b/handlers/keys/keys.py index 5586c164..018d3f1e 100644 --- a/handlers/keys/keys.py +++ b/handlers/keys/keys.py @@ -48,7 +48,6 @@ from handlers.buttons.add_subscribe import ( ) from handlers.keys.key_utils import ( delete_key_from_cluster, - delete_key_from_db, renew_key_in_cluster, update_subscription, ) @@ -354,7 +353,7 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery, s async def delete_key_from_servers(): try: tasks = [] - for cluster_id, cluster in servers.items(): + for cluster_id, _cluster in servers.items(): tasks.append(delete_key_from_cluster(cluster_id, email, client_id)) await asyncio.gather(*tasks) @@ -364,7 +363,7 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery, s asyncio.create_task(delete_key_from_servers()) - await delete_key_from_db(client_id, session) + await delete_key(client_id, session) else: response_message = "Ключ не найден или уже удален." diff --git a/handlers/notifications.py b/handlers/notifications.py index 614ee497..f99cc6c3 100644 --- a/handlers/notifications.py +++ b/handlers/notifications.py @@ -2,6 +2,7 @@ import asyncio import os from datetime import datetime, timedelta +import aiofiles import asyncpg import pytz from aiogram import Bot, Router, types @@ -169,10 +170,11 @@ async def process_10h_record(record, bot, conn): ) if os.path.isfile(image_path): - with open(image_path, "rb") as image_file: + async with aiofiles.open(image_path, "rb") as image_file: + image_data = await image_file.read() await bot.send_photo( tg_id, - photo=BufferedInputFile(image_file.read(), filename="notify_10h.jpg"), + photo=BufferedInputFile(image_data, filename="notify_10h.jpg"), caption=KEY_RENEWED.format(email=email), reply_markup=keyboard, ) @@ -258,10 +260,11 @@ async def process_24h_record(record, bot, conn): ) if os.path.isfile(image_path): - with open(image_path, "rb") as image_file: + async with aiofiles.open(image_path, "rb") as image_file: + image_data = await image_file.read() await bot.send_photo( tg_id, - photo=BufferedInputFile(image_file.read(), filename="notify_24h.jpg"), + photo=BufferedInputFile(image_data, filename="notify_24h.jpg"), caption=KEY_RENEWED.format(email=email), reply_markup=keyboard, ) @@ -286,10 +289,11 @@ async def send_renewal_notification(bot, tg_id, email, message, conn, client_id, image_path = os.path.join("img", "notify_24h.jpg") if os.path.isfile(image_path): - with open(image_path, "rb") as image_file: + async with aiofiles.open(image_path, "rb") as image_file: + image_data = await image_file.read() await bot.send_photo( tg_id, - photo=BufferedInputFile(image_file.read(), filename="notify_24h.jpg"), + photo=BufferedInputFile(image_data, filename="notify_24h.jpg"), caption=message, reply_markup=keyboard.as_markup(), ) @@ -298,7 +302,7 @@ async def send_renewal_notification(bot, tg_id, email, message, conn, client_id, logger.info(f"Уведомление отправлено пользователю {tg_id}.") - await conn.execute(f"UPDATE keys SET {flag} = TRUE WHERE client_id = $1", client_id) + await conn.execute("UPDATE keys SET notified_24h = $1 WHERE client_id = $2", flag, client_id) except Exception as e: logger.error(f"Ошибка при отправке уведомления пользователю {tg_id}: {e}") @@ -456,10 +460,11 @@ async def process_key(record, bot, conn): try: if os.path.isfile(image_path): - with open(image_path, "rb") as image_file: + async with aiofiles.open(image_path, "rb") as image_file: + image_data = await image_file.read() await bot.send_photo( tg_id, - photo=BufferedInputFile(image_file.read(), filename="notify_expired.jpg"), + photo=BufferedInputFile(image_data, filename="notify_expired.jpg"), caption=KEY_RENEWED.format(email=email), reply_markup=keyboard, ) @@ -473,10 +478,11 @@ async def process_key(record, bot, conn): message_expired = f"Ваша подписка {email} истекла. Пополните баланс для продления." try: if os.path.isfile(image_path): - with open(image_path, "rb") as image_file: + async with aiofiles.open(image_path, "rb") as image_file: + image_data = await image_file.read() await bot.send_photo( tg_id, - photo=BufferedInputFile(image_file.read(), filename="notify_expired.jpg"), + photo=BufferedInputFile(image_data, filename="notify_expired.jpg"), caption=message_expired, reply_markup=keyboard, ) @@ -511,7 +517,7 @@ async def process_key(record, bot, conn): async def check_online_users(): servers = await get_servers() - for cluster_id, cluster in servers.items(): + for _cluster_id, cluster in servers.items(): for server_id, server in enumerate(cluster): xui = AsyncApi(server["api_url"], username=ADMIN_USERNAME, password=ADMIN_PASSWORD) await xui.login() diff --git a/handlers/utils.py b/handlers/utils.py index 29d525f7..57339821 100644 --- a/handlers/utils.py +++ b/handlers/utils.py @@ -1,6 +1,8 @@ import json import random import re +import secrets +import string import aiohttp import asyncpg @@ -49,7 +51,7 @@ def generate_random_email(length: int = 6) -> str: Returns: str: Сгенерированная случайная строка. """ - return "".join(random.choices("abcdefghijklmnopqrstuvwxyz0123456789", k=length)) + return "".join(secrets.choice(string.ascii_lowercase + string.digits) for _ in range(length)) if length > 0 else "" async def get_least_loaded_cluster() -> str: diff --git a/middlewares/delete.py b/middlewares/delete.py index af20634f..b305c7bb 100644 --- a/middlewares/delete.py +++ b/middlewares/delete.py @@ -9,6 +9,7 @@ from keyboards.admin.panel_kb import AdminPanelCallback from keyboards.admin.sender_kb import AdminSenderCallback from keyboards.admin.servers_kb import AdminServerEditorCallback from keyboards.admin.users_kb import AdminUserEditorCallback, AdminUserKeyEditorCallback +from logger import logger pass_callbacks = [ AdminPanelCallback, @@ -31,8 +32,8 @@ class DeleteMessageMiddleware(BaseMiddleware): if not event.text or not event.text.startswith("/start"): try: await event.bot.delete_message(event.chat.id, event.message_id - 1) - except Exception: - pass + except Exception as e: + logger.error(e) await event.delete() if isinstance(event, CallbackQuery): diff --git a/pyproject.toml b/pyproject.toml index 69cbd0f1..634f9533 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [tool.ruff] line-length = 120 -target-version = "py310" +target-version = "py312" [tool.ruff.lint] select = ["E", "F", "W", "I", "N", "UP", "ANN", "ASYNC", "S", "BLE", "FBT", "B", "A", "C4", "DTZ", "T10", "ISC", "ICN", "G", "PIE"] -ignore = ["ANN101", "ANN102", "S101",'ANN201','ANN001','BLE001'] +ignore = ["ANN101", "ANN102", "S101",'ANN201','ANN001','BLE001','W291','ANN401','DTZ003','DTZ005','F401','FBT002','FBT001','FBT003','A005','E501','UP017','DTZ004','W293','ANN202','DTZ007'] exclude = [ ".git", "venv", diff --git a/servers.py b/servers.py index 6d29431c..e1f35d53 100644 --- a/servers.py +++ b/servers.py @@ -33,7 +33,7 @@ async def sync_servers_with_db(): logger.info("Подключение к базе данных для синхронизации серверов успешно.") for cluster_name, servers in CLUSTERS.items(): - for server_key, server_info in servers.items(): + for _server_key, server_info in servers.items(): exists = await check_unique_server_name(server_info["name"], conn, cluster_name) if not exists: