diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 00000000..410b1bac --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,35 @@ +name: Code Formatting + +on: [push, pull_request] + +jobs: + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install ruff + + - name: Run Ruff formatter with pyproject.toml + run: ruff format . --config pyproject.toml --exclude main.py,handlers/payments + + - name: Check for changes + id: check_changes + run: | + git diff --exit-code || echo "changes_detected=true" >> $GITHUB_OUTPUT + + - name: Commit and push changes + if: steps.check_changes.outputs.changes_detected == 'true' + run: | + git config --global user.name 'GitHub Actions' + git config --global user.email 'actions@github.com' + git add . + git commit -m "Auto-format code with Ruff using pyproject.toml" + git push \ No newline at end of file diff --git a/Makefile b/Makefile index aef0b00f..d949db7d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ formatting: - @echo "Running Ruff..." && ruff check . --fix - @echo "Running Ruff format..." && ruff format . + @echo "Running Ruff format..." && ruff format . --config pyproject.toml --exclude main.py,handlers/payments + + @echo "Running Ruff..." && ruff check . --config pyproject.toml --exclude main.py,handlers/payments --fix lint: - @echo "Running Ruff checks..." && ruff check . \ No newline at end of file + @echo "Running Ruff checks..." && ruff check . --config pyproject.toml --exclude main.py,handlers/payments \ No newline at end of file diff --git a/backup.py b/backup.py index e44b45f1..c97c4364 100644 --- a/backup.py +++ b/backup.py @@ -59,9 +59,7 @@ def _create_database_backup(): async def _send_backup_to_admin(bot, backup_file_path): try: with open(backup_file_path, "rb") as backup_file: - backup_input_file = BufferedInputFile( - backup_file.read(), filename=os.path.basename(backup_file_path) - ) + backup_input_file = BufferedInputFile(backup_file.read(), filename=os.path.basename(backup_file_path)) admin_ids: int | list[int] = ADMIN_ID if isinstance(admin_ids, list): for id in admin_ids: diff --git a/bot.py b/bot.py index 99596357..006efeb4 100644 --- a/bot.py +++ b/bot.py @@ -44,7 +44,7 @@ async def errors_handler( filename=f"error_{event.update.update_id}.txt", ), caption=f"{hbold(type(event.exception).__name__)}: {str(event.exception)[:1021]}...", - ) + ) except TelegramBadRequest as exception: logger.warning(f"Failed to send error details: {exception}") except Exception as exception: diff --git a/client.py b/client.py index 10953586..f0561bc3 100644 --- a/client.py +++ b/client.py @@ -15,7 +15,7 @@ async def add_client( enable: bool, flow: str, inbound_id: int, - sub_id + sub_id, ): """ Adds a client to the server via 3x-ui. @@ -45,9 +45,7 @@ async def add_client( error_message = str(e) if "Duplicate email" in error_message: - logger.warning( - f"Дублированный email: {email}. Пропуск. Сообщение: {error_message}" - ) + logger.warning(f"Дублированный email: {email}. Пропуск. Сообщение: {error_message}") return {"status": "duplicate", "email": email} logger.error(f"Ошибка при добавлении клиента {email}: {error_message}") @@ -55,7 +53,7 @@ async def add_client( async def extend_client_key( - xui, inbound_id, email: str, new_expiry_time: int, client_id: str, total_gb: int, sub_id = str + xui, inbound_id, email: str, new_expiry_time: int, client_id: str, total_gb: int, sub_id=str ): """ Функция для обновления срока действия ключа клиента по email. @@ -72,9 +70,7 @@ async def extend_client_key( logger.warning(f"Ошибка: клиент {email} не имеет действительного ID.") return - logger.info( - f"Обновление ключа клиента {client.email} с ID {client.id} до нового времени: {new_expiry_time}" - ) + logger.info(f"Обновление ключа клиента {client.email} с ID {client.id} до нового времени: {new_expiry_time}") client.id = client_id client.expiry_time = new_expiry_time @@ -87,9 +83,7 @@ async def extend_client_key( await xui.client.update(client.id, client) await xui.client.reset_stats(inbound_id, email) - logger.info( - f"Ключ клиента {client.email} успешно продлён до {new_expiry_time}." - ) + logger.info(f"Ключ клиента {client.email} успешно продлён до {new_expiry_time}.") except Exception as e: logger.error(f"Ошибка при обновлении клиента с email {email}: {e}") diff --git a/database.py b/database.py index 92db1b6e..a2d1a9ca 100644 --- a/database.py +++ b/database.py @@ -26,9 +26,7 @@ async def save_temporary_data(session, tg_id: int, state: str, data: dict): 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 - ) + 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 @@ -71,18 +69,14 @@ async def check_unique_server_name(server_name: str) -> bool: """ conn = await asyncpg.connect(DATABASE_URL) - result = await conn.fetchrow( - "SELECT 1 FROM servers WHERE server_name = $1 LIMIT 1", server_name - ) + result = await conn.fetchrow("SELECT 1 FROM servers WHERE server_name = $1 LIMIT 1", server_name) await conn.close() return result is None -async def create_coupon( - coupon_code: str, amount: float, usage_limit: int, session: Any -): +async def create_coupon(coupon_code: str, amount: float, usage_limit: int, session: Any): """ Создает новый купон в базе данных. @@ -142,7 +136,8 @@ async def get_all_coupons(session: Any, page: int = 1, per_page: int = 10): ORDER BY id LIMIT $1 OFFSET $2 """, - per_page, offset + per_page, + offset, ) total_count = await session.fetchval("SELECT COUNT(*) FROM coupons") @@ -150,12 +145,7 @@ async def get_all_coupons(session: Any, page: int = 1, per_page: int = 10): logger.info(f"Успешно получено {len(coupons)} купонов из базы данных (страница {page})") - return { - "coupons": coupons, - "total": total_count, - "pages": total_pages, - "current_page": page - } + return {"coupons": coupons, "total": total_count, "pages": total_pages, "current_page": page} except Exception as e: logger.error(f"Критическая ошибка при получении списка купонов: {e}") logger.exception("Трассировка стека ошибки получения купонов") @@ -230,9 +220,7 @@ async def restore_trial(tg_id: int, session: Any): logger.info(f"Триальный период успешно восстановлен для пользователя {tg_id}") return True except Exception as e: - logger.error( - f"Ошибка при восстановлении триального периода для пользователя {tg_id}: {e}" - ) + logger.error(f"Ошибка при восстановлении триального периода для пользователя {tg_id}: {e}") return False @@ -264,9 +252,7 @@ async def use_trial(tg_id: int, session: Any): return False -async def add_connection( - tg_id: int, balance: float = 0.0, trial: int = 0, session: Any = None -): +async def add_connection(tg_id: int, balance: float = 0.0, trial: int = 0, session: Any = None): """ Добавляет новое подключение для пользователя в базу данных. @@ -293,9 +279,7 @@ async def add_connection( f"Успешно добавлено новое подключение для пользователя {tg_id} с балансом {balance} и статусом триала {trial}" ) except Exception as e: - logger.error( - f"Не удалось добавить подключение для пользователя {tg_id}. Причина: {e}" - ) + logger.error(f"Не удалось добавить подключение для пользователя {tg_id}. Причина: {e}") raise @@ -369,9 +353,7 @@ async def store_key( key, server_id, ) - logger.info( - f"Ключ успешно сохранен для пользователя {tg_id} на сервере {server_id}" - ) + logger.info(f"Ключ успешно сохранен для пользователя {tg_id} на сервере {server_id}") except Exception as e: logger.error(f"Ошибка при сохранении ключа для пользователя {tg_id}: {e}") raise @@ -437,14 +419,10 @@ async def get_keys_by_server(tg_id: int, server_id: str): tg_id, server_id, ) - logger.info( - f"Успешно получено {len(records)} ключей для пользователя {tg_id} на сервере {server_id}" - ) + logger.info(f"Успешно получено {len(records)} ключей для пользователя {tg_id} на сервере {server_id}") return records except Exception as e: - logger.error( - f"Ошибка при получении ключей для пользователя {tg_id} на сервере {server_id}: {e}" - ) + logger.error(f"Ошибка при получении ключей для пользователя {tg_id} на сервере {server_id}: {e}") raise finally: if conn: @@ -468,14 +446,10 @@ async def has_active_key(tg_id: int) -> bool: 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}" - ) + logger.info(f"Проверка наличия ключей для пользователя {tg_id}. Найдено ключей: {count}") return count > 0 except Exception as e: - logger.error( - f"Ошибка при проверке наличия ключей для пользователя {tg_id}: {e}" - ) + logger.error(f"Ошибка при проверке наличия ключей для пользователя {tg_id}: {e}") raise finally: if conn: @@ -498,9 +472,7 @@ async def get_balance(tg_id: int) -> float: conn = None try: conn = await asyncpg.connect(DATABASE_URL) - balance = await conn.fetchval( - "SELECT balance FROM connections WHERE tg_id = $1", tg_id - ) + balance = await conn.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id) logger.info(f"Получен баланс для пользователя {tg_id}: {balance}") return balance if balance is not None else 0.0 except Exception as e: @@ -558,15 +530,11 @@ async def get_trial(tg_id: int, session: Any) -> int: int: Статус триала (0 - не использован, 1 - использован) """ try: - trial = await session.fetchval( - "SELECT trial FROM connections WHERE tg_id = $1", tg_id - ) + 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}" - ) + logger.error(f"Ошибка при получении статуса триала для пользователя {tg_id}: {e}") return 0 @@ -590,9 +558,7 @@ async def get_key_count(tg_id: int) -> int: 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}" - ) + logger.error(f"Ошибка при получении количества ключей для пользователя {tg_id}: {e}") return 0 finally: if conn: @@ -623,11 +589,8 @@ async def get_all_users(conn): 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} попытался использовать свою собственную реферальную ссылку." - ) + logger.warning(f"Пользователь {referred_tg_id} попытался использовать свою собственную реферальную ссылку.") return await session.execute( @@ -638,9 +601,7 @@ async def add_referral(referred_tg_id: int, referrer_tg_id: int, session: Any): referred_tg_id, referrer_tg_id, ) - logger.info( - f"Добавлена реферальная связь: приглашенный {referred_tg_id}, пригласивший {referrer_tg_id}" - ) + logger.info(f"Добавлена реферальная связь: приглашенный {referred_tg_id}, пригласивший {referrer_tg_id}") except Exception as e: logger.error(f"Ошибка при добавлении реферала: {e}") raise @@ -673,9 +634,7 @@ async def handle_referral_on_balance_update(tg_id: int, amount: float): for level in range(1, MAX_REFERRAL_LEVELS + 1): if current_tg_id in visited_tg_ids: - logger.warning( - f"Обнаружен цикл в реферальной цепочке для пользователя {current_tg_id}. Прекращение." - ) + logger.warning(f"Обнаружен цикл в реферальной цепочке для пользователя {current_tg_id}. Прекращение.") break visited_tg_ids.add(current_tg_id) @@ -714,15 +673,11 @@ async def handle_referral_on_balance_update(tg_id: int, amount: float): bonus = round(amount * bonus_percent, 2) if bonus > 0: - logger.info( - f"Начисление бонуса {bonus} рублей рефереру {referrer_tg_id} на уровне {level}." - ) + 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}" - ) + logger.error(f"Ошибка при обработке многоуровневой реферальной системы для {tg_id}: {e}") finally: if conn: await conn.close() @@ -821,9 +776,7 @@ async def get_referral_stats(referrer_tg_id: int): ) total_referral_bonus = total_referral_bonus or 0 - logger.debug( - f"Получена общая сумма бонусов от рефералов: {total_referral_bonus}" - ) + logger.debug(f"Получена общая сумма бонусов от рефералов: {total_referral_bonus}") return { "total_referrals": total_referrals, @@ -833,9 +786,7 @@ async def get_referral_stats(referrer_tg_id: int): } except Exception as e: - logger.error( - f"Ошибка при получении статистики рефералов для пользователя {referrer_tg_id}: {e}" - ) + logger.error(f"Ошибка при получении статистики рефералов для пользователя {referrer_tg_id}: {e}") raise finally: if conn: @@ -857,9 +808,7 @@ async def update_key_expiry(client_id: str, new_expiry_time: int): conn = None try: conn = await asyncpg.connect(DATABASE_URL) - logger.info( - f"Установлено подключение к базе данных для обновления времени истечения ключа клиента {client_id}" - ) + logger.info(f"Установлено подключение к базе данных для обновления времени истечения ключа клиента {client_id}") await conn.execute( """ @@ -873,9 +822,7 @@ async def update_key_expiry(client_id: str, new_expiry_time: int): logger.info(f"Успешно обновлено время истечения ключа для клиента {client_id}") except Exception as e: - logger.error( - f"Ошибка при обновлении времени истечения ключа для клиента {client_id}: {e}" - ) + logger.error(f"Ошибка при обновлении времени истечения ключа для клиента {client_id}: {e}") raise finally: if conn: @@ -896,9 +843,7 @@ async def delete_key(client_id: str): conn = None try: conn = await asyncpg.connect(DATABASE_URL) - logger.info( - f"Установлено подключение к базе данных для удаления ключа клиента {client_id}" - ) + logger.info(f"Установлено подключение к базе данных для удаления ключа клиента {client_id}") await conn.execute( """ @@ -932,9 +877,7 @@ async def add_balance_to_client(client_id: str, amount: float): conn = None try: conn = await asyncpg.connect(DATABASE_URL) - logger.info( - f"Установлено подключение к базе данных для пополнения баланса клиента {client_id}" - ) + logger.info(f"Установлено подключение к базе данных для пополнения баланса клиента {client_id}") await conn.execute( """ @@ -972,9 +915,7 @@ async def get_client_id_by_email(email: str): conn = None try: conn = await asyncpg.connect(DATABASE_URL) - logger.info( - f"Установлено подключение к базе данных для поиска client_id по email: {email}" - ) + logger.info(f"Установлено подключение к базе данных для поиска client_id по email: {email}") client_id = await conn.fetchval( """ @@ -1015,13 +956,9 @@ async def get_tg_id_by_client_id(client_id: str): conn = None try: conn = await asyncpg.connect(DATABASE_URL) - logger.info( - f"Установлено подключение к базе данных для поиска Telegram ID по client_id: {client_id}" - ) + logger.info(f"Установлено подключение к базе данных для поиска Telegram ID по client_id: {client_id}") - result = await conn.fetchrow( - "SELECT tg_id FROM keys WHERE client_id = $1", 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}") @@ -1064,9 +1001,7 @@ async def upsert_user( conn = None try: conn = await asyncpg.connect(DATABASE_URL) - logger.info( - f"Установлено подключение к базе данных для обновления пользователя {tg_id}" - ) + logger.info(f"Установлено подключение к базе данных для обновления пользователя {tg_id}") await conn.execute( """ @@ -1113,9 +1048,7 @@ async def add_payment(tg_id: int, amount: float, payment_system: str): conn = None try: conn = await asyncpg.connect(DATABASE_URL) - logger.info( - f"Установлено подключение к базе данных для добавления платежа пользователя {tg_id}" - ) + logger.info(f"Установлено подключение к базе данных для добавления платежа пользователя {tg_id}") await conn.execute( """ @@ -1126,9 +1059,7 @@ async def add_payment(tg_id: int, amount: float, payment_system: str): amount, payment_system, ) - logger.info( - f"Успешно добавлен платеж для пользователя {tg_id} на сумму {amount}" - ) + logger.info(f"Успешно добавлен платеж для пользователя {tg_id} на сумму {amount}") except Exception as e: logger.error(f"Ошибка при добавлении платежа для пользователя {tg_id}: {e}") raise @@ -1161,19 +1092,13 @@ async def add_notification(tg_id: int, notification_type: str, session: Any): tg_id, notification_type, ) - logger.info( - f"Успешно добавлено уведомление типа {notification_type} для пользователя {tg_id}" - ) + logger.info(f"Успешно добавлено уведомление типа {notification_type} для пользователя {tg_id}") except Exception as e: - logger.error( - f"Ошибка при добавлении notification для пользователя {tg_id}: {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: +async def check_notification_time(tg_id: int, notification_type: str, hours: int = 12, session: Any = None) -> bool: """ Проверяет, прошло ли указанное количество часов с момента последнего уведомления. @@ -1218,9 +1143,7 @@ async def check_notification_time( return can_notify except Exception as e: - logger.error( - f"Ошибка при проверке времени уведомления для пользователя {tg_id}: {e}" - ) + logger.error(f"Ошибка при проверке времени уведомления для пользователя {tg_id}: {e}") return False finally: @@ -1259,11 +1182,8 @@ async def get_servers_from_db(): 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 - ) + 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) @@ -1322,7 +1242,6 @@ async def store_gift_link( logger.error(f"Не удалось добавить подарок с ID {gift_id} в базу данных.") return False except Exception as e: - logger.error(f"Ошибка при сохранении подарка с ID {gift_id} в базе данных: {e}") return False diff --git a/filters/admin.py b/filters/admin.py index 95328eaa..5a392217 100644 --- a/filters/admin.py +++ b/filters/admin.py @@ -10,6 +10,5 @@ class IsAdminFilter(BaseFilter): admin_ids: int | list[int] = ADMIN_ID if isinstance(admin_ids, list): return message.from_user.id in admin_ids - return message.from_user.id == admin_ids except Exception: return False diff --git a/handlers/__init__.py b/handlers/__init__.py index 92c8292a..2ac2e777 100644 --- a/handlers/__init__.py +++ b/handlers/__init__.py @@ -3,6 +3,7 @@ __all__ = ("router",) from aiogram import Router from .admin import router as admin_router +from .captcha import router as captcha_router from .coupons import router as coupons_router from .donate import router as donate_router from .instructions import router as instructions_router @@ -13,7 +14,6 @@ from .payments import router as payments_router from .profile import router as profile_router from .start import router as start_router from .user import router as user_router -from .captcha import router as captcha_router router = Router(name="handlers_main_router") diff --git a/handlers/admin/admin_coupons.py b/handlers/admin/admin_coupons.py index 9af0c575..e83c8fcf 100644 --- a/handlers/admin/admin_coupons.py +++ b/handlers/admin/admin_coupons.py @@ -19,25 +19,19 @@ router = Router() @router.callback_query(F.data == "coupons_editor", IsAdminFilter()) -async def show_coupon_management_menu( - callback_query: types.CallbackQuery, state: FSMContext -): +async def show_coupon_management_menu(callback_query: types.CallbackQuery, state: FSMContext): await state.clear() builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="➕ Создать купон", callback_data="create_coupon") - ) + builder.row(InlineKeyboardButton(text="➕ Создать купон", callback_data="create_coupon")) builder.row(InlineKeyboardButton(text="🎟️ Купоны", callback_data="coupons")) builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin")) - await callback_query.message.answer( - "🛠 Меню управления купонами:", reply_markup=builder.as_markup() - ) + await callback_query.message.answer("🛠 Меню управления купонами:", reply_markup=builder.as_markup()) @router.callback_query(F.data.startswith("coupons"), IsAdminFilter()) async def show_coupon_list(callback_query: types.CallbackQuery, session: Any): try: - page = int(callback_query.data.split(':')[1]) if ':' in callback_query.data else 1 + page = int(callback_query.data.split(":")[1]) if ":" in callback_query.data else 1 per_page = 10 result = await get_all_coupons(session, page, per_page) coupons = result["coupons"] @@ -63,15 +57,16 @@ async def show_coupon_list(callback_query: types.CallbackQuery, session: Any): f"🔢 Лимит использования: {coupon['usage_limit']} раз\n" f"✅ Использовано: {coupon['usage_count']} раз\n\n" ) - builder.row(InlineKeyboardButton( - text=f"❌ Удалить {coupon['code']}", - callback_data=f"delete_coupon_{coupon['code']}" - )) + builder.row( + InlineKeyboardButton( + text=f"❌ Удалить {coupon['code']}", callback_data=f"delete_coupon_{coupon['code']}" + ) + ) if current_page > 1: - builder.row(InlineKeyboardButton(text="⬅️ Предыдущая", callback_data=f"coupons:{current_page-1}")) + builder.row(InlineKeyboardButton(text="⬅️ Предыдущая", callback_data=f"coupons:{current_page - 1}")) if current_page < total_pages: - builder.row(InlineKeyboardButton(text="➡️ Следующая", callback_data=f"coupons:{current_page+1}")) + builder.row(InlineKeyboardButton(text="➡️ Следующая", callback_data=f"coupons:{current_page + 1}")) builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor")) await callback_query.message.answer(coupon_list, reply_markup=builder.as_markup()) @@ -154,9 +149,7 @@ async def process_coupon_data(message: types.Message, state: FSMContext, session ) builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor") - ) + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor")) await message.answer(result_message, reply_markup=builder.as_markup()) await state.clear() diff --git a/handlers/admin/admin_panel.py b/handlers/admin/admin_panel.py index d6548d6c..1ee47627 100644 --- a/handlers/admin/admin_panel.py +++ b/handlers/admin/admin_panel.py @@ -40,48 +40,23 @@ async def handle_admin_message(message: types.Message, state: FSMContext): BOT_VERSION = "4.0.0-preAlpha(14)" builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton( - text="📊 Статистика пользователей", callback_data="user_stats" - ) - ) - builder.row( - InlineKeyboardButton( - text="👥 Управление пользователями", callback_data="user_editor" - ) - ) - builder.row( - InlineKeyboardButton( - text="🖥️ Управление серверами", callback_data="servers_editor" - ) - ) - builder.row( - InlineKeyboardButton( - text="🎟️ Управление купонами", callback_data="coupons_editor" - ) - ) - builder.row( - InlineKeyboardButton(text="📢 Массовая рассылка", callback_data="send_to") - ) - builder.row( - InlineKeyboardButton(text="🤖 Управление Ботом", callback_data="bot_management") - ) + builder.row(InlineKeyboardButton(text="📊 Статистика пользователей", callback_data="user_stats")) + builder.row(InlineKeyboardButton(text="👥 Управление пользователями", callback_data="user_editor")) + builder.row(InlineKeyboardButton(text="🖥️ Управление серверами", callback_data="servers_editor")) + builder.row(InlineKeyboardButton(text="🎟️ Управление купонами", callback_data="coupons_editor")) + builder.row(InlineKeyboardButton(text="📢 Массовая рассылка", callback_data="send_to")) + builder.row(InlineKeyboardButton(text="🤖 Управление Ботом", callback_data="bot_management")) builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) await message.answer( - f"🤖 Панель администратора\n\nВерсия бота: {BOT_VERSION}", - reply_markup=builder.as_markup() + f"🤖 Панель администратора\n\nВерсия бота: {BOT_VERSION}", reply_markup=builder.as_markup() ) @router.callback_query(F.data == "bot_management") async def handle_bot_management(callback_query: types.CallbackQuery): builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="💾 Создать резервную копию", callback_data="backups") - ) - builder.row( - InlineKeyboardButton(text="🔄 Перезагрузить бота", callback_data="restart_bot") - ) + builder.row(InlineKeyboardButton(text="💾 Создать резервную копию", callback_data="backups")) + builder.row(InlineKeyboardButton(text="🔄 Перезагрузить бота", callback_data="restart_bot")) builder.row(InlineKeyboardButton(text="🚫 Баны", callback_data="ban_user")) builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="admin")) await callback_query.message.answer( @@ -106,13 +81,9 @@ async def user_stats_menu(callback_query: CallbackQuery, session: Any): total_payments_month = await session.fetchval( "SELECT COALESCE(SUM(amount), 0) FROM payments WHERE created_at >= date_trunc('month', CURRENT_DATE)" ) - total_payments_all_time = await session.fetchval( - "SELECT COALESCE(SUM(amount), 0) FROM payments" - ) + total_payments_all_time = await session.fetchval("SELECT COALESCE(SUM(amount), 0) FROM payments") - registrations_today = await session.fetchval( - "SELECT COUNT(*) FROM users WHERE created_at >= CURRENT_DATE" - ) + registrations_today = await session.fetchval("SELECT COUNT(*) FROM users WHERE created_at >= CURRENT_DATE") registrations_week = await session.fetchval( "SELECT COUNT(*) FROM users WHERE created_at >= date_trunc('week', CURRENT_DATE)" ) @@ -120,9 +91,7 @@ async def user_stats_menu(callback_query: CallbackQuery, session: Any): "SELECT COUNT(*) FROM users WHERE created_at >= date_trunc('month', CURRENT_DATE)" ) - users_updated_today = await session.fetchval( - "SELECT COUNT(*) FROM users WHERE updated_at >= CURRENT_DATE" - ) + users_updated_today = await session.fetchval("SELECT COUNT(*) FROM users WHERE updated_at >= CURRENT_DATE") active_keys = await session.fetchval( "SELECT COUNT(*) FROM keys WHERE expiry_time > $1", @@ -153,27 +122,17 @@ async def user_stats_menu(callback_query: CallbackQuery, session: Any): ) builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="🔄 Обновить", callback_data="user_stats") - ) + builder.row(InlineKeyboardButton(text="🔄 Обновить", callback_data="user_stats")) builder.row( InlineKeyboardButton( text="📥 Выгрузить пользователей в CSV", callback_data="export_users_csv", ) ) - builder.row( - InlineKeyboardButton( - text="📥 Выгрузить оплаты в CSV", callback_data="export_payments_csv" - ) - ) - builder.row( - InlineKeyboardButton(text="🔙 Вернуться в меню", callback_data="admin") - ) + builder.row(InlineKeyboardButton(text="📥 Выгрузить оплаты в CSV", callback_data="export_payments_csv")) + builder.row(InlineKeyboardButton(text="🔙 Вернуться в меню", callback_data="admin")) - await callback_query.message.answer( - stats_message, reply_markup=builder.as_markup() - ) + await callback_query.message.answer(stats_message, reply_markup=builder.as_markup()) except Exception as e: logger.error(f"Error in user_stats_menu: {e}") @@ -200,14 +159,10 @@ async def export_users_csv(callback_query: CallbackQuery, session: Any): ) if not users: - await callback_query.message.answer( - "📭 Нет пользователей для экспорта.", reply_markup=builder.as_markup() - ) + await callback_query.message.answer("📭 Нет пользователей для экспорта.", reply_markup=builder.as_markup()) return - csv_data = ( - "tg_id,username,first_name,last_name,language_code,is_bot,balance,trial\n" - ) + csv_data = "tg_id,username,first_name,last_name,language_code,is_bot,balance,trial\n" for user in users: csv_data += f"{user['tg_id']},{user['username']},{user['first_name']},{user['last_name']},{user['language_code']},{user['is_bot']},{user['balance']},{user['trial']}\n" @@ -253,9 +208,7 @@ async def export_payments_csv(callback_query: CallbackQuery, session: Any): ) if not payments: - await callback_query.message.answer( - "📭 Нет платежей для экспорта.", reply_markup=builder.as_markup() - ) + await callback_query.message.answer("📭 Нет платежей для экспорта.", reply_markup=builder.as_markup()) return csv_data = "tg_id,username,first_name,last_name,amount,payment_system,status,created_at\n" # Заголовки CSV @@ -283,24 +236,10 @@ async def export_payments_csv(callback_query: CallbackQuery, session: Any): @router.callback_query(F.data == "send_to", IsAdminFilter()) async def handle_send_to_all(callback_query: CallbackQuery, state: FSMContext): builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="📢 Отправить всем", callback_data="send_to_all") - ) - builder.row( - InlineKeyboardButton( - text="📢 Отправить с подпиской", callback_data="send_to_subscribed" - ) - ) - builder.row( - InlineKeyboardButton( - text="📢 Отправить без подписки", callback_data="send_to_unsubscribed" - ) - ) - builder.row( - InlineKeyboardButton( - text="📢 Рассылка по кластеру", callback_data="send_to_cluster" - ) - ) + builder.row(InlineKeyboardButton(text="📢 Отправить всем", callback_data="send_to_all")) + builder.row(InlineKeyboardButton(text="📢 Отправить с подпиской", callback_data="send_to_subscribed")) + builder.row(InlineKeyboardButton(text="📢 Отправить без подписки", callback_data="send_to_unsubscribed")) + builder.row(InlineKeyboardButton(text="📢 Рассылка по кластеру", callback_data="send_to_cluster")) builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin")) await callback_query.message.answer( "✍️ Выберите группу пользователей и введите текст сообщения для рассылки:", @@ -311,34 +250,26 @@ async def handle_send_to_all(callback_query: CallbackQuery, state: FSMContext): @router.callback_query(F.data == "send_to_all", IsAdminFilter()) async def handle_send_to_all(callback_query: CallbackQuery, state: FSMContext): await state.update_data(send_to="all") - await callback_query.message.answer( - "✍️ Введите текст сообщения для рассылки всем пользователям:" - ) + await callback_query.message.answer("✍️ Введите текст сообщения для рассылки всем пользователям:") await state.set_state(UserEditorState.waiting_for_message) @router.callback_query(F.data == "send_to_subscribed", IsAdminFilter()) async def handle_send_to_subscribed(callback_query: CallbackQuery, state: FSMContext): await state.update_data(send_to="subscribed") - await callback_query.message.answer( - "✍️ Введите текст сообщения для рассылки пользователям с активной подпиской:" - ) + await callback_query.message.answer("✍️ Введите текст сообщения для рассылки пользователям с активной подпиской:") await state.set_state(UserEditorState.waiting_for_message) @router.callback_query(F.data == "send_to_unsubscribed", IsAdminFilter()) async def handle_send_to_unsubscribed(callback_query: CallbackQuery, state: FSMContext): await state.update_data(send_to="unsubscribed") - await callback_query.message.answer( - "✍️ Введите текст сообщения для рассылки пользователям без активной подписки:" - ) + await callback_query.message.answer("✍️ Введите текст сообщения для рассылки пользователям без активной подписки:") await state.set_state(UserEditorState.waiting_for_message) @router.callback_query(F.data == "send_to_cluster", IsAdminFilter()) -async def handle_send_to_cluster( - callback_query: CallbackQuery, state: FSMContext, session: Any -): +async def handle_send_to_cluster(callback_query: CallbackQuery, state: FSMContext, session: Any): clusters = await session.fetch("SELECT DISTINCT cluster_name FROM servers") builder = InlineKeyboardBuilder() @@ -368,9 +299,7 @@ async def handle_send_cluster(callback_query: CallbackQuery, state: FSMContext): @router.message(UserEditorState.waiting_for_message, IsAdminFilter()) -async def process_message_to_all( - message: types.Message, state: FSMContext, session: Any -): +async def process_message_to_all(message: types.Message, state: FSMContext, session: Any): text_message = message.text try: @@ -424,9 +353,7 @@ async def process_message_to_all( success_count += 1 except Exception as e: error_count += 1 - logger.error( - f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}" - ) + logger.error(f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}") await message.answer( f"📤 Рассылка завершена:\n" @@ -442,13 +369,9 @@ async def process_message_to_all( @router.callback_query(F.data == "backups", IsAdminFilter()) async def handle_backup(callback_query: CallbackQuery, state: FSMContext): - await callback_query.message.answer( - "💾 Инициализация резервного копирования базы данных..." - ) + await callback_query.message.answer("💾 Инициализация резервного копирования базы данных...") await backup_database() - await callback_query.message.answer( - "✅ Резервная копия успешно создана и отправлена администратору." - ) + await callback_query.message.answer("✅ Резервная копия успешно создана и отправлена администратору.") @router.callback_query(F.data == "restart_bot", IsAdminFilter()) @@ -456,9 +379,7 @@ async def handle_restart(callback_query: CallbackQuery, state: FSMContext): await state.set_state(UserEditorState.waiting_for_restart_confirmation) builder = InlineKeyboardBuilder() builder.row( - InlineKeyboardButton( - text="✅ Да, перезапустить", callback_data="confirm_restart" - ), + InlineKeyboardButton(text="✅ Да, перезапустить", callback_data="confirm_restart"), InlineKeyboardButton(text="❌ Нет, отмена", callback_data="admin"), ) builder.row(InlineKeyboardButton(text="🔙 Вернуться в меню", callback_data="admin")) @@ -484,13 +405,9 @@ async def confirm_restart_bot(callback_query: CallbackQuery, state: FSMContext): text=True, ) await state.clear() - await callback_query.message.answer( - "🔄 Бот успешно перезапущен.", reply_markup=builder.as_markup() - ) + await callback_query.message.answer("🔄 Бот успешно перезапущен.", reply_markup=builder.as_markup()) except subprocess.CalledProcessError: - await callback_query.message.answer( - "🔄 Бот успешно перезапущен.", reply_markup=builder.as_markup() - ) + await callback_query.message.answer("🔄 Бот успешно перезапущен.", reply_markup=builder.as_markup()) except Exception as e: await callback_query.message.answer( f"⚠️ Ошибка при перезагрузке бота: {e.stderr}", @@ -507,37 +424,20 @@ async def user_editor_menu(callback_query: CallbackQuery): callback_data="search_by_key_name", ) ) - builder.row( - InlineKeyboardButton( - text="🆔 Поиск по Telegram ID", callback_data="search_by_tg_id" - ) - ) - builder.row( - InlineKeyboardButton( - text="🌐 Поиск по Username", callback_data="search_by_username" - ) - ) + builder.row(InlineKeyboardButton(text="🆔 Поиск по Telegram ID", callback_data="search_by_tg_id")) + builder.row(InlineKeyboardButton(text="🌐 Поиск по Username", callback_data="search_by_username")) builder.row(InlineKeyboardButton(text="🔙 Вернуться назад", callback_data="admin")) - await callback_query.message.answer( - "👇 Выберите способ поиска пользователя:", reply_markup=builder.as_markup() - ) + await callback_query.message.answer("👇 Выберите способ поиска пользователя:", reply_markup=builder.as_markup()) @router.callback_query(F.data == "ban_user") async def handle_ban_user(callback_query: types.CallbackQuery): builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="📄 Выгрузить в CSV", callback_data="export_to_csv") - ) - builder.row( - InlineKeyboardButton( - text="🗑️ Удалить из БД", callback_data="delete_banned_users" - ) - ) + builder.row(InlineKeyboardButton(text="📄 Выгрузить в CSV", callback_data="export_to_csv")) + builder.row(InlineKeyboardButton(text="🗑️ Удалить из БД", callback_data="delete_banned_users")) builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management")) await callback_query.message.answer( - "🚫 Заблокировавшие бота\n\n" - "Здесь можно просматривать и удалять пользователей, которые забанили вашего бота!", + "🚫 Заблокировавшие бота\n\nЗдесь можно просматривать и удалять пользователей, которые забанили вашего бота!", reply_markup=builder.as_markup(), ) @@ -559,14 +459,10 @@ async def export_banned_users_to_csv(callback_query: types.CallbackQuery): csv_output.seek(0) - document = BufferedInputFile( - file=csv_output.getvalue().encode("utf-8"), filename="banned_users.csv" - ) + document = BufferedInputFile(file=csv_output.getvalue().encode("utf-8"), filename="banned_users.csv") builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management") - ) + builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management")) await callback_query.message.answer_document( document=document, @@ -575,9 +471,7 @@ async def export_banned_users_to_csv(callback_query: types.CallbackQuery): ) except Exception as e: builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management") - ) + builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management")) await callback_query.message.answer( text=f"Ошибка при выгрузке CSV: {e}", reply_markup=builder.as_markup(), @@ -594,31 +488,23 @@ async def delete_banned_users(callback_query: types.CallbackQuery): blocked_ids = [record["tg_id"] for record in blocked_users] if not blocked_ids: - await callback_query.message.answer( - "📂 Нет заблокировавших пользователей для удаления." - ) + await callback_query.message.answer("📂 Нет заблокировавших пользователей для удаления.") return for tg_id in blocked_ids: await delete_user_data(conn, tg_id) - await conn.execute( - "DELETE FROM blocked_users WHERE tg_id = ANY($1)", blocked_ids - ) + await conn.execute("DELETE FROM blocked_users WHERE tg_id = ANY($1)", blocked_ids) builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management") - ) + builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management")) await callback_query.message.answer( text=f"🗑️ Удалено данные о {len(blocked_ids)} пользователях и связанных записях.", reply_markup=builder.as_markup(), ) except Exception as e: builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management") - ) + builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management")) await callback_query.message.answer( text=f"Ошибка при удалении записей: {e}", reply_markup=builder.as_markup(), diff --git a/handlers/admin/admin_servers.py b/handlers/admin/admin_servers.py index 53792c59..1a039c77 100644 --- a/handlers/admin/admin_servers.py +++ b/handlers/admin/admin_servers.py @@ -9,7 +9,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder from py3xui import AsyncApi from backup import create_backup_and_send_to_admins -from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, SUPERNODE +from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL from database import check_unique_server_name, get_servers_from_db from filters.admin import IsAdminFilter from handlers.keys.key_utils import create_key_on_cluster @@ -33,15 +33,9 @@ async def handle_servers_editor(callback_query: types.CallbackQuery): builder = InlineKeyboardBuilder() for cluster_name, cluster_servers in servers.items(): - builder.row( - InlineKeyboardButton( - text=f"⚙️ {cluster_name}", callback_data=f"manage_cluster|{cluster_name}" - ) - ) + builder.row(InlineKeyboardButton(text=f"⚙️ {cluster_name}", callback_data=f"manage_cluster|{cluster_name}")) - builder.row( - InlineKeyboardButton(text="➕ Добавить кластер", callback_data="add_cluster") - ) + builder.row(InlineKeyboardButton(text="➕ Добавить кластер", callback_data="add_cluster")) builder.row(InlineKeyboardButton(text="🔙 Назад в админку", callback_data="admin")) await callback_query.message.answer( @@ -72,11 +66,7 @@ async def handle_cluster_name_input(message: types.Message, state: FSMContext): if cluster_name == "❌ Отменить": await state.clear() builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton( - text="🔧 Управление кластерами", callback_data="servers_editor" - ) - ) + builder.row(InlineKeyboardButton(text="🔧 Управление кластерами", callback_data="servers_editor")) await message.answer( "Процесс создания кластера отменен. Вы вернулись в меню управления серверами.", reply_markup=builder.as_markup(), @@ -90,9 +80,7 @@ async def handle_cluster_name_input(message: types.Message, state: FSMContext): await state.update_data(cluster_name=cluster_name) builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor") - ) + builder.row(InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor")) await message.answer( f"Введите имя сервера для кластера {cluster_name}:\n\n" @@ -110,11 +98,7 @@ async def handle_server_name_input(message: types.Message, state: FSMContext): if server_name == "❌ Отменить": await state.clear() builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton( - text="🔧 Управление кластерами", callback_data="servers_editor" - ) - ) + builder.row(InlineKeyboardButton(text="🔧 Управление кластерами", callback_data="servers_editor")) await message.answer( "Процесс создания кластера был отменен. Вы вернулись в меню управления серверами.", reply_markup=builder.as_markup(), @@ -127,9 +111,7 @@ async def handle_server_name_input(message: types.Message, state: FSMContext): server_unique = await check_unique_server_name(server_name) if not server_unique: - await message.answer( - "❌ Сервер с таким именем уже существует. Пожалуйста, выберите другое имя." - ) + await message.answer("❌ Сервер с таким именем уже существует. Пожалуйста, выберите другое имя.") return user_data = await state.get_data() @@ -137,9 +119,7 @@ async def handle_server_name_input(message: types.Message, state: FSMContext): await state.update_data(server_name=server_name) builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor") - ) + builder.row(InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor")) await message.answer( f"Введите API URL для сервера {server_name} в кластере {cluster_name}:\n\n" @@ -158,11 +138,7 @@ async def handle_api_url_input(message: types.Message, state: FSMContext): if api_url == "❌ Отменить": await state.clear() builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton( - text="🔧 Управление кластерами", callback_data="servers_editor" - ) - ) + builder.row(InlineKeyboardButton(text="🔧 Управление кластерами", callback_data="servers_editor")) await message.answer( "Процесс создания кластера был отменен. Вы вернулись в меню управления серверами.", reply_markup=builder.as_markup(), @@ -183,9 +159,7 @@ async def handle_api_url_input(message: types.Message, state: FSMContext): await state.update_data(api_url=api_url) builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor") - ) + builder.row(InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor")) await message.answer( f"Введите subscription_url для сервера {server_name} в кластере {cluster_name}:\n\n" @@ -205,11 +179,7 @@ async def handle_subscription_url_input(message: types.Message, state: FSMContex if subscription_url == "❌ Отменить": await state.clear() builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton( - text="🔧 Управление кластерами", callback_data="servers_editor" - ) - ) + builder.row(InlineKeyboardButton(text="🔧 Управление кластерами", callback_data="servers_editor")) await message.answer( "Процесс создания кластера был отменен. Вы вернулись в меню управления серверами.", reply_markup=builder.as_markup(), @@ -230,9 +200,7 @@ async def handle_subscription_url_input(message: types.Message, state: FSMContex await state.update_data(subscription_url=subscription_url) builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor") - ) + builder.row(InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor")) await message.answer( f"Введите inbound_id для сервера {server_name} в кластере {cluster_name}:\n\n" @@ -247,9 +215,7 @@ async def handle_inbound_id_input(message: types.Message, state: FSMContext): inbound_id = message.text.strip() if not inbound_id.isdigit(): - await message.answer( - "❌ inbound_id должен быть числовым значением. Попробуйте снова." - ) + await message.answer("❌ inbound_id должен быть числовым значением. Попробуйте снова.") return user_data = await state.get_data() @@ -273,11 +239,7 @@ async def handle_inbound_id_input(message: types.Message, state: FSMContext): await conn.close() builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton( - text="🔙 Назад к кластерам", callback_data="servers_editor" - ) - ) + builder.row(InlineKeyboardButton(text="🔙 Назад к кластерам", callback_data="servers_editor")) await message.answer( f"✅ Кластер {cluster_name} и сервер {server_name} успешно добавлены!", @@ -304,11 +266,7 @@ async def handle_manage_cluster(callback_query: types.CallbackQuery, state: FSMC ) ) - builder.row( - InlineKeyboardButton( - text="➕ Добавить сервер", callback_data=f"add_server|{cluster_name}" - ) - ) + builder.row(InlineKeyboardButton(text="➕ Добавить сервер", callback_data=f"add_server|{cluster_name}")) builder.row( InlineKeyboardButton( @@ -331,11 +289,7 @@ async def handle_manage_cluster(callback_query: types.CallbackQuery, state: FSMC ) ) - builder.row( - InlineKeyboardButton( - text="🔙 Назад в управление кластерами", callback_data="servers_editor" - ) - ) + builder.row(InlineKeyboardButton(text="🔙 Назад в управление кластерами", callback_data="servers_editor")) await callback_query.message.answer( f"🔧 Управление серверами для кластера {cluster_name}", @@ -361,11 +315,7 @@ async def sync_cluster_handler(callback_query: types.CallbackQuery): await callback_query.message.answer( f"❌ Нет ключей для синхронизации в кластере {cluster_name}.", reply_markup=InlineKeyboardBuilder() - .row( - InlineKeyboardButton( - text="🔙 Назад", callback_data="servers_editor" - ) - ) + .row(InlineKeyboardButton(text="🔙 Назад", callback_data="servers_editor")) .as_markup(), ) return @@ -421,36 +371,24 @@ async def handle_check_server_availability(callback_query: types.CallbackQuery): "Это может занять до 1 минуты, пожалуйста, подождите..." ) - availability_message = ( - f"🖥️ Проверка доступности серверов для кластера {cluster_name} завершена:\n\n" - ) + availability_message = f"🖥️ Проверка доступности серверов для кластера {cluster_name} завершена:\n\n" for server in cluster_servers: - xui = AsyncApi( - server["api_url"], username=ADMIN_USERNAME, password=ADMIN_PASSWORD - ) + xui = AsyncApi(server["api_url"], username=ADMIN_USERNAME, password=ADMIN_PASSWORD) try: await xui.login() online_users = len(await xui.client.online()) - availability_message += ( - f"🌍 {server['server_name']}: {online_users} активных пользователей.\n" - ) + availability_message += f"🌍 {server['server_name']}: {online_users} активных пользователей.\n" except Exception as e: availability_message += f"❌ {server['server_name']}: Не удалось получить информацию. Ошибка: {e}\n" builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton( - text="🔙 Назад", callback_data=f"manage_cluster|{cluster_name}" - ) - ) + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data=f"manage_cluster|{cluster_name}")) - await in_progress_message.edit_text( - availability_message, reply_markup=builder.as_markup() - ) + await in_progress_message.edit_text(availability_message, reply_markup=builder.as_markup()) await callback_query.answer() @@ -464,9 +402,7 @@ async def handle_manage_server(callback_query: types.CallbackQuery, state: FSMCo server = None cluster_name = None for cluster, cluster_servers in servers.items(): - server = next( - (s for s in cluster_servers if s["server_name"] == server_name), None - ) + server = next((s for s in cluster_servers if s["server_name"] == server_name), None) if server: cluster_name = cluster break @@ -477,16 +413,8 @@ async def handle_manage_server(callback_query: types.CallbackQuery, state: FSMCo inbound_id = server["inbound_id"] builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton( - text="🗑️ Удалить", callback_data=f"delete_server|{server_name}" - ) - ) - builder.row( - InlineKeyboardButton( - text="🔙 Назад", callback_data=f"manage_cluster|{cluster_name}" - ) - ) + builder.row(InlineKeyboardButton(text="🗑️ Удалить", callback_data=f"delete_server|{server_name}")) + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data=f"manage_cluster|{cluster_name}")) await callback_query.message.answer( f"🔧 Информация о сервере {server_name}:\n\n" @@ -505,12 +433,8 @@ async def handle_delete_server(callback_query: types.CallbackQuery, state: FSMCo builder = InlineKeyboardBuilder() builder.row( - InlineKeyboardButton( - text="✅ Да", callback_data=f"confirm_delete_server|{server_name}" - ), - InlineKeyboardButton( - text="❌ Нет", callback_data=f"manage_server|{server_name}" - ), + InlineKeyboardButton(text="✅ Да", callback_data=f"confirm_delete_server|{server_name}"), + InlineKeyboardButton(text="❌ Нет", callback_data=f"manage_server|{server_name}"), ) await callback_query.message.answer( @@ -520,9 +444,7 @@ async def handle_delete_server(callback_query: types.CallbackQuery, state: FSMCo @router.callback_query(F.data.startswith("confirm_delete_server|"), IsAdminFilter()) -async def handle_confirm_delete_server( - callback_query: types.CallbackQuery, state: FSMContext -): +async def handle_confirm_delete_server(callback_query: types.CallbackQuery, state: FSMContext): server_name = callback_query.data.split("|")[1] conn = await asyncpg.connect(DATABASE_URL) @@ -535,15 +457,9 @@ async def handle_confirm_delete_server( await conn.close() builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton( - text="🔙 Назад в управление кластерами", callback_data="servers_editor" - ) - ) + builder.row(InlineKeyboardButton(text="🔙 Назад в управление кластерами", callback_data="servers_editor")) - await callback_query.message.answer( - f"🗑️ Сервер {server_name} успешно удален.", reply_markup=builder.as_markup() - ) + await callback_query.message.answer(f"🗑️ Сервер {server_name} успешно удален.", reply_markup=builder.as_markup()) @router.callback_query(F.data.startswith("add_server|"), IsAdminFilter()) @@ -553,9 +469,7 @@ async def handle_add_server(callback_query: types.CallbackQuery, state: FSMConte await state.update_data(cluster_name=cluster_name) builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor") - ) + builder.row(InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor")) await callback_query.message.answer( f"Введите имя сервера для кластера {cluster_name}:\n\n" diff --git a/handlers/admin/admin_user_editor.py b/handlers/admin/admin_user_editor.py index db2be7ba..94e02f33 100644 --- a/handlers/admin/admin_user_editor.py +++ b/handlers/admin/admin_user_editor.py @@ -43,9 +43,7 @@ class UserEditorState(StatesGroup): async def prompt_tg_id(callback_query: CallbackQuery, state: FSMContext): builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) - await callback_query.message.answer( - "🔍 Введите Telegram ID клиента:", reply_markup=builder.as_markup() - ) + await callback_query.message.answer("🔍 Введите Telegram ID клиента:", reply_markup=builder.as_markup()) await state.set_state(UserEditorState.waiting_for_tg_id) @@ -53,20 +51,14 @@ async def prompt_tg_id(callback_query: CallbackQuery, state: FSMContext): async def prompt_username(callback_query: CallbackQuery, state: FSMContext): builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) - await callback_query.message.answer( - "🔍 Введите Username клиента:", reply_markup=builder.as_markup() - ) + await callback_query.message.answer("🔍 Введите Username клиента:", reply_markup=builder.as_markup()) await state.set_state(UserEditorState.waiting_for_username) @router.message(UserEditorState.waiting_for_username, IsAdminFilter()) -async def handle_username_input( - message: types.Message, state: FSMContext, session: Any -): +async def handle_username_input(message: types.Message, state: FSMContext, session: Any): username = message.text.strip().lstrip("@").replace("https://t.me/", "") - user_record = await session.fetchrow( - "SELECT tg_id FROM users WHERE username = $1", username - ) + user_record = await session.fetchrow("SELECT tg_id FROM users WHERE username = $1", username) if not user_record: builder = InlineKeyboardBuilder() @@ -79,16 +71,10 @@ async def handle_username_input( return tg_id = user_record["tg_id"] - username = await session.fetchval( - "SELECT username FROM users WHERE tg_id = $1", tg_id - ) - balance = await session.fetchval( - "SELECT balance FROM connections WHERE tg_id = $1", tg_id - ) + username = await session.fetchval("SELECT username FROM users WHERE tg_id = $1", tg_id) + balance = await session.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id) key_records = await session.fetch("SELECT email FROM keys WHERE tg_id = $1", tg_id) - referral_count = await session.fetchval( - "SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id - ) + referral_count = await session.fetchval("SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id) if balance is None: builder = InlineKeyboardBuilder() @@ -103,9 +89,7 @@ async def handle_username_input( builder = InlineKeyboardBuilder() for (email,) in key_records: - builder.row( - InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}") - ) + builder.row(InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}")) builder.row( InlineKeyboardButton( @@ -120,21 +104,9 @@ async def handle_username_input( callback_data=f"restore_trial_{tg_id}", ) ) - builder.row( - InlineKeyboardButton( - text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}" - ) - ) - builder.row( - InlineKeyboardButton( - text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}" - ) - ) - builder.row( - InlineKeyboardButton( - text="✉️ Отправить сообщение", callback_data=f"send_message_{tg_id}" - ) - ) + builder.row(InlineKeyboardButton(text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}")) + builder.row(InlineKeyboardButton(text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}")) + builder.row(InlineKeyboardButton(text="✉️ Отправить сообщение", callback_data=f"send_message_{tg_id}")) builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) user_info = ( @@ -153,9 +125,7 @@ async def handle_username_input( async def handle_send_message(callback_query: types.CallbackQuery, state: FSMContext): tg_id = callback_query.data.split("_")[2] await state.update_data(target_tg_id=tg_id) - await callback_query.message.answer( - "✉️ Введите текст сообщения, которое вы хотите отправить пользователю." - ) + await callback_query.message.answer("✉️ Введите текст сообщения, которое вы хотите отправить пользователю.") await state.set_state(UserEditorState.waiting_for_message_text) @@ -181,16 +151,10 @@ async def process_send_message(message: types.Message, state: FSMContext, bot: B @router.message(UserEditorState.waiting_for_tg_id, F.text.isdigit(), IsAdminFilter()) async def handle_tg_id_input(message: types.Message, state: FSMContext, session: Any): tg_id = int(message.text) - username = await session.fetchval( - "SELECT username FROM users WHERE tg_id = $1", tg_id - ) - balance = await session.fetchval( - "SELECT balance FROM connections WHERE tg_id = $1", tg_id - ) + username = await session.fetchval("SELECT username FROM users WHERE tg_id = $1", tg_id) + balance = await session.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id) key_records = await session.fetch("SELECT email FROM keys WHERE tg_id = $1", tg_id) - referral_count = await session.fetchval( - "SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id - ) + referral_count = await session.fetchval("SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id) if balance is None: builder = InlineKeyboardBuilder() @@ -205,9 +169,7 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext, session: builder = InlineKeyboardBuilder() for (email,) in key_records: - builder.row( - InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}") - ) + builder.row(InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}")) builder.row( InlineKeyboardButton( @@ -215,27 +177,15 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext, session: callback_data=f"change_balance_{tg_id}", ) ) - builder.row( - InlineKeyboardButton( - text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}" - ) - ) - builder.row( - InlineKeyboardButton( - text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}" - ) - ) + builder.row(InlineKeyboardButton(text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}")) + builder.row(InlineKeyboardButton(text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}")) builder.row( InlineKeyboardButton( text="🔄 Восстановить пробник", callback_data=f"restore_trial_{tg_id}", ) ) - builder.row( - InlineKeyboardButton( - text="✉️ Отправить сообщение", callback_data=f"send_message_{tg_id}" - ) - ) + builder.row(InlineKeyboardButton(text="✉️ Отправить сообщение", callback_data=f"send_message_{tg_id}")) builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) @@ -258,15 +208,9 @@ async def handle_restore_trial(callback_query: types.CallbackQuery, session: Any await restore_trial(tg_id, session) builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton( - text="🔙 Назад в меню администратора", callback_data="admin" - ) - ) + builder.row(InlineKeyboardButton(text="🔙 Назад в меню администратора", callback_data="admin")) - await callback_query.message.answer( - "✅ Триал успешно восстановлен.", reply_markup=builder.as_markup() - ) + await callback_query.message.answer("✅ Триал успешно восстановлен.", reply_markup=builder.as_markup()) @router.callback_query(F.data.startswith("change_balance_"), IsAdminFilter()) @@ -275,16 +219,12 @@ async def process_balance_change(callback_query: CallbackQuery, state: FSMContex await state.update_data(tg_id=tg_id) builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) - await callback_query.message.answer( - "💸 Введите новую сумму баланса:", reply_markup=builder.as_markup() - ) + await callback_query.message.answer("💸 Введите новую сумму баланса:", reply_markup=builder.as_markup()) await state.set_state(UserEditorState.waiting_for_new_balance) @router.message(UserEditorState.waiting_for_new_balance, IsAdminFilter()) -async def handle_new_balance_input( - message: types.Message, state: FSMContext, session: Any -): +async def handle_new_balance_input(message: types.Message, state: FSMContext, session: Any): if not message.text.isdigit() or int(message.text) < 0: builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) @@ -318,7 +258,6 @@ async def handle_new_balance_input( async def get_key_details(email, session): - record = await session.fetchrow( """ SELECT k.key, k.expiry_time, k.server_id, c.tg_id, c.balance @@ -399,25 +338,19 @@ async def process_key_edit(callback_query: CallbackQuery, session: Any): ) builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) - await callback_query.message.answer( - response_message, reply_markup=builder.as_markup() - ) + await callback_query.message.answer(response_message, reply_markup=builder.as_markup()) @router.callback_query(F.data == "search_by_key_name", IsAdminFilter()) async def prompt_key_name(callback_query: CallbackQuery, state: FSMContext): builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) - await callback_query.message.answer( - "🔑 Введите имя ключа:", reply_markup=builder.as_markup() - ) + await callback_query.message.answer("🔑 Введите имя ключа:", reply_markup=builder.as_markup()) await state.set_state(UserEditorState.waiting_for_key_name) @router.message(UserEditorState.waiting_for_key_name, IsAdminFilter()) -async def handle_key_name_input( - message: types.Message, state: FSMContext, session: Any -): +async def handle_key_name_input(message: types.Message, state: FSMContext, session: Any): key_name = sanitize_key_name(message.text) key_details = await get_key_details(key_name, session) @@ -474,18 +407,14 @@ async def prompt_expiry_change(callback_query: CallbackQuery, state: FSMContext) @router.message(UserEditorState.waiting_for_expiry_time, IsAdminFilter()) -async def handle_expiry_time_input( - message: types.Message, state: FSMContext, session: Any -): +async def handle_expiry_time_input(message: types.Message, state: FSMContext, session: Any): user_data = await state.get_data() email = user_data.get("email") if not email: builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) - await message.answer( - "📧 Email не найден в состоянии. 🚫", reply_markup=builder.as_markup() - ) + await message.answer("📧 Email не найден в состоянии. 🚫", reply_markup=builder.as_markup()) await state.clear() return @@ -499,9 +428,7 @@ async def handle_expiry_time_input( client_id = await get_client_id_by_email(email) if client_id is None: builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor") - ) + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) await message.answer( f"🚫 Клиент с email {email} не найден. 🔍", reply_markup=builder.as_markup(), @@ -509,14 +436,10 @@ async def handle_expiry_time_input( await state.clear() return - record = await session.fetchrow( - "SELECT server_id FROM keys WHERE client_id = $1", client_id - ) + record = await session.fetchrow("SELECT server_id FROM keys WHERE client_id = $1", client_id) if not record: builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor") - ) + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) await message.answer( "🚫 Клиент не найден в базе данных. 🔍", reply_markup=builder.as_markup(), @@ -547,7 +470,9 @@ async def handle_expiry_time_input( await update_key_expiry(client_id, expiry_time) - response_message = f"✅ Время истечения ключа для клиента {client_id} ({email}) успешно обновлено на всех серверах." + response_message = ( + f"✅ Время истечения ключа для клиента {client_id} ({email}) успешно обновлено на всех серверах." + ) builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin")) @@ -565,20 +490,14 @@ async def handle_expiry_time_input( @router.callback_query(F.data.startswith("delete_key_admin|"), IsAdminFilter()) -async def process_callback_delete_key( - callback_query: types.CallbackQuery, session: Any -): +async def process_callback_delete_key(callback_query: types.CallbackQuery, session: Any): email = callback_query.data.split("|")[1] - client_id = await session.fetchval( - "SELECT client_id FROM keys WHERE email = $1", email - ) + client_id = await session.fetchval("SELECT client_id FROM keys WHERE email = $1", email) if client_id is None: builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) - await callback_query.message.answer( - "🔍 Ключ не найден. 🚫", reply_markup=builder.as_markup() - ) + await callback_query.message.answer("🔍 Ключ не найден. 🚫", reply_markup=builder.as_markup()) return builder = InlineKeyboardBuilder() @@ -588,9 +507,7 @@ async def process_callback_delete_key( callback_data=f"confirm_delete_admin|{client_id}", ) ) - builder.row( - types.InlineKeyboardButton(text="❌ Нет, отменить", callback_data="user_editor") - ) + builder.row(types.InlineKeyboardButton(text="❌ Нет, отменить", callback_data="user_editor")) await callback_query.message.answer( "❓ Вы уверены, что хотите удалить ключ?", reply_markup=builder.as_markup(), @@ -598,13 +515,9 @@ async def process_callback_delete_key( @router.callback_query(F.data.startswith("confirm_delete_admin|"), IsAdminFilter()) -async def process_callback_confirm_delete( - callback_query: types.CallbackQuery, session: Any -): +async def process_callback_confirm_delete(callback_query: types.CallbackQuery, session: Any): client_id = callback_query.data.split("|")[1] - record = await session.fetchrow( - "SELECT email FROM keys WHERE client_id = $1", client_id - ) + record = await session.fetchrow("SELECT email FROM keys WHERE client_id = $1", client_id) if record: email = record["email"] @@ -618,74 +531,38 @@ async def process_callback_confirm_delete( tasks = [] for cluster_name, cluster_servers in clusters.items(): for server in cluster_servers: - tasks.append( - delete_key_from_cluster(cluster_name, email, client_id) - ) + 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 callback_query.message.answer( - response_message, reply_markup=builder.as_markup() - ) + await callback_query.message.answer(response_message, reply_markup=builder.as_markup()) else: response_message = "🚫 Ключ не найден или уже удален." builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys")) - await callback_query.message.answer( - response_message, reply_markup=builder.as_markup() - ) + await callback_query.message.answer(response_message, reply_markup=builder.as_markup()) @router.callback_query(F.data.startswith("user_info|"), IsAdminFilter()) -async def handle_user_info( - callback_query: types.CallbackQuery, state: FSMContext, session: Any -): +async def handle_user_info(callback_query: types.CallbackQuery, state: FSMContext, session: Any): tg_id = int(callback_query.data.split("|")[1]) - username = await session.fetchval( - "SELECT username FROM users WHERE tg_id = $1", tg_id - ) - balance = await session.fetchval( - "SELECT balance FROM connections WHERE tg_id = $1", tg_id - ) + username = await session.fetchval("SELECT username FROM users WHERE tg_id = $1", tg_id) + balance = await session.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id) key_records = await session.fetch("SELECT email FROM keys WHERE tg_id = $1", tg_id) - referral_count = await session.fetchval( - "SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id - ) + referral_count = await session.fetchval("SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id) builder = InlineKeyboardBuilder() for (email,) in key_records: - builder.row( - InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}") - ) + builder.row(InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}")) - builder.row( - InlineKeyboardButton( - text="📝 Изменить баланс", callback_data=f"change_balance_{tg_id}" - ) - ) - builder.row( - InlineKeyboardButton( - text="🔄 Восстановить пробник", callback_data=f"restore_trial_{tg_id}" - ) - ) - builder.row( - InlineKeyboardButton( - text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}" - ) - ) - builder.row( - InlineKeyboardButton( - text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}" - ) - ) - builder.row( - InlineKeyboardButton( - text="✉️ Отправить сообщение", callback_data=f"send_message_{tg_id}" - ) - ) + builder.row(InlineKeyboardButton(text="📝 Изменить баланс", callback_data=f"change_balance_{tg_id}")) + builder.row(InlineKeyboardButton(text="🔄 Восстановить пробник", callback_data=f"restore_trial_{tg_id}")) + builder.row(InlineKeyboardButton(text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}")) + builder.row(InlineKeyboardButton(text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}")) + builder.row(InlineKeyboardButton(text="✉️ Отправить сообщение", callback_data=f"send_message_{tg_id}")) builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) user_info = ( @@ -702,19 +579,13 @@ async def handle_user_info( @router.callback_query(F.data.startswith("confirm_delete_user_"), IsAdminFilter()) -async def confirm_delete_user( - callback_query: types.CallbackQuery, state: FSMContext, session: Any -): +async def confirm_delete_user(callback_query: types.CallbackQuery, state: FSMContext, session: Any): tg_id = int(callback_query.data.split("_")[3]) confirmation_markup = InlineKeyboardMarkup( row_width=2, inline_keyboard=[ - [ - InlineKeyboardButton( - text="✅ Подтвердить", callback_data=f"delete_user_{tg_id}" - ) - ], + [InlineKeyboardButton(text="✅ Подтвердить", callback_data=f"delete_user_{tg_id}")], [InlineKeyboardButton(text="❌ Отменить", callback_data="user_editor")], ], ) @@ -729,9 +600,7 @@ async def confirm_delete_user( async def delete_user(callback_query: types.CallbackQuery, session: Any): tg_id = int(callback_query.data.split("_")[2]) - key_records = await session.fetch( - "SELECT email, client_id FROM keys WHERE tg_id = $1", tg_id - ) + key_records = await session.fetch("SELECT email, client_id FROM keys WHERE tg_id = $1", tg_id) async def delete_keys_from_servers(): try: @@ -742,9 +611,7 @@ async def delete_user(callback_query: types.CallbackQuery, session: Any): tasks.append(delete_key_from_cluster(cluster_id, email, client_id)) await asyncio.gather(*tasks) except Exception as e: - logger.error( - f"Ошибка при удалении ключей с серверов для пользователя {tg_id}: {e}" - ) + logger.error(f"Ошибка при удалении ключей с серверов для пользователя {tg_id}: {e}") await delete_keys_from_servers() @@ -754,13 +621,9 @@ async def delete_user(callback_query: types.CallbackQuery, session: Any): back_button = InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor") keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]]) - await callback_query.message.answer( - f"🗑️ Пользователь с ID {tg_id} был удален.", reply_markup=keyboard - ) + await callback_query.message.answer(f"🗑️ Пользователь с ID {tg_id} был удален.", reply_markup=keyboard) except Exception as e: - logger.error( - f"Ошибка при удалении данных из базы данных для пользователя {tg_id}: {e}" - ) + logger.error(f"Ошибка при удалении данных из базы данных для пользователя {tg_id}: {e}") await callback_query.message.answer( f"❌ Произошла ошибка при удалении пользователя с ID {tg_id}. Попробуйте снова." ) diff --git a/handlers/buttons/yookassa.py b/handlers/buttons/yookassa.py index 52401db4..5bb26cbb 100644 --- a/handlers/buttons/yookassa.py +++ b/handlers/buttons/yookassa.py @@ -3,6 +3,10 @@ PAY_2 = "Оплатить" BACK = "⬅️ Назад" CUSTOM_SUM = "💰 Ввести свою сумму" PROFILE = "👤 Личный кабинет" -KEY_CREATION_PAYMENT_MESSAGE = "Вы выбрали пополнение на {amount} рублей для создания нового ключа. Перейдите по ссылке для оплаты:" -KEY_RENEWAL_PAYMENT_MESSAGE = "Вы выбрали пополнение на {amount} рублей для продления ключа. Перейдите по ссылке для оплаты:" +KEY_CREATION_PAYMENT_MESSAGE = ( + "Вы выбрали пополнение на {amount} рублей для создания нового ключа. Перейдите по ссылке для оплаты:" +) +KEY_RENEWAL_PAYMENT_MESSAGE = ( + "Вы выбрали пополнение на {amount} рублей для продления ключа. Перейдите по ссылке для оплаты:" +) DEFAULT_PAYMENT_MESSAGE = "Вы выбрали пополнение на {amount} рублей. Перейдите по ссылке для оплаты:" diff --git a/handlers/captcha.py b/handlers/captcha.py index c7d5ed16..fd67d534 100644 --- a/handlers/captcha.py +++ b/handlers/captcha.py @@ -3,7 +3,7 @@ from typing import Any from aiogram import F, Router from aiogram.fsm.context import FSMContext -from aiogram.types import CallbackQuery +from aiogram.types import CallbackQuery, Message from aiogram.utils.keyboard import InlineKeyboardBuilder from config import CAPTCHA_EMOJIS @@ -13,7 +13,7 @@ from logger import logger router = Router() -async def generate_captcha(state: FSMContext): +async def generate_captcha(message: Message, state: FSMContext): """Генерирует новую капчу и сохраняет правильный ответ в состоянии Пример словаря CAPTCHA_EMOJIS: @@ -28,48 +28,42 @@ async def generate_captcha(state: FSMContext): """ # Выбираем случайный эмодзи и его описание из конфига correct_emoji, correct_text = random.choice(list(CAPTCHA_EMOJIS.items())) - + # Получаем 3 случайных неправильных эмодзи - wrong_emojis = random.sample( - [e for e in CAPTCHA_EMOJIS.keys() if e != correct_emoji], 3 - ) - + wrong_emojis = random.sample([e for e in CAPTCHA_EMOJIS.keys() if e != correct_emoji], 3) + # Создаем список всех эмодзи и перемешиваем их all_emojis = [correct_emoji] + wrong_emojis random.shuffle(all_emojis) - + # Сохраняем правильный ответ в состоянии await state.update_data(correct_emoji=correct_emoji) - + await state.update_data(message=message) + # Создаем клавиатуру builder = InlineKeyboardBuilder() for emoji in all_emojis: builder.button(text=emoji, callback_data=f"captcha_{emoji}") builder.adjust(2, 2) - + return { "text": f"🔒 Для подтверждения что вы не робот,\nвыберите кнопку с {correct_text}", - "markup": builder.as_markup() + "markup": builder.as_markup(), } @router.callback_query(F.data.startswith("captcha_")) -async def check_captcha( - callback: CallbackQuery, state: FSMContext, session: Any, admin: bool -): +async def check_captcha(callback: CallbackQuery, state: FSMContext, session: Any, admin: bool): """Проверяет ответ пользователя на капчу""" selected_emoji = callback.data.split("captcha_")[1] state_data = await state.get_data() correct_emoji = state_data.get("correct_emoji") + message = state_data.get("message", callback.message) if selected_emoji == correct_emoji: logger.info(f"Пользователь {callback.message.chat.id} успешно прошел капчу") - await start_command(callback.message, state, session, admin) + await start_command(message, state, session, admin, False) else: - logger.warning( - f"Пользователь {callback.message.chat.id} неверно ответил на капчу" - ) - captcha = await generate_captcha(state) - await callback.message.answer( - text=captcha["text"], reply_markup=captcha["markup"] - ) + logger.warning(f"Пользователь {callback.message.chat.id} неверно ответил на капчу") + captcha = await generate_captcha(message, state) + await callback.message.answer(text=captcha["text"], reply_markup=captcha["markup"]) diff --git a/handlers/coupons.py b/handlers/coupons.py index eb0f7d4c..d010a028 100644 --- a/handlers/coupons.py +++ b/handlers/coupons.py @@ -18,9 +18,7 @@ router = Router() @router.callback_query(F.data == "activate_coupon") -async def handle_activate_coupon( - callback_query: types.CallbackQuery, state: FSMContext -): +async def handle_activate_coupon(callback_query: types.CallbackQuery, state: FSMContext): builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) @@ -40,9 +38,7 @@ async def process_coupon_code(message: types.Message, state: FSMContext, session builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) - await message.answer( - activation_result, reply_markup=builder.as_markup() - ) + await message.answer(activation_result, reply_markup=builder.as_markup()) await state.clear() diff --git a/handlers/donate.py b/handlers/donate.py index ad460085..3de7d323 100644 --- a/handlers/donate.py +++ b/handlers/donate.py @@ -22,11 +22,7 @@ async def process_donate(callback_query: types.CallbackQuery, state: FSMContext) await state.clear() builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton( - text="🤖 Бот для покупки звезд", url="https://t.me/PremiumBot" - ) - ) + builder.row(InlineKeyboardButton(text="🤖 Бот для покупки звезд", url="https://t.me/PremiumBot")) builder.row( InlineKeyboardButton( text="💰 Ввести сумму доната", @@ -44,14 +40,10 @@ async def process_donate(callback_query: types.CallbackQuery, state: FSMContext) @router.callback_query(F.data == "enter_custom_donate_amount") -async def process_enter_donate_amount( - callback_query: types.CallbackQuery, state: FSMContext -): +async def process_enter_donate_amount(callback_query: types.CallbackQuery, state: FSMContext): builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="donate")) - await callback_query.message.answer( - "💸 Введите сумму доната в рублях:", reply_markup=builder.as_markup() - ) + await callback_query.message.answer("💸 Введите сумму доната в рублях:", reply_markup=builder.as_markup()) await state.set_state(DonateState.entering_donate_amount) @@ -60,9 +52,7 @@ async def process_donate_amount_input(message: types.Message, state: FSMContext) if message.text.isdigit(): amount = int(message.text) if amount // RUB_TO_XTR <= 0: - await message.answer( - f"Сумма доната должна быть больше {RUB_TO_XTR}. Пожалуйста, введите сумму еще раз:" - ) + await message.answer(f"Сумма доната должна быть больше {RUB_TO_XTR}. Пожалуйста, введите сумму еще раз:") return await state.update_data(amount=amount) @@ -99,9 +89,7 @@ async def on_successful_donate(message: types.Message, state: FSMContext): try: amount = float(message.successful_payment.invoice_payload.split("_")[0]) builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile") - ) + builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) await message.answer( text=f"🙏 Спасибо за донат {amount} рублей! Ваша поддержка очень важна для нас. 💖", reply_markup=builder.as_markup(), diff --git a/handlers/instructions/instructions.py b/handlers/instructions/instructions.py index a77e63ba..3b278264 100644 --- a/handlers/instructions/instructions.py +++ b/handlers/instructions/instructions.py @@ -29,9 +29,7 @@ async def send_instructions( if not os.path.isfile(image_path): if isinstance(callback_query_or_message, types.CallbackQuery): - await callback_query_or_message.message.answer( - "Файл изображения не найден." - ) + await callback_query_or_message.message.answer("Файл изображения не найден.") else: await callback_query_or_message.answer("Файл изображения не найден.") return @@ -71,9 +69,7 @@ async def process_connect_pc(callback_query: types.CallbackQuery, session: Any): ) if not record: - await callback_query.message.answer( - "❌ Ключ не найден. Проверьте имя ключа. 🔍" - ) + await callback_query.message.answer("❌ Ключ не найден. Проверьте имя ключа. 🔍") return key = record["key"] @@ -81,14 +77,8 @@ async def process_connect_pc(callback_query: types.CallbackQuery, session: Any): instruction_message = f"{key_message}{INSTRUCTION_PC}" builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton( - text="💻 Подключить Windows", url=f"{CONNECT_WINDOWS}{key}" - ) - ) - builder.row( - InlineKeyboardButton(text="💻 Подключить MacOS", url=f"{CONNECT_MACOS}{key}") - ) + builder.row(InlineKeyboardButton(text="💻 Подключить Windows", url=f"{CONNECT_WINDOWS}{key}")) + builder.row(InlineKeyboardButton(text="💻 Подключить MacOS", url=f"{CONNECT_MACOS}{key}")) builder.row(InlineKeyboardButton(text="🆘 Поддержка", url=f"{SUPPORT_CHAT_URL}")) builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) @@ -104,11 +94,7 @@ async def process_connect_tv(callback_query: types.CallbackQuery): key_name = callback_query.data.split("|")[1] builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton( - text="▶ Продолжить", callback_data=f"continue_tv|{key_name}" - ) - ) + builder.row(InlineKeyboardButton(text="▶ Продолжить", callback_data=f"continue_tv|{key_name}")) builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) await callback_query.message.answer( @@ -146,13 +132,7 @@ async def process_continue_tv(callback_query: types.CallbackQuery): message_text = SUBSCRIPTION_DETAILS_TEXT.format(subscription_link=subscription_link) builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton( - text="📖 Полная инструкция", url="https://vpn4tv.com/quick-guide.html" - ) - ) + builder.row(InlineKeyboardButton(text="📖 Полная инструкция", url="https://vpn4tv.com/quick-guide.html")) builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) - await callback_query.message.answer( - text=message_text, reply_markup=builder.as_markup() - ) + await callback_query.message.answer(text=message_text, reply_markup=builder.as_markup()) diff --git a/handlers/keys/key_management.py b/handlers/keys/key_management.py index 625947b5..bcb90715 100644 --- a/handlers/keys/key_management.py +++ b/handlers/keys/key_management.py @@ -55,16 +55,12 @@ class Form(StatesGroup): @router.callback_query(F.data == "create_key") -async def confirm_create_new_key( - callback_query: CallbackQuery, state: FSMContext, session: Any -): +async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext, session: Any): tg_id = callback_query.message.chat.id logger.info(f"User {tg_id} confirmed creation of a new key.") - logger.info( - f"Balance for user {tg_id} is sufficient. Proceeding with key creation." - ) + logger.info(f"Balance for user {tg_id} is sufficient. Proceeding with key creation.") await handle_key_creation(tg_id, state, session, callback_query) @@ -83,9 +79,7 @@ async def handle_key_creation( expiry_time = current_time + timedelta(days=TRIAL_TIME) logger.info(f"Assigned 1-day trial to user {tg_id}.") - await session.execute( - "UPDATE connections SET trial = 1 WHERE tg_id = $1", tg_id - ) + await session.execute("UPDATE connections SET trial = 1 WHERE tg_id = $1", tg_id) await create_key(tg_id, expiry_time, state, session, message_or_query) else: builder = InlineKeyboardBuilder() @@ -107,9 +101,7 @@ async def handle_key_creation( ) ) - builder.row( - InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile") - ) + builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) await message_or_query.message.answer( "💳 Выберите тарифный план для создания нового ключа:", @@ -153,12 +145,8 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any): await handle_custom_amount_input(callback_query, session) else: builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="💳 Пополнить баланс", callback_data="pay") - ) - builder.row( - InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile") - ) + builder.row(InlineKeyboardButton(text="💳 Пополнить баланс", callback_data="pay")) + builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) await callback_query.message.answer( f"💳 Недостаточно средств. Для продолжения необходимо пополнить баланс на {required_amount}₽.", @@ -193,9 +181,7 @@ async def create_key( ) if not existing_key: break - logger.warning( - f"Key name '{key_name}' already exists for user {tg_id}. Generating a new one." - ) + logger.warning(f"Key name '{key_name}' already exists for user {tg_id}. Generating a new one.") client_id = str(uuid.uuid4()) email = key_name.lower() diff --git a/handlers/keys/key_utils.py b/handlers/keys/key_utils.py index 47f9401c..f0d149d4 100644 --- a/handlers/keys/key_utils.py +++ b/handlers/keys/key_utils.py @@ -33,9 +33,7 @@ async def create_key_on_cluster(cluster_id, tg_id, client_id, email, expiry_time server_name = server_info.get("server_name", "unknown") if not inbound_id: - logger.warning( - f"INBOUND_ID отсутствует для сервера {server_name}. Пропуск." - ) + logger.warning(f"INBOUND_ID отсутствует для сервера {server_name}. Пропуск.") return if SUPERNODE: @@ -56,7 +54,7 @@ async def create_key_on_cluster(cluster_id, tg_id, client_id, email, expiry_time enable=True, flow="xtls-rprx-vision", inbound_id=int(inbound_id), - sub_id=sub_id + sub_id=sub_id, ) if SUPERNODE: @@ -73,8 +71,6 @@ async def create_key_on_cluster(cluster_id, tg_id, client_id, email, expiry_time raise e - - async def renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, total_gb): try: servers = await get_servers_from_db() @@ -95,9 +91,7 @@ async def renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, to server_name = server_info.get("server_name", "unknown") if not inbound_id: - logger.warning( - f"INBOUND_ID отсутствует для сервера {server_name}. Пропуск." - ) + logger.warning(f"INBOUND_ID отсутствует для сервера {server_name}. Пропуск.") continue if SUPERNODE: @@ -108,23 +102,13 @@ async def renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, to sub_id = unique_email tasks.append( - extend_client_key( - xui, - int(inbound_id), - unique_email, - new_expiry_time, - client_id, - total_gb, - sub_id - ) + extend_client_key(xui, int(inbound_id), unique_email, new_expiry_time, client_id, total_gb, sub_id) ) await asyncio.gather(*tasks) except Exception as e: - logger.error( - f"Не удалось продлить ключ {client_id} в кластере {cluster_id}: {e}" - ) + logger.error(f"Не удалось продлить ключ {client_id} в кластере {cluster_id}: {e}") raise e @@ -171,9 +155,7 @@ async def delete_key_from_cluster(cluster_id, email, client_id): await asyncio.gather(*tasks) except Exception as e: - logger.error( - f"Не удалось удалить ключ {client_id} в кластере {cluster_id}: {e}" - ) + logger.error(f"Не удалось удалить ключ {client_id} в кластере {cluster_id}: {e}") raise e @@ -212,18 +194,14 @@ async def update_key_on_cluster(tg_id, client_id, email, expiry_time, cluster_id enable=True, flow="xtls-rprx-vision", inbound_id=int(inbound_id), - sub_id=email + sub_id=email, ) ) await asyncio.gather(*tasks) - logger.info( - f"Ключ успешно обновлен для {client_id} на всех серверах в кластере {cluster_id}" - ) + logger.info(f"Ключ успешно обновлен для {client_id} на всех серверах в кластере {cluster_id}") except Exception as e: - logger.error( - f"Ошибка при обновлении ключа на серверах кластера {cluster_id} для {client_id}: {e}" - ) + logger.error(f"Ошибка при обновлении ключа на серверах кластера {cluster_id} для {client_id}: {e}") raise e diff --git a/handlers/keys/keys.py b/handlers/keys/keys.py index 8a9cbbb7..5fdc6f16 100644 --- a/handlers/keys/keys.py +++ b/handlers/keys/keys.py @@ -86,9 +86,7 @@ async def process_callback_or_message_view_keys( inline_keyboard, response_message = build_keys_response(records) image_path = os.path.join("img", "pic_keys.jpg") - await send_with_optional_image( - send_message, send_photo, image_path, response_message, inline_keyboard - ) + await send_with_optional_image(send_message, send_photo, image_path, response_message, inline_keyboard) except Exception as e: error_message = f"Ошибка при получении ключей: {e}" await send_message(text=error_message) @@ -105,15 +103,10 @@ def build_keys_response(records): key_name = record["email"] expiry_date = datetime.utcfromtimestamp(record["expiry_time"] / 1000).strftime("%d.%m.%Y") builder.row( - InlineKeyboardButton( - text=f"🔑 {key_name} (до {expiry_date})", - callback_data=f"view_key|{key_name}" - ) + InlineKeyboardButton(text=f"🔑 {key_name} (до {expiry_date})", callback_data=f"view_key|{key_name}") ) - builder.row( - InlineKeyboardButton(text="➕ Добавить подписку", callback_data="create_key") - ) + builder.row(InlineKeyboardButton(text="➕ Добавить подписку", callback_data="create_key")) builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) @@ -125,18 +118,14 @@ def build_keys_response(records): return inline_keyboard, response_message -async def send_with_optional_image( - send_message, send_photo, image_path, text, keyboard -): +async def send_with_optional_image(send_message, send_photo, image_path, text, keyboard): """ Отправляет сообщение с изображением, если файл существует. В противном случае отправляет только текст. """ if os.path.isfile(image_path): with open(image_path, "rb") as image_file: await send_photo( - photo=BufferedInputFile( - image_file.read(), filename=os.path.basename(image_path) - ), + photo=BufferedInputFile(image_file.read(), filename=os.path.basename(image_path)), caption=text, reply_markup=keyboard, ) @@ -184,9 +173,7 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session ) formatted_expiry_date = expiry_date.strftime("%d %B %Y года") - response_message = key_message( - key, formatted_expiry_date, days_left_message, server_name - ) + response_message = key_message(key, formatted_expiry_date, days_left_message, server_name) builder = InlineKeyboardBuilder() @@ -200,51 +187,29 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session builder.row( InlineKeyboardButton(text=DOWNLOAD_IOS_BUTTON, url=DOWNLOAD_IOS), - InlineKeyboardButton( - text=DOWNLOAD_ANDROID_BUTTON, url=DOWNLOAD_ANDROID - ), + InlineKeyboardButton(text=DOWNLOAD_ANDROID_BUTTON, url=DOWNLOAD_ANDROID), ) builder.row( InlineKeyboardButton(text=IMPORT_IOS, url=f"{CONNECT_IOS}{key}"), - InlineKeyboardButton( - text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{key}" - ), + InlineKeyboardButton(text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{key}"), ) builder.row( - InlineKeyboardButton( - text=PC_BUTTON, callback_data=f"connect_pc|{key_name}" - ), - InlineKeyboardButton( - text=TV_BUTTON, callback_data=f"connect_tv|{key_name}" - ), + InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{key_name}"), + InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{key_name}"), ) # ✅ Добавлена проверка флага ENABLE_DELETE_KEY_BUTTON if ENABLE_DELETE_KEY_BUTTON: builder.row( - InlineKeyboardButton( - text="⏳ Продлить", callback_data=f"renew_key|{key_name}" - ), - InlineKeyboardButton( - text="❌ Удалить", callback_data=f"delete_key|{key_name}" - ), + InlineKeyboardButton(text="⏳ Продлить", callback_data=f"renew_key|{key_name}"), + InlineKeyboardButton(text="❌ Удалить", callback_data=f"delete_key|{key_name}"), ) else: - builder.row( - InlineKeyboardButton( - text="⏳ Продлить", callback_data=f"renew_key|{key_name}" - ) - ) - builder.row( - InlineKeyboardButton( - text="🔙 Назад", callback_data="view_keys" - ) - ) - builder.row( - InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile") - ) + builder.row(InlineKeyboardButton(text="⏳ Продлить", callback_data=f"renew_key|{key_name}")) + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="view_keys")) + builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) keyboard = builder.as_markup() @@ -273,9 +238,7 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session @router.callback_query(F.data.startswith("update_subscription|")) -async def process_callback_update_subscription( - callback_query: types.CallbackQuery, session: Any -): +async def process_callback_update_subscription(callback_query: types.CallbackQuery, session: Any): tg_id = callback_query.message.chat.id email = callback_query.data.split("|")[1] try: @@ -335,9 +298,7 @@ async def process_callback_update_subscription( else: await callback_query.message.answer("Ключ не найден в базе данных.") except Exception as e: - await handle_error( - tg_id, callback_query, f"Ошибка при обновлении подписки: {e}" - ) + await handle_error(tg_id, callback_query, f"Ошибка при обновлении подписки: {e}") @router.callback_query(F.data.startswith("delete_key|")) @@ -352,11 +313,7 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery): callback_data=f"confirm_delete|{client_id}", ) ], - [ - types.InlineKeyboardButton( - text="❌ Нет, отменить", callback_data="view_keys" - ) - ], + [types.InlineKeyboardButton(text="❌ Нет, отменить", callback_data="view_keys")], ] ) @@ -391,43 +348,39 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery, sessio builder.row( InlineKeyboardButton( - text=f'📅 1 месяц ({RENEWAL_PLANS["1"]["price"]} руб.)', + text=f"📅 1 месяц ({RENEWAL_PLANS['1']['price']} руб.)", callback_data=f"renew_plan|1|{client_id}", ) ) builder.row( InlineKeyboardButton( - text=f'📅 3 месяца ({RENEWAL_PLANS["3"]["price"]} руб.) {DISCOUNTS["3"]}% скидка', + text=f"📅 3 месяца ({RENEWAL_PLANS['3']['price']} руб.) {DISCOUNTS['3']}% скидка", callback_data=f"renew_plan|3|{client_id}", ) ) builder.row( InlineKeyboardButton( - text=f'📅 6 месяцев ({RENEWAL_PLANS["6"]["price"]} руб.) {DISCOUNTS["6"]}% скидка', + text=f"📅 6 месяцев ({RENEWAL_PLANS['6']['price']} руб.) {DISCOUNTS['6']}% скидка", callback_data=f"renew_plan|6|{client_id}", ) ) builder.row( InlineKeyboardButton( - text=f'📅 12 месяцев ({RENEWAL_PLANS["12"]["price"]} руб.) ({DISCOUNTS["12"]}% 🔥)', + text=f"📅 12 месяцев ({RENEWAL_PLANS['12']['price']} руб.) ({DISCOUNTS['12']}% 🔥)", callback_data=f"renew_plan|12|{client_id}", ) ) - back_button = InlineKeyboardButton( - text="🔙 Назад", callback_data="view_keys" - ) + back_button = InlineKeyboardButton(text="🔙 Назад", callback_data="view_keys") builder.row(back_button) balance = await get_balance(tg_id) response_message = PLAN_SELECTION_MSG.format( balance=balance, - expiry_date=datetime.utcfromtimestamp(expiry_time / 1000).strftime( - "%Y-%m-%d %H:%M:%S" - ), + expiry_date=datetime.utcfromtimestamp(expiry_time / 1000).strftime("%Y-%m-%d %H:%M:%S"), ) await callback_query.message.answer( @@ -441,21 +394,15 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery, sessio @router.callback_query(F.data.startswith("confirm_delete|")) -async def process_callback_confirm_delete( - callback_query: types.CallbackQuery, session: Any -): +async def process_callback_confirm_delete(callback_query: types.CallbackQuery, session: Any): email = callback_query.data.split("|")[1] try: - record = await session.fetchrow( - "SELECT client_id FROM keys WHERE email = $1", email - ) + record = await session.fetchrow("SELECT client_id FROM keys WHERE email = $1", email) if record: client_id = record["client_id"] response_message = "Ключ успешно удален." - back_button = types.InlineKeyboardButton( - text="Назад", callback_data="view_keys" - ) + back_button = types.InlineKeyboardButton(text="Назад", callback_data="view_keys") keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]]) await delete_key(client_id) @@ -470,9 +417,7 @@ async def process_callback_confirm_delete( try: tasks = [] for cluster_id, cluster in servers.items(): - tasks.append( - delete_key_from_cluster(cluster_id, email, client_id) - ) + tasks.append(delete_key_from_cluster(cluster_id, email, client_id)) await asyncio.gather(*tasks) @@ -485,9 +430,7 @@ async def process_callback_confirm_delete( else: response_message = "Ключ не найден или уже удален." - back_button = types.InlineKeyboardButton( - text="Назад", callback_data="view_keys" - ) + back_button = types.InlineKeyboardButton(text="Назад", callback_data="view_keys") keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]]) await callback_query.message.answer( @@ -529,7 +472,9 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery, sessi if balance < cost: required_amount = cost - balance - logger.info(f"[RENEW] Пользователю {tg_id} не хватает {required_amount}₽. Запуск доплаты через {USE_NEW_PAYMENT_FLOW}") + logger.info( + f"[RENEW] Пользователю {tg_id} не хватает {required_amount}₽. Запуск доплаты через {USE_NEW_PAYMENT_FLOW}" + ) await save_temporary_data( session, @@ -610,4 +555,3 @@ async def complete_key_renewal(tg_id, client_id, email, new_expiry_time, total_g logger.info(f"[RENEW] Ключ {client_id} успешно продлён на {plan} мес. для пользователя {tg_id}.") await renew_key_on_servers() - diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index 35b19f29..4f21ec14 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -13,6 +13,7 @@ from logger import logger # Глобальная переменная для пула соединений db_pool = None + async def init_db_pool(): """ Инициализация пула соединений, если он ещё не создан. @@ -21,6 +22,7 @@ async def init_db_pool(): if not db_pool: db_pool = await asyncpg.create_pool(dsn=DATABASE_URL, min_size=5, max_size=20) + async def fetch_url_content(url, tg_id): try: logger.info(f"Получение URL: {url} для tg_id: {tg_id}") @@ -32,9 +34,7 @@ async def fetch_url_content(url, tg_id): logger.info(f"Успешно получен контент с {url} для tg_id: {tg_id}") return base64.b64decode(content).decode("utf-8").split("\n") else: - logger.error( - f"Не удалось получить {url} для tg_id: {tg_id}, статус: {response.status}" - ) + logger.error(f"Не удалось получить {url} для tg_id: {tg_id}, статус: {response.status}") return [] except asyncio.TimeoutError: logger.error(f"Таймаут при получении {url} для tg_id: {tg_id}") @@ -43,6 +43,7 @@ async def fetch_url_content(url, tg_id): logger.error(f"Ошибка при получении {url} для tg_id: {tg_id}: {e}") return [] + async def combine_unique_lines(urls, tg_id, query_string): if SUPERNODE: logger.info(f"Режим SUPERNODE активен. Возвращаем первую ссылку для tg_id: {tg_id}") @@ -51,9 +52,7 @@ async def combine_unique_lines(urls, tg_id, query_string): url_with_query = f"{urls[0]}?{query_string}" if query_string else urls[0] return await fetch_url_content(url_with_query, tg_id) - logger.info( - f"Начинаем объединение подписок для tg_id: {tg_id}, запрос: {query_string}" - ) + logger.info(f"Начинаем объединение подписок для tg_id: {tg_id}, запрос: {query_string}") urls_with_query = [f"{url}?{query_string}" if query_string else url for url in urls] logger.info(f"Составлены URL-адреса: {urls_with_query}") @@ -65,19 +64,17 @@ async def combine_unique_lines(urls, tg_id, query_string): for lines in results: all_lines.update(filter(None, lines)) - logger.info( - f"Объединено {len(all_lines)} строк после фильтрации и удаления дубликатов для tg_id: {tg_id}" - ) + logger.info(f"Объединено {len(all_lines)} строк после фильтрации и удаления дубликатов для tg_id: {tg_id}") return list(all_lines) + transition_date = datetime.strptime(TRANSITION_DATE_STR, "%Y-%m-%d %H:%M:%S") transition_timestamp_ms = int(transition_date.timestamp() * 1000) transition_timestamp_ms_adjusted = transition_timestamp_ms - (3 * 60 * 60 * 1000) -logger.info( - f"Время перехода (с поправкой на часовой пояс): {transition_timestamp_ms_adjusted}" -) +logger.info(f"Время перехода (с поправкой на часовой пояс): {transition_timestamp_ms_adjusted}") + async def handle_old_subscription(request): email = request.match_info.get("email") @@ -95,9 +92,7 @@ async def handle_old_subscription(request): await init_db_pool() async with db_pool.acquire() as conn: - key_info = await conn.fetchrow( - "SELECT created_at, server_id FROM keys WHERE email = $1", email - ) + key_info = await conn.fetchrow("SELECT created_at, server_id FROM keys WHERE email = $1", email) if not key_info: logger.warning(f"Клиент с email {email} не найден в базе.") @@ -115,14 +110,10 @@ async def handle_old_subscription(request): status=400, ) - logger.info( - f"Значение created_at для клиента с email {email}: {created_at_ms}, кластер: {cluster_name}" - ) + logger.info(f"Значение created_at для клиента с email {email}: {created_at_ms}, кластер: {cluster_name}") created_at_datetime = datetime.utcfromtimestamp(created_at_ms / 1000) - logger.info( - f"Время создания клиента в формате datetime (UTC): {created_at_datetime}" - ) + logger.info(f"Время создания клиента в формате datetime (UTC): {created_at_datetime}") if created_at_ms >= transition_timestamp_ms_adjusted: logger.info(f"Клиент с email {email} является новым.") @@ -135,23 +126,18 @@ async def handle_old_subscription(request): cluster_servers = servers.get(cluster_name, []) logger.info(f"Сервера в кластере: {cluster_servers}") - urls = [ - f"{server['subscription_url']}/{email}" for server in cluster_servers - ] + urls = [f"{server['subscription_url']}/{email}" for server in cluster_servers] combined_subscriptions = await combine_unique_lines(urls, email, "") - base64_encoded = base64.b64encode( - "\n".join(combined_subscriptions).encode("utf-8") - ).decode("utf-8") + base64_encoded = base64.b64encode("\n".join(combined_subscriptions).encode("utf-8")).decode("utf-8") encoded_project_name = f"{PROJECT_NAME} - {SUB_MESSAGE}" headers = { "Content-Type": "text/plain; charset=utf-8", "Content-Disposition": "inline", "profile-update-interval": "7", - "profile-title": "base64:" - + base64.b64encode(encoded_project_name.encode("utf-8")).decode("utf-8"), + "profile-title": "base64:" + base64.b64encode(encoded_project_name.encode("utf-8")).decode("utf-8"), } logger.info(f"Возвращаем объединенные подписки для email: {email}") @@ -175,9 +161,7 @@ async def handle_new_subscription(request): await init_db_pool() async with db_pool.acquire() as conn: - client_data = await conn.fetchrow( - "SELECT tg_id, server_id FROM keys WHERE email = $1", email - ) + client_data = await conn.fetchrow("SELECT tg_id, server_id FROM keys WHERE email = $1", email) if not client_data: logger.warning(f"Клиент с email {email} не найден в базе.") @@ -199,18 +183,14 @@ async def handle_new_subscription(request): servers = await get_servers_from_db() cluster_servers = servers.get(cluster_name, []) - urls = [ - f"{server['subscription_url']}/{email}" for server in cluster_servers - ] + urls = [f"{server['subscription_url']}/{email}" for server in cluster_servers] query_string = request.query_string logger.info(f"Извлечен query string: {query_string}") combined_subscriptions = await combine_unique_lines(urls, tg_id, query_string) - base64_encoded = base64.b64encode( - "\n".join(combined_subscriptions).encode("utf-8") - ).decode("utf-8") + base64_encoded = base64.b64encode("\n".join(combined_subscriptions).encode("utf-8")).decode("utf-8") encoded_project_name = f"{PROJECT_NAME} - {SUB_MESSAGE}" @@ -218,8 +198,7 @@ async def handle_new_subscription(request): "Content-Type": "text/plain; charset=utf-8", "Content-Disposition": "inline", "profile-update-interval": "7", - "profile-title": "base64:" - + base64.b64encode(encoded_project_name.encode("utf-8")).decode("utf-8"), + "profile-title": "base64:" + base64.b64encode(encoded_project_name.encode("utf-8")).decode("utf-8"), } logger.info(f"Возвращаем объединенные подписки для email: {email}") diff --git a/handlers/keys/trial_key.py b/handlers/keys/trial_key.py index 97821fb0..9ec15090 100644 --- a/handlers/keys/trial_key.py +++ b/handlers/keys/trial_key.py @@ -16,9 +16,7 @@ from logger import logger async def create_trial_key(tg_id: int, session: Any): try: - trial_status = await session.fetchval( - "SELECT trial FROM connections WHERE tg_id = $1", tg_id - ) + trial_status = await session.fetchval("SELECT trial FROM connections WHERE tg_id = $1", tg_id) if trial_status == 1: return {"error": "Вы уже использовали пробную версию."} except Exception as e: @@ -67,7 +65,7 @@ async def create_trial_key(tg_id: int, session: Any): enable=True, flow="xtls-rprx-vision", inbound_id=int(server_info["inbound_id"]), - sub_id=base_email + sub_id=base_email, ) ) diff --git a/handlers/notifications.py b/handlers/notifications.py index cdc2263c..7ac9210c 100644 --- a/handlers/notifications.py +++ b/handlers/notifications.py @@ -36,6 +36,7 @@ from logger import logger router = Router() + async def periodic_expired_keys_check(bot: Bot): """Периодическая проверка истекших ключей с кастомным интервалом.""" while True: @@ -54,7 +55,6 @@ async def periodic_expired_keys_check(bot: Bot): await asyncio.sleep(EXPIRED_KEYS_CHECK_INTERVAL) - async def notify_expiring_keys(bot: Bot): conn = None try: @@ -84,21 +84,16 @@ async def notify_expiring_keys(bot: Bot): logger.info("Соединение с базой данных закрыто.") - async def is_bot_blocked(bot: Bot, chat_id: int) -> bool: if DEV_MODE: return False try: member = await bot.get_chat_member(chat_id, bot.id) blocked = member.status == "left" - logger.info( - f"Статус бота для пользователя {chat_id}: {'заблокирован' if blocked else 'активен'}" - ) + logger.info(f"Статус бота для пользователя {chat_id}: {'заблокирован' if blocked else 'активен'}") return blocked except Exception as e: - logger.warning( - f"Не удалось проверить статус бота для пользователя {chat_id}: {e}" - ) + logger.warning(f"Не удалось проверить статус бота для пользователя {chat_id}: {e}") return False @@ -137,7 +132,11 @@ async def process_10h_record(record, bot, conn): time_left = expiry_date - current_date days_left_message = ( - "Ключ истек" if time_left.total_seconds() <= 0 else f"{time_left.days}" if time_left.days > 0 else f"{time_left.seconds // 3600}" + "Ключ истек" + if time_left.total_seconds() <= 0 + else f"{time_left.days}" + if time_left.days > 0 + else f"{time_left.seconds // 3600}" ) message = KEY_EXPIRY_10H.format( @@ -200,7 +199,6 @@ async def notify_24h_keys( logger.info("Обработка всех уведомлений за 24 часа завершена.") - async def process_24h_record(record, bot, conn): tg_id = record["tg_id"] email = record["email"] @@ -213,7 +211,11 @@ async def process_24h_record(record, bot, conn): time_left = expiry_date - current_date days_left_message = ( - "Ключ истек" if time_left.total_seconds() <= 0 else f"{time_left.days}" if time_left.days > 0 else f"{time_left.seconds // 3600}" + "Ключ истек" + if time_left.total_seconds() <= 0 + else f"{time_left.days}" + if time_left.days > 0 + else f"{time_left.seconds // 3600}" ) message_24h = KEY_EXPIRY_24H.format( @@ -287,9 +289,7 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection): username = user.get("username", "Пользователь") try: - can_notify = await check_notification_time( - tg_id, "inactive_trial", hours=24, session=conn - ) + can_notify = await check_notification_time(tg_id, "inactive_trial", hours=24, session=conn) if can_notify: builder = InlineKeyboardBuilder() @@ -299,11 +299,7 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection): callback_data="create_key", ) ) - builder.row( - types.InlineKeyboardButton( - text="👤 Личный кабинет", callback_data="profile" - ) - ) + builder.row(types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) keyboard = builder.as_markup() message = ( @@ -315,20 +311,14 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection): try: await bot.send_message(tg_id, message, reply_markup=keyboard) - logger.info( - f"Отправлено уведомление неактивному пользователю {tg_id}." - ) + logger.info(f"Отправлено уведомление неактивному пользователю {tg_id}.") await add_notification(tg_id, "inactive_trial", session=conn) except TelegramForbiddenError: - logger.warning( - f"Бот заблокирован пользователем {tg_id}. Добавляем в blocked_users." - ) + logger.warning(f"Бот заблокирован пользователем {tg_id}. Добавляем в blocked_users.") await add_blocked_user(tg_id, conn) except Exception as e: - logger.error( - f"Ошибка при отправке уведомления пользователю {tg_id}: {e}" - ) + logger.error(f"Ошибка при отправке уведомления пользователю {tg_id}: {e}") except Exception as e: logger.error(f"Ошибка при обработке пользователя {tg_id}: {e}") @@ -378,13 +368,7 @@ async def process_key(record, bot, conn): ) keyboard = types.InlineKeyboardMarkup( - inline_keyboard=[ - [ - types.InlineKeyboardButton( - text="👤 Личный кабинет", callback_data="profile" - ) - ] - ] + inline_keyboard=[[types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")]] ) try: @@ -446,15 +430,12 @@ async def process_key(record, bot, conn): logger.error(f"Ошибка при обработке ключа для клиента {tg_id}: {e}") - async def check_online_users(): servers = await get_servers_from_db() 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 - ) + xui = AsyncApi(server["api_url"], username=ADMIN_USERNAME, password=ADMIN_PASSWORD) await xui.login() try: online_users = len(await xui.client.online()) @@ -462,6 +443,4 @@ async def check_online_users(): f"Сервер '{server['server_name']}' доступен, текущее количество активных пользователей: {online_users}." ) except Exception as e: - logger.error( - f"Не удалось проверить пользователей на сервере {server_id}: {e}" - ) + logger.error(f"Не удалось проверить пользователей на сервере {server_id}: {e}") diff --git a/handlers/pay.py b/handlers/pay.py index 2b7c753c..24dd97a1 100644 --- a/handlers/pay.py +++ b/handlers/pay.py @@ -54,15 +54,9 @@ async def handle_pay(callback_query: CallbackQuery): ) ) - builder.row( - InlineKeyboardButton( - text="🎟️ Активировать купон", callback_data="activate_coupon" - ) - ) + builder.row(InlineKeyboardButton(text="🎟️ Активировать купон", callback_data="activate_coupon")) if DONATIONS_ENABLE: - builder.row( - InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate") - ) + builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate")) builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) diff --git a/handlers/payments/robokassa_pay.py b/handlers/payments/robokassa_pay.py index 37aa3692..4a796631 100644 --- a/handlers/payments/robokassa_pay.py +++ b/handlers/payments/robokassa_pay.py @@ -52,9 +52,7 @@ if ROBOKASSA_ENABLE: def generate_payment_link(amount, inv_id, description, tg_id): """Генерация ссылки на оплату.""" - logger.debug( - f"Generating payment link for amount: {amount}, inv_id: {inv_id}, description: {description}" - ) + logger.debug(f"Generating payment link for amount: {amount}, inv_id: {inv_id}, description: {description}") payment_link = robokassa._payment.link.generate_by_script( out_sum=amount, inv_id=inv_id, @@ -66,9 +64,7 @@ def generate_payment_link(amount, inv_id, description, tg_id): @router.callback_query(F.data == "pay_robokassa") -async def process_callback_pay_robokassa( - callback_query: types.CallbackQuery, state: FSMContext, session: Any -): +async def process_callback_pay_robokassa(callback_query: types.CallbackQuery, state: FSMContext, session: Any): tg_id = callback_query.message.chat.id logger.info(f"User {tg_id} initiated Robokassa payment.") @@ -78,18 +74,18 @@ async def process_callback_pay_robokassa( builder.row( InlineKeyboardButton( text=PAYMENT_OPTIONS[i]["text"], - callback_data=f'robokassa_amount|{PAYMENT_OPTIONS[i]["callback_data"]}', + callback_data=f"robokassa_amount|{PAYMENT_OPTIONS[i]['callback_data']}", ), InlineKeyboardButton( text=PAYMENT_OPTIONS[i + 1]["text"], - callback_data=f'robokassa_amount|{PAYMENT_OPTIONS[i + 1]["callback_data"]}', + callback_data=f"robokassa_amount|{PAYMENT_OPTIONS[i + 1]['callback_data']}", ), ) else: builder.row( InlineKeyboardButton( text=PAYMENT_OPTIONS[i]["text"], - callback_data=f'robokassa_amount|{PAYMENT_OPTIONS[i]["callback_data"]}', + callback_data=f"robokassa_amount|{PAYMENT_OPTIONS[i]['callback_data']}", ) ) builder.row( @@ -117,9 +113,7 @@ async def process_callback_pay_robokassa( @router.callback_query(F.data.startswith("robokassa_amount|")) -async def process_amount_selection( - callback_query: types.CallbackQuery, state: FSMContext -): +async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext): logger.info(f"Получены данные callback_data: {callback_query.data}") data = callback_query.data.split("|") @@ -173,9 +167,7 @@ async def robokassa_webhook(request): shp_id = params.get("shp_id") signature_value = params.get("SignatureValue") - logger.info( - f"OutSum: {amount}, InvId: {inv_id}, shp_id: {shp_id}, SignatureValue: {signature_value}" - ) + logger.info(f"OutSum: {amount}, InvId: {inv_id}, shp_id: {shp_id}, SignatureValue: {signature_value}") if not check_payment_signature(params): logger.error("Неверная подпись или данные запроса.") @@ -214,9 +206,7 @@ def check_payment_signature(params): logger.info(f"Signature string before hashing: {signature_string}") - expected_signature = ( - hashlib.md5(signature_string.encode("utf-8")).hexdigest().upper() - ) + expected_signature = hashlib.md5(signature_string.encode("utf-8")).hexdigest().upper() logger.info(f"Expected signature: {expected_signature}") logger.info(f"Received signature: {signature_value}") @@ -225,9 +215,7 @@ def check_payment_signature(params): @router.callback_query(F.data == "enter_custom_amount_robokassa") -async def process_custom_amount_selection( - callback_query: types.CallbackQuery, state: FSMContext -): +async def process_custom_amount_selection(callback_query: types.CallbackQuery, state: FSMContext): tg_id = callback_query.message.chat.id logger.info(f"User {tg_id} chose to enter a custom amount.") @@ -239,13 +227,13 @@ async def process_custom_amount_selection( reply_markup=builder.as_markup(), ) - await state.set_state( - ReplenishBalanceState.waiting_for_payment_confirmation_robokassa - ) + await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_robokassa) @router.message(ReplenishBalanceState.waiting_for_payment_confirmation_robokassa) -async def handle_custom_amount_input(message: types.Message | types.CallbackQuery, state: FSMContext = None, session: Any = None): +async def handle_custom_amount_input( + message: types.Message | types.CallbackQuery, state: FSMContext = None, session: Any = None +): if isinstance(message, types.CallbackQuery): tg_id = message.message.chat.id else: @@ -255,7 +243,6 @@ async def handle_custom_amount_input(message: types.Message | types.CallbackQuer inv_id = 0 try: - conn = await asyncpg.connect(DATABASE_URL) user_data = await get_temporary_data(conn, tg_id) await conn.close() @@ -283,9 +270,13 @@ async def handle_custom_amount_input(message: types.Message | types.CallbackQuer ) if state_type == "waiting_for_payment": - message_text = f"Вы выбрали пополнение на {amount} рублей для создания нового ключа. Перейдите по ссылке для оплаты:" + message_text = ( + f"Вы выбрали пополнение на {amount} рублей для создания нового ключа. Перейдите по ссылке для оплаты:" + ) elif state_type == "waiting_for_renewal_payment": - message_text = f"Вы выбрали пополнение на {amount} рублей для продления ключа. Перейдите по ссылке для оплаты:" + message_text = ( + f"Вы выбрали пополнение на {amount} рублей для продления ключа. Перейдите по ссылке для оплаты:" + ) else: await message.answer("Некорректное состояние данных. Попробуйте снова.") return diff --git a/handlers/profile.py b/handlers/profile.py index dfd5fa23..185eb793 100644 --- a/handlers/profile.py +++ b/handlers/profile.py @@ -51,9 +51,7 @@ async def process_callback_view_profile( try: trial_status = await get_trial(chat_id, conn) - profile_message = profile_message_send( - username, chat_id, int(balance), key_count - ) + profile_message = profile_message_send(username, chat_id, int(balance), key_count) if key_count == 0: profile_message += "\n
🔧 Нажмите кнопку ➕ Подписка, чтобы настроить VPN-подключение
" @@ -81,9 +79,7 @@ async def process_callback_view_profile( InlineKeyboardButton(text=INSTRUCTIONS, callback_data="instructions"), ) if admin: - builder.row( - InlineKeyboardButton(text="🔧 Администратор", callback_data="admin") - ) + builder.row(InlineKeyboardButton(text="🔧 Администратор", callback_data="admin")) builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="start")) if os.path.isfile(image_path): @@ -122,10 +118,8 @@ async def balance_handler(callback_query: types.CallbackQuery): builder.row(InlineKeyboardButton(text=BALANCE_HISTORY, callback_data="balance_history")) builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) - await callback_query.message.answer( - "💰 Управление балансом:", - reply_markup=builder.as_markup() - ) + await callback_query.message.answer("💰 Управление балансом:", reply_markup=builder.as_markup()) + @router.callback_query(F.data == "balance_history") async def balance_history_handler(callback_query: types.CallbackQuery, session: Any): @@ -145,10 +139,10 @@ async def balance_history_handler(callback_query: types.CallbackQuery, session: if records: history_text = "📊 Последние 3 операции с балансом:\n\n" for record in records: - amount = record['amount'] - payment_system = record['payment_system'] - status = record['status'] - date = record['created_at'].strftime('%Y-%m-%d %H:%M:%S') + amount = record["amount"] + payment_system = record["payment_system"] + status = record["status"] + date = record["created_at"].strftime("%Y-%m-%d %H:%M:%S") history_text += ( f"Сумма: {amount}₽\n" f"Способ оплаты: {payment_system}\n" @@ -158,11 +152,7 @@ async def balance_history_handler(callback_query: types.CallbackQuery, session: else: history_text = "❌ У вас пока нет операций с балансом." - await callback_query.message.answer( - history_text, - reply_markup=builder.as_markup() - ) - + await callback_query.message.answer(history_text, reply_markup=builder.as_markup()) @router.message(F.text == "/tariffs") diff --git a/handlers/start.py b/handlers/start.py index 070f57fa..d7560ac3 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -13,6 +13,7 @@ from aiogram.types import ( from aiogram.utils.keyboard import InlineKeyboardBuilder from config import ( + CAPTCHA_ENABLE, CHANNEL_EXISTS, CHANNEL_URL, CONNECT_ANDROID, @@ -21,7 +22,6 @@ from config import ( DOWNLOAD_ANDROID, DOWNLOAD_IOS, SUPPORT_CHAT_URL, - CAPTCHA_ENABLE, ) from database import ( add_connection, @@ -38,32 +38,32 @@ from handlers.buttons.add_subscribe import ( PC_BUTTON, TV_BUTTON, ) +from handlers.captcha import generate_captcha from handlers.keys.key_management import create_key from handlers.keys.trial_key import create_trial_key from handlers.texts import INSTRUCTIONS_TRIAL, WELCOME_TEXT, get_about_vpn from logger import logger -from handlers.captcha import generate_captcha router = Router() @router.callback_query(F.data == "start") async def handle_start_callback_query( - callback_query: CallbackQuery, state: FSMContext, session: Any, admin: bool + callback_query: CallbackQuery, state: FSMContext, session: Any, admin: bool, captcha: bool = False ): - await start_command(callback_query.message, state, session, admin) + await start_command(callback_query.message, state, session, admin, captcha) @router.message(Command("start")) -async def start_command(message: Message, state: FSMContext, session: Any, admin: bool): +async def start_command(message: Message, state: FSMContext, session: Any, admin: bool, captcha: bool = True): """Обрабатывает команду /start, включает логику рефералов и подарков.""" logger.info(f"Вызвана функция start_command для пользователя {message.chat.id}") await state.clear() # Проверка капчи, если включена - if CAPTCHA_ENABLE: - captcha = await generate_captcha(state) + if CAPTCHA_ENABLE and captcha: + captcha = await generate_captcha(message, state) await message.answer(text=captcha["text"], reply_markup=captcha["markup"]) return @@ -74,9 +74,7 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin if not connection_exists: await add_connection(tg_id=message.chat.id, session=session) - logger.info( - f"Пользователь {message.chat.id} успешно добавлен в базу данных." - ) + logger.info(f"Пользователь {message.chat.id} успешно добавлен в базу данных.") if "gift_" in message.text: logger.info(f"Обнаружена ссылка на подарок: {message.text}") @@ -91,36 +89,24 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin ) if gift_info is None: - logger.warning( - f"Подарок с ID {gift_id} уже был использован или не существует." - ) - await message.answer( - "Этот подарок уже был использован или не существует." - ) + logger.warning(f"Подарок с ID {gift_id} уже был использован или не существует.") + await message.answer("Этот подарок уже был использован или не существует.") return await show_start_menu(message, admin, session) if gift_info["sender_tg_id"] == recipient_tg_id: logger.warning( f"Пользователь {recipient_tg_id} попытался активировать подарок, который был отправлен им самим." ) - await message.answer( - "❌ Вы не можете получить подарок от самого себя." - ) + await message.answer("❌ Вы не можете получить подарок от самого себя.") return await show_start_menu(message, admin, session) selected_months = gift_info["selected_months"] expiry_time = gift_info["expiry_time"] expiry_time_naive = expiry_time.replace(tzinfo=None) - logger.info( - f"Подарок с ID {gift_id} успешно найден для пользователя {recipient_tg_id}." - ) + logger.info(f"Подарок с ID {gift_id} успешно найден для пользователя {recipient_tg_id}.") - await create_key( - recipient_tg_id, expiry_time_naive, state, session, message - ) - logger.info( - f"Ключ создан для пользователя {recipient_tg_id} на срок {selected_months} месяцев." - ) + await create_key(recipient_tg_id, expiry_time_naive, state, session, message) + logger.info(f"Ключ создан для пользователя {recipient_tg_id} на срок {selected_months} месяцев.") await session.execute( "UPDATE gifts SET is_used = TRUE, recipient_tg_id = $1 WHERE gift_id = $2", @@ -131,9 +117,7 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin await message.answer( f"🎉 Ваш подарок на {selected_months} {'месяц' if selected_months == 1 else 'месяца' if selected_months in [2, 3, 4] else 'месяцев'} активирован!" ) - logger.info( - f"Подарок на {selected_months} месяцев активирован для пользователя {recipient_tg_id}." - ) + logger.info(f"Подарок на {selected_months} месяцев активирован для пользователя {recipient_tg_id}.") return elif "referral_" in message.text: @@ -141,21 +125,13 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin referrer_tg_id = int(message.text.split("referral_")[1]) if connection_exists: - logger.info( - f"Пользователь {message.chat.id} уже зарегистрирован и не может стать рефералом." - ) - await message.answer( - "❌ Вы уже зарегистрированы и не можете использовать реферальную ссылку." - ) + logger.info(f"Пользователь {message.chat.id} уже зарегистрирован и не может стать рефералом.") + await message.answer("❌ Вы уже зарегистрированы и не можете использовать реферальную ссылку.") return await show_start_menu(message, admin, session) if referrer_tg_id == message.chat.id: - logger.warning( - f"Пользователь {message.chat.id} попытался стать рефералом самого себя." - ) - await message.answer( - "❌ Вы не можете быть рефералом самого себя." - ) + logger.warning(f"Пользователь {message.chat.id} попытался стать рефералом самого себя.") + await message.answer("❌ Вы не можете быть рефералом самого себя.") return await show_start_menu(message, admin, session) existing_referral = await session.fetchrow( @@ -168,9 +144,7 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin return await show_start_menu(message, admin, session) await add_referral(message.chat.id, referrer_tg_id, session) - logger.info( - f"Реферал {message.chat.id} использовал ссылку от пользователя {referrer_tg_id}" - ) + logger.info(f"Реферал {message.chat.id} использовал ссылку от пользователя {referrer_tg_id}") return await show_start_menu(message, admin, session) except (ValueError, IndexError) as e: @@ -178,16 +152,12 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin return else: - logger.info( - f"Пользователь {message.chat.id} зашел без реферальной ссылки или подарка." - ) + logger.info(f"Пользователь {message.chat.id} зашел без реферальной ссылки или подарка.") await show_start_menu(message, admin, session) except (ValueError, IndexError) as e: - logger.error( - f"Ошибка при обработке сообщения пользователя {message.chat.id}: {e}" - ) + logger.error(f"Ошибка при обработке сообщения пользователя {message.chat.id}: {e}") await message.answer("❌ Произошла ошибка. Пожалуйста, попробуйте снова.") else: await show_start_menu(message, admin, session) @@ -202,26 +172,20 @@ async def show_start_menu(message: Message, admin: bool, session: Any): builder = InlineKeyboardBuilder() if trial_status == 0: - builder.row( - InlineKeyboardButton(text="🔗 Подключить VPN", callback_data="connect_vpn") - ) + builder.row(InlineKeyboardButton(text="🔗 Подключить VPN", callback_data="connect_vpn")) builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) if CHANNEL_EXISTS: builder.row( InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL), - InlineKeyboardButton(text="📢 Канал", url=CHANNEL_URL) + InlineKeyboardButton(text="📢 Канал", url=CHANNEL_URL), ) else: - builder.row( - InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL) - ) + builder.row(InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL)) if admin: - builder.row( - InlineKeyboardButton(text="🔧 Администратор", callback_data="admin") - ) + builder.row(InlineKeyboardButton(text="🔧 Администратор", callback_data="admin")) builder.row(InlineKeyboardButton(text="🌐 О VPN", callback_data="about_vpn")) @@ -267,24 +231,20 @@ async def handle_connect_vpn(callback_query: CallbackQuery, session: Any): builder.row( InlineKeyboardButton( text=IMPORT_IOS, - url=f'{CONNECT_IOS}{trial_key_info["key"]}', + url=f"{CONNECT_IOS}{trial_key_info['key']}", ), InlineKeyboardButton( text=IMPORT_ANDROID, - url=f'{CONNECT_ANDROID}{trial_key_info["key"]}', + url=f"{CONNECT_ANDROID}{trial_key_info['key']}", ), ) builder.row( InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{email}"), InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{email}"), ) - builder.row( - InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile") - ) + builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) - await callback_query.message.answer( - key_message, reply_markup=builder.as_markup() - ) + await callback_query.message.answer(key_message, reply_markup=builder.as_markup()) @router.callback_query(F.data == "about_vpn") @@ -292,9 +252,7 @@ async def handle_about_vpn(callback_query: CallbackQuery): builder = InlineKeyboardBuilder() if DONATIONS_ENABLE: - builder.row( - InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate") - ) + builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate")) builder.row( InlineKeyboardButton(text="📞 Техническая поддержка", url=SUPPORT_CHAT_URL), @@ -305,6 +263,4 @@ async def handle_about_vpn(callback_query: CallbackQuery): ) builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="start")) - await callback_query.message.answer( - get_about_vpn("3.2.3-minor"), reply_markup=builder.as_markup() - ) + await callback_query.message.answer(get_about_vpn("3.2.3-minor"), reply_markup=builder.as_markup()) diff --git a/handlers/user.py b/handlers/user.py index 5e183db8..5fc65f9c 100644 --- a/handlers/user.py +++ b/handlers/user.py @@ -12,7 +12,9 @@ router = Router() @router.my_chat_member(ChatMemberUpdatedFilter(member_status_changed=KICKED)) async def user_blocked_bot(event: ChatMemberUpdated, session: Any): logger.info(f"User {event.from_user.id} blocked the bot.") - await session.execute("INSERT INTO blocked_users (tg_id) VALUES ($1) ON CONFLICT (tg_id) DO NOTHING", event.from_user.id) + await session.execute( + "INSERT INTO blocked_users (tg_id) VALUES ($1) ON CONFLICT (tg_id) DO NOTHING", event.from_user.id + ) @router.my_chat_member(ChatMemberUpdatedFilter(member_status_changed=MEMBER)) diff --git a/handlers/utils.py b/handlers/utils.py index c482ff7f..608a0f4c 100644 --- a/handlers/utils.py +++ b/handlers/utils.py @@ -14,9 +14,7 @@ from logger import logger async def get_usd_rate(): try: async with aiohttp.ClientSession() as session: - async with session.get( - "https://www.cbr-xml-daily.ru/daily_json.js" - ) as response: + async with session.get("https://www.cbr-xml-daily.ru/daily_json.js") as response: if response.status == 200: data = await response.text() usd = float(json.loads(data)["Valute"]["USD"]["Value"]) @@ -86,9 +84,7 @@ async def get_least_loaded_cluster() -> str: return least_loaded_cluster -async def handle_error( - tg_id: int, callback_query: object | None = None, message: str = "" -) -> None: +async def handle_error(tg_id: int, callback_query: object | None = None, message: str = "") -> None: """ Обрабатывает ошибку, отправляя сообщение пользователю. @@ -100,9 +96,7 @@ async def handle_error( try: if callback_query and hasattr(callback_query, "message"): try: - await bot.delete_message( - chat_id=tg_id, message_id=callback_query.message.message_id - ) + await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id) except Exception as delete_error: logger.warning(f"Не удалось удалить сообщение: {delete_error}") diff --git a/middlewares/delete.py b/middlewares/delete.py index 846ed89a..5c88f29b 100644 --- a/middlewares/delete.py +++ b/middlewares/delete.py @@ -16,9 +16,7 @@ class DeleteMessageMiddleware(BaseMiddleware): if isinstance(event, Message): if not event.text or not event.text.startswith("/start"): try: - await event.bot.delete_message( - event.chat.id, event.message_id - 1 - ) + await event.bot.delete_message(event.chat.id, event.message_id - 1) except Exception: pass await event.delete() diff --git a/middlewares/throttling.py b/middlewares/throttling.py index 8fd18052..d1085dba 100644 --- a/middlewares/throttling.py +++ b/middlewares/throttling.py @@ -59,8 +59,6 @@ class ThrottlingMiddleware(BaseMiddleware): ) self.caches[key][user.id] = None else: - logger.debug( - f"No throttling key provided for user {user.id}, proceeding without throttle." - ) + logger.debug(f"No throttling key provided for user {user.id}, proceeding without throttle.") return await handler(event, data) diff --git a/pyproject.toml b/pyproject.toml index e8e30a7a..69cbd0f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ exclude = [ [tool.ruff.format] quote-style = "double" -indent-style = "tab" +indent-style = "space" [tool.darker] src = ["."] diff --git a/servers.py b/servers.py index a5920cc6..4a2b0b81 100644 --- a/servers.py +++ b/servers.py @@ -16,9 +16,7 @@ try: from config import CLUSTERS except ImportError: CLUSTERS = None - logger.warning( - "Переменная CLUSTERS не найдена в конфигурации. Добавьте сервера через админ-панель!" - ) + logger.warning("Переменная CLUSTERS не найдена в конфигурации. Добавьте сервера через админ-панель!") async def sync_servers_with_db(): @@ -27,9 +25,7 @@ async def sync_servers_with_db(): Если CLUSTERS не найден, синхронизация не будет выполнена. """ if CLUSTERS is None: - logger.info( - "Конфигурация CLUSTERS не найдена. Синхронизация не будет выполнена." - ) + logger.info("Конфигурация CLUSTERS не найдена. Синхронизация не будет выполнена.") return try: @@ -59,13 +55,9 @@ async def sync_servers_with_db(): server_info["SUBSCRIPTION"], server_info["INBOUND_ID"], ) - logger.info( - f"Сервер {server_info['name']} из кластера {cluster_name} добавлен в базу данных." - ) + logger.info(f"Сервер {server_info['name']} из кластера {cluster_name} добавлен в базу данных.") else: - logger.info( - f"Сервер {server_info['name']} из кластера {cluster_name} уже существует." - ) + logger.info(f"Сервер {server_info['name']} из кластера {cluster_name} уже существует.") except Exception as e: logger.error(f"Ошибка при синхронизации серверов: {e}") @@ -104,25 +96,14 @@ async def notify_admin(server_name: str): current_time = datetime.now() last_notification_time = last_notification_times.get(server_name) - if ( - last_notification_time - and current_time - last_notification_time < timedelta(minutes=3) - ): - logger.info( - f"Не отправляем уведомление для сервера {server_name}, так как прошло менее 3 минут." - ) + if last_notification_time and current_time - last_notification_time < timedelta(minutes=3): + logger.info(f"Не отправляем уведомление для сервера {server_name}, так как прошло менее 3 минут.") return - logger.info( - f"Отправка уведомлений администратору о недоступности сервера {server_name}..." - ) + logger.info(f"Отправка уведомлений администратору о недоступности сервера {server_name}...") builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton( - text="Управление сервером", callback_data=f"manage_server|{server_name}" - ) - ) + builder.row(InlineKeyboardButton(text="Управление сервером", callback_data=f"manage_server|{server_name}")) for admin_id in ADMIN_ID: await bot.send_message( @@ -134,9 +115,7 @@ async def notify_admin(server_name: str): ), reply_markup=builder.as_markup(), ) - logger.info( - f"Уведомление отправлено администратору с ID {admin_id} о сервере {server_name}." - ) + logger.info(f"Уведомление отправлено администратору с ID {admin_id} о сервере {server_name}.") last_notification_times[server_name] = current_time except Exception as e: @@ -160,9 +139,7 @@ async def check_servers(): server_name = server["server_name"] server_host = extract_host(original_api_url) - logger.debug( - f"Проверка доступности сервера '{server_name}' с хостом {server_host}" - ) + logger.debug(f"Проверка доступности сервера '{server_name}' с хостом {server_host}") is_online = await ping_server(server_host) @@ -170,18 +147,12 @@ async def check_servers(): last_ping_times[server_name] = current_time else: last_ping_time = last_ping_times.get(server_name) - if last_ping_time and current_time - last_ping_time > timedelta( - minutes=3 - ): - logger.warning( - f"Сервер {server_name} не отвечает более 3 минут. Отправляю уведомление." - ) + if last_ping_time and current_time - last_ping_time > timedelta(minutes=3): + logger.warning(f"Сервер {server_name} не отвечает более 3 минут. Отправляю уведомление.") await notify_admin(server_name) elif not last_ping_time: last_ping_times[server_name] = current_time - logger.info( - f"Сервер {server_name} не отвечал ранее, но теперь зарегистрирован." - ) + logger.info(f"Сервер {server_name} не отвечал ранее, но теперь зарегистрирован.") logger.info("Завершена проверка всех серверов.") await asyncio.sleep(PING_TIME)