From e3584587b81125ed7be37dc6630b8db3b105ec9d Mon Sep 17 00:00:00 2001 From: Zakhar Izmaylov Date: Sun, 24 Nov 2024 22:35:43 +0300 Subject: [PATCH 1/8] Remove unused __init__.py file and update payment handler callback buttons to unify navigation. Change "back_to_profile" callback to "pay" in multiple payment processing files for consistency. --- __init__.py | 0 handlers/keys/trial_key.py | 7 +------ handlers/payments/cryprobot_pay.py | 2 +- handlers/payments/freekassa_pay.py | 2 +- handlers/payments/robokassa_pay.py | 2 +- handlers/payments/yookassa_pay.py | 2 +- 6 files changed, 5 insertions(+), 10 deletions(-) delete mode 100644 __init__.py diff --git a/__init__.py b/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/handlers/keys/trial_key.py b/handlers/keys/trial_key.py index 13c74362..5350eb52 100644 --- a/handlers/keys/trial_key.py +++ b/handlers/keys/trial_key.py @@ -42,12 +42,7 @@ async def create_trial_key(tg_id: int, session: Any): ) await store_key( - tg_id, - client_id, - email, - expiry_timestamp, - public_link, - server_id=least_loaded_cluster, + tg_id, client_id, email, expiry_timestamp, public_link, server_id=least_loaded_cluster, session=session ) await use_trial(tg_id, session) return result diff --git a/handlers/payments/cryprobot_pay.py b/handlers/payments/cryprobot_pay.py index d25c263d..9a612586 100644 --- a/handlers/payments/cryprobot_pay.py +++ b/handlers/payments/cryprobot_pay.py @@ -54,7 +54,7 @@ async def process_callback_pay_cryptobot(callback_query: types.CallbackQuery, st callback_data="enter_custom_amount_crypto", ) ) - builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile")) + builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay")) key_count = await get_key_count(callback_query.message.chat.id) if key_count == 0: exists = await check_connection_exists(callback_query.message.chat.id) diff --git a/handlers/payments/freekassa_pay.py b/handlers/payments/freekassa_pay.py index b3c26790..7c423129 100644 --- a/handlers/payments/freekassa_pay.py +++ b/handlers/payments/freekassa_pay.py @@ -110,7 +110,7 @@ async def process_callback_pay_freekassa(callback_query: types.CallbackQuery, st callback_data="enter_custom_amount_freekassa", ) ) - builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile")) + builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay")) await callback_query.message.answer( text="Выберите сумму пополнения через FreeKassa:", diff --git a/handlers/payments/robokassa_pay.py b/handlers/payments/robokassa_pay.py index f9248aec..f2e50906 100644 --- a/handlers/payments/robokassa_pay.py +++ b/handlers/payments/robokassa_pay.py @@ -79,7 +79,7 @@ async def process_callback_pay_robokassa(callback_query: types.CallbackQuery, st callback_data="enter_custom_amount_robokassa", ) ) - builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile")) + builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay")) key_count = await get_key_count(tg_id) diff --git a/handlers/payments/yookassa_pay.py b/handlers/payments/yookassa_pay.py index edc9f071..7d073a55 100644 --- a/handlers/payments/yookassa_pay.py +++ b/handlers/payments/yookassa_pay.py @@ -61,7 +61,7 @@ async def process_callback_pay_yookassa(callback_query: types.CallbackQuery, sta callback_data="enter_custom_amount_yookassa", ) ) - builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) + builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay")) key_count = await get_key_count(tg_id) From 900ffd3e2e9c6e42284ba6d88739eaf276886d1a Mon Sep 17 00:00:00 2001 From: Zakhar Izmaylov Date: Sun, 24 Nov 2024 23:45:27 +0300 Subject: [PATCH 2/8] Refactor notification and profile handlers to include RENEWAL_PLANS from config. Update key management and keys handlers to streamline imports and enhance code organization. --- handlers/keys/key_management.py | 4 ++-- handlers/keys/keys.py | 3 +-- handlers/notifications.py | 4 ++-- handlers/profile.py | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/handlers/keys/key_management.py b/handlers/keys/key_management.py index 780a7e71..f4e0057e 100644 --- a/handlers/keys/key_management.py +++ b/handlers/keys/key_management.py @@ -9,7 +9,7 @@ from aiogram.fsm.state import State, StatesGroup from aiogram.types import CallbackQuery, InlineKeyboardButton, Message from aiogram.utils.keyboard import InlineKeyboardBuilder -from config import CONNECT_ANDROID, CONNECT_IOS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, PUBLIC_LINK, SUPPORT_CHAT_URL +from config import CONNECT_ANDROID, CONNECT_IOS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, PUBLIC_LINK, SUPPORT_CHAT_URL,RENEWAL_PLANS from database import ( add_connection, check_connection_exists, @@ -20,7 +20,7 @@ from database import ( use_trial, ) from handlers.keys.key_utils import create_key_on_cluster -from handlers.texts import KEY, KEY_TRIAL, NULL_BALANCE, RENEWAL_PLANS, key_message_success +from handlers.texts import KEY, KEY_TRIAL, NULL_BALANCE, key_message_success from handlers.utils import get_least_loaded_cluster, sanitize_key_name from logger import logger diff --git a/handlers/keys/keys.py b/handlers/keys/keys.py index 689526bf..a839dcbd 100644 --- a/handlers/keys/keys.py +++ b/handlers/keys/keys.py @@ -8,7 +8,7 @@ from aiogram import F, Router, types from aiogram.types import BufferedInputFile, InlineKeyboardButton from aiogram.utils.keyboard import InlineKeyboardBuilder -from config import CLUSTERS, CONNECT_ANDROID, CONNECT_IOS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, PUBLIC_LINK, TOTAL_GB +from config import CLUSTERS, CONNECT_ANDROID, CONNECT_IOS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, PUBLIC_LINK, TOTAL_GB,RENEWAL_PLANS from database import delete_key, get_balance, store_key, update_balance, update_key_expiry from handlers.keys.key_utils import ( delete_key_from_cluster, @@ -21,7 +21,6 @@ from handlers.texts import ( KEY_NOT_FOUND_MSG, NO_KEYS, PLAN_SELECTION_MSG, - RENEWAL_PLANS, SUCCESS_RENEWAL_MSG, key_message, ) diff --git a/handlers/notifications.py b/handlers/notifications.py index 613f940a..1400ef0d 100644 --- a/handlers/notifications.py +++ b/handlers/notifications.py @@ -7,7 +7,7 @@ import asyncpg from py3xui import AsyncApi from client import delete_client -from config import ADMIN_PASSWORD, ADMIN_USERNAME, CLUSTERS, DATABASE_URL, TOTAL_GB, TRIAL_TIME +from config import ADMIN_PASSWORD, ADMIN_USERNAME, CLUSTERS, DATABASE_URL, TOTAL_GB, TRIAL_TIME,RENEWAL_PLANS from database import ( add_notification, check_notification_time, @@ -17,7 +17,7 @@ from database import ( update_key_expiry, ) from handlers.keys.key_utils import renew_key_in_cluster -from handlers.texts import KEY_EXPIRY_10H, KEY_EXPIRY_24H, KEY_RENEWED, RENEWAL_PLANS +from handlers.texts import KEY_EXPIRY_10H, KEY_EXPIRY_24H, KEY_RENEWED from logger import logger router = Router() diff --git a/handlers/profile.py b/handlers/profile.py index e4d67c20..8bb0fe55 100644 --- a/handlers/profile.py +++ b/handlers/profile.py @@ -5,9 +5,9 @@ from aiogram.fsm.context import FSMContext from aiogram.types import BufferedInputFile, InlineKeyboardButton from aiogram.utils.keyboard import InlineKeyboardBuilder -from config import CHANNEL_URL +from config import CHANNEL_URL,RENEWAL_PLANS from database import get_balance, get_key_count, get_referral_stats -from handlers.texts import RENEWAL_PLANS, get_referral_link, invite_message_send, profile_message_send +from handlers.texts import get_referral_link, invite_message_send, profile_message_send router = Router() From 27db7f6814c620cdc3ae4d51b878d4751163be3c Mon Sep 17 00:00:00 2001 From: Zakhar Izmaylov Date: Sun, 24 Nov 2024 23:45:38 +0300 Subject: [PATCH 3/8] Refactor import statements in notification, profile, key management, and keys handlers for improved organization and consistency. Ensure RENEWAL_PLANS is included in all relevant files. --- handlers/keys/key_management.py | 10 +++++++++- handlers/keys/keys.py | 11 ++++++++++- handlers/notifications.py | 2 +- handlers/profile.py | 2 +- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/handlers/keys/key_management.py b/handlers/keys/key_management.py index f4e0057e..3cb78646 100644 --- a/handlers/keys/key_management.py +++ b/handlers/keys/key_management.py @@ -9,7 +9,15 @@ from aiogram.fsm.state import State, StatesGroup from aiogram.types import CallbackQuery, InlineKeyboardButton, Message from aiogram.utils.keyboard import InlineKeyboardBuilder -from config import CONNECT_ANDROID, CONNECT_IOS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, PUBLIC_LINK, SUPPORT_CHAT_URL,RENEWAL_PLANS +from config import ( + CONNECT_ANDROID, + CONNECT_IOS, + DOWNLOAD_ANDROID, + DOWNLOAD_IOS, + PUBLIC_LINK, + RENEWAL_PLANS, + SUPPORT_CHAT_URL, +) from database import ( add_connection, check_connection_exists, diff --git a/handlers/keys/keys.py b/handlers/keys/keys.py index a839dcbd..b14940b8 100644 --- a/handlers/keys/keys.py +++ b/handlers/keys/keys.py @@ -8,7 +8,16 @@ from aiogram import F, Router, types from aiogram.types import BufferedInputFile, InlineKeyboardButton from aiogram.utils.keyboard import InlineKeyboardBuilder -from config import CLUSTERS, CONNECT_ANDROID, CONNECT_IOS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, PUBLIC_LINK, TOTAL_GB,RENEWAL_PLANS +from config import ( + CLUSTERS, + CONNECT_ANDROID, + CONNECT_IOS, + DOWNLOAD_ANDROID, + DOWNLOAD_IOS, + PUBLIC_LINK, + RENEWAL_PLANS, + TOTAL_GB, +) from database import delete_key, get_balance, store_key, update_balance, update_key_expiry from handlers.keys.key_utils import ( delete_key_from_cluster, diff --git a/handlers/notifications.py b/handlers/notifications.py index 1400ef0d..3940c726 100644 --- a/handlers/notifications.py +++ b/handlers/notifications.py @@ -7,7 +7,7 @@ import asyncpg from py3xui import AsyncApi from client import delete_client -from config import ADMIN_PASSWORD, ADMIN_USERNAME, CLUSTERS, DATABASE_URL, TOTAL_GB, TRIAL_TIME,RENEWAL_PLANS +from config import ADMIN_PASSWORD, ADMIN_USERNAME, CLUSTERS, DATABASE_URL, RENEWAL_PLANS, TOTAL_GB, TRIAL_TIME from database import ( add_notification, check_notification_time, diff --git a/handlers/profile.py b/handlers/profile.py index 8bb0fe55..9029aa9f 100644 --- a/handlers/profile.py +++ b/handlers/profile.py @@ -5,7 +5,7 @@ from aiogram.fsm.context import FSMContext from aiogram.types import BufferedInputFile, InlineKeyboardButton from aiogram.utils.keyboard import InlineKeyboardBuilder -from config import CHANNEL_URL,RENEWAL_PLANS +from config import CHANNEL_URL, RENEWAL_PLANS from database import get_balance, get_key_count, get_referral_stats from handlers.texts import get_referral_link, invite_message_send, profile_message_send From 79ea8d431dd1d2a10a5d3ad2095578cc89e02df8 Mon Sep 17 00:00:00 2001 From: Zakhar Izmaylov Date: Mon, 25 Nov 2024 00:16:12 +0300 Subject: [PATCH 4/8] =?UTF-8?q?Update=20button=20texts=20in=20start=20and?= =?UTF-8?q?=20admin=20user=20editor=20handlers=20for=20improved=20navigati?= =?UTF-8?q?on.=20Change=20"=D0=9B=D0=B8=D1=87=D0=BD=D1=8B=D0=B9=20=D0=BA?= =?UTF-8?q?=D0=B0=D0=B1=D0=B8=D0=BD=D0=B5=D1=82"=20to=20"=D0=9F=D0=BE?= =?UTF-8?q?=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D0=B0"=20in=20start.py=20a?= =?UTF-8?q?nd=20adjust=20"=D0=9D=D0=B0=D0=B7=D0=B0=D0=B4"=20callback=20dat?= =?UTF-8?q?a=20in=20admin=5Fuser=5Feditor.py=20for=20consistency.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handlers/admin/admin_user_editor.py | 4 ++-- handlers/start.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/handlers/admin/admin_user_editor.py b/handlers/admin/admin_user_editor.py index c485963f..83a45c85 100644 --- a/handlers/admin/admin_user_editor.py +++ b/handlers/admin/admin_user_editor.py @@ -283,7 +283,7 @@ async def handle_key_name_input(message: types.Message, state: FSMContext, sessi builder.row( InlineKeyboardButton( text="🔙 Назад в меню администратора", - callback_data="admin", + callback_data="user_editor", ) ) @@ -314,7 +314,7 @@ async def handle_key_name_input(message: types.Message, state: FSMContext, sessi callback_data=f"delete_key_admin|{key_name}", ) ) - key_buttons.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin")) + key_buttons.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) await message.answer(response_message, reply_markup=key_buttons.as_markup()) await state.clear() diff --git a/handlers/start.py b/handlers/start.py index 2c6a7799..0a2ace43 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -80,7 +80,7 @@ async def handle_connect_vpn(callback_query: CallbackQuery, session: Any): ) builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) + builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL)) builder.row( InlineKeyboardButton(text="🍏 Скачать для iOS", url=DOWNLOAD_IOS), InlineKeyboardButton(text="🤖 Скачать для Android", url=DOWNLOAD_ANDROID), @@ -95,6 +95,7 @@ async def handle_connect_vpn(callback_query: CallbackQuery, session: Any): url=f'{CONNECT_ANDROID}{trial_key_info["key"]}', ), ) + builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) await callback_query.message.answer(key_message, reply_markup=builder.as_markup()) From 6a7890845d6030005ea479aa7e4f8c8e61ac7580 Mon Sep 17 00:00:00 2001 From: Zakhar Izmaylov Date: Mon, 25 Nov 2024 00:29:06 +0300 Subject: [PATCH 5/8] =?UTF-8?q?Refactor=20notification=20and=20admin=20use?= =?UTF-8?q?r=20editor=20handlers=20to=20improve=20user=20feedback.=20Comme?= =?UTF-8?q?nt=20out=20inactive=20trial=20user=20notifications=20in=20notif?= =?UTF-8?q?ications.py.=20Enhance=20error=20messages=20with=20consistent?= =?UTF-8?q?=20"=D0=9D=D0=B0=D0=B7=D0=B0=D0=B4"=20button=20for=20better=20n?= =?UTF-8?q?avigation=20in=20admin=5Fuser=5Feditor.py.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handlers/admin/admin_user_editor.py | 56 +++++++++++++++++++---------- handlers/notifications.py | 5 +-- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/handlers/admin/admin_user_editor.py b/handlers/admin/admin_user_editor.py index 83a45c85..63c87ebe 100644 --- a/handlers/admin/admin_user_editor.py +++ b/handlers/admin/admin_user_editor.py @@ -45,7 +45,9 @@ async def handle_username_input(message: types.Message, state: FSMContext, sessi user_record = await session.fetchrow("SELECT tg_id FROM users WHERE username = $1", username) if not user_record: - await message.answer("🔍 Пользователь с указанным username не найден. 🚫") + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) + await message.answer("🔍 Пользователь с указанным username не найден. 🚫", reply_markup=builder.as_markup()) await state.clear() return @@ -56,7 +58,9 @@ async def handle_username_input(message: types.Message, state: FSMContext, sessi referral_count = await session.fetchval("SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id) if balance is None: - await message.answer("🚫 Пользователь с указанным tg_id не найден. 🔍") + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) + await message.answer("🚫 Пользователь с указанным tg_id не найден. 🔍", reply_markup=builder.as_markup()) await state.clear() return @@ -102,7 +106,9 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext, session: referral_count = await session.fetchval("SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id) if balance is None: - await message.answer("❌ Пользователь с указанным tg_id не найден. 🔍") + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) + await message.answer("❌ Пользователь с указанным tg_id не найден. 🔍", reply_markup=builder.as_markup()) await state.clear() return @@ -163,7 +169,11 @@ async def process_balance_change(callback_query: CallbackQuery, state: FSMContex @router.message(UserEditorState.waiting_for_new_balance, IsAdminFilter()) async def handle_new_balance_input(message: types.Message, state: FSMContext, session: Any): if not message.text.isdigit() or int(message.text) < 0: - await message.answer("❌ Пожалуйста, введите корректную сумму для изменения баланса.") + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) + await message.answer( + "❌ Пожалуйста, введите корректную сумму для изменения баланса.", reply_markup=builder.as_markup() + ) return new_balance = int(message.text) @@ -239,7 +249,11 @@ async def process_key_edit(callback_query: CallbackQuery, session: Any): key_details = await get_key_details(email, session) if not key_details: - await callback_query.message.answer("🔍 Информация о ключе не найдена. 🚫") + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) + await callback_query.message.answer( + "🔍 Информация о ключе не найдена. 🚫", reply_markup=builder.as_markup() + ) return response_message = ( @@ -280,13 +294,7 @@ async def handle_key_name_input(message: types.Message, state: FSMContext, sessi if not key_details: 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(), @@ -336,7 +344,9 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext, se email = user_data.get("email") if not email: - await message.answer("📧 Email не найден в состоянии. 🚫") + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) + await message.answer("📧 Email не найден в состоянии. 🚫", reply_markup=builder.as_markup()) await state.clear() return @@ -346,13 +356,17 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext, se client_id = await get_client_id_by_email(email) if client_id is None: - await message.answer(f"🚫 Клиент с email {email} не найден. 🔍") + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) + await message.answer(f"🚫 Клиент с email {email} не найден. 🔍", reply_markup=builder.as_markup()) await state.clear() return record = await session.fetchrow("SELECT server_id FROM keys WHERE client_id = $1", client_id) if not record: - await message.answer("🚫 Клиент не найден в базе данных. 🔍") + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) + await message.answer("🚫 Клиент не найден в базе данных. 🔍", reply_markup=builder.as_markup()) await state.clear() return @@ -384,7 +398,11 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext, se builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin")) await message.answer(response_message, reply_markup=builder.as_markup()) except ValueError: - await message.answer("❌ Пожалуйста, используйте формат: YYYY-MM-DD HH:MM:SS.") + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) + await message.answer( + "❌ Пожалуйста, используйте формат: YYYY-MM-DD HH:MM:SS.", reply_markup=builder.as_markup() + ) except Exception as e: logger.error(e) await state.clear() @@ -396,9 +414,9 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery, sessi client_id = await session.fetchval("SELECT client_id FROM keys WHERE email = $1", email) if client_id is None: - await callback_query.message.answer( - "🔍 Ключ не найден. 🚫", - ) + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) + await callback_query.message.answer("🔍 Ключ не найден. 🚫", reply_markup=builder.as_markup()) return builder = InlineKeyboardBuilder() diff --git a/handlers/notifications.py b/handlers/notifications.py index 3940c726..6e92e053 100644 --- a/handlers/notifications.py +++ b/handlers/notifications.py @@ -35,8 +35,9 @@ async def notify_expiring_keys(bot: Bot): logger.info("Начало обработки уведомлений.") - await notify_inactive_trial_users(bot, conn) - await asyncio.sleep(1) + # TODO + # await notify_inactive_trial_users(bot, conn) + # await asyncio.sleep(1) await notify_10h_keys(bot, conn, current_time, threshold_time_10h) await asyncio.sleep(1) await notify_24h_keys(bot, conn, current_time, threshold_time_24h) From 7db545e2499c788933174ba9bdb918409f12efea Mon Sep 17 00:00:00 2001 From: Zakhar Izmaylov Date: Tue, 26 Nov 2024 02:52:15 +0300 Subject: [PATCH 6/8] Enhanced notifications and admin: CSV export for users/payments, better feedback/navigation, DEV_MODE checks for notifications, .csv in .gitignore, fixed trial expiry with TRIAL_TIME. --- .gitignore | 3 +- handlers/admin/admin_panel.py | 97 ++++++++++++++++- handlers/admin/admin_user_editor.py | 62 ++++++++++- handlers/keys/key_management.py | 5 +- handlers/keys/trial_key.py | 2 +- handlers/notifications.py | 161 ++++++++++++++-------------- handlers/start.py | 1 + main.py | 1 + 8 files changed, 244 insertions(+), 88 deletions(-) diff --git a/.gitignore b/.gitignore index 56a3eb02..2e61aaf2 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,5 @@ Thumbs.db nginx.conf scripts models.py -Dockerfile \ No newline at end of file +Dockerfile +.csv \ No newline at end of file diff --git a/handlers/admin/admin_panel.py b/handlers/admin/admin_panel.py index ce7466f6..29a2a365 100644 --- a/handlers/admin/admin_panel.py +++ b/handlers/admin/admin_panel.py @@ -1,4 +1,5 @@ from datetime import datetime +from io import BytesIO import subprocess from typing import Any @@ -6,7 +7,7 @@ from aiogram import F, Router, types from aiogram.filters import Command from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup -from aiogram.types import CallbackQuery, InlineKeyboardButton +from aiogram.types import BufferedInputFile, CallbackQuery, InlineKeyboardButton from aiogram.utils.keyboard import InlineKeyboardBuilder from backup import backup_database @@ -82,6 +83,8 @@ async def user_stats_menu(callback_query: CallbackQuery, session: Any): builder = InlineKeyboardBuilder() 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")) await callback_query.message.answer(stats_message, reply_markup=builder.as_markup()) @@ -89,6 +92,98 @@ async def user_stats_menu(callback_query: CallbackQuery, session: Any): logger.error(f"Error in user_stats_menu: {e}") +@router.callback_query(F.data == "export_users_csv", IsAdminFilter()) +async def export_users_csv(callback_query: CallbackQuery, session: Any): + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_stats")) + try: + users = await session.fetch( + """ + SELECT + u.tg_id, + u.username, + u.first_name, + u.last_name, + u.language_code, + u.is_bot, + c.balance, + c.trial + FROM users u + LEFT JOIN connections c ON u.tg_id = c.tg_id + """ + ) + + if not users: + 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 + 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" + + file_name = BytesIO(csv_data.encode("utf-8-sig")) + file_name.seek(0) + + file = BufferedInputFile(file_name.getvalue(), filename="users_export.csv") + + await callback_query.message.answer_document( + file, caption="📥 Экспорт пользователей в CSV", reply_markup=builder.as_markup() + ) + file_name.close() + + except Exception as e: + logger.error(f"Ошибка при экспорте пользователей в CSV: {e}") + await callback_query.message.answer( + "❗ Произошла ошибка при экспорте пользователей.", reply_markup=builder.as_markup() + ) + + +@router.callback_query(F.data == "export_payments_csv", IsAdminFilter()) +async def export_payments_csv(callback_query: CallbackQuery, session: Any): + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_stats")) + try: + payments = await session.fetch( + """ + SELECT + u.tg_id, + u.username, + u.first_name, + u.last_name, + p.amount, + p.payment_system, + p.status, + p.created_at + FROM users u + JOIN payments p ON u.tg_id = p.tg_id + """ + ) + + if not payments: + 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 + for payment in payments: + csv_data += f"{payment['tg_id']},{payment['username']},{payment['first_name']},{payment['last_name']},{payment['amount']},{payment['payment_system']},{payment['status']},{payment['created_at']}\n" + + file_name = BytesIO(csv_data.encode("utf-8-sig")) + file_name.seek(0) + + file = BufferedInputFile(file_name.getvalue(), filename="payments_export.csv") + + await callback_query.message.answer_document( + file, caption="📥 Экспорт платежей в CSV", reply_markup=builder.as_markup() + ) + file_name.close() + + except Exception as e: + logger.error(f"Ошибка при экспорте платежей в CSV: {e}") + await callback_query.message.answer( + "❗ Произошла ошибка при экспорте платежей.", reply_markup=builder.as_markup() + ) + + @router.callback_query(F.data == "send_to_alls", IsAdminFilter()) async def handle_send_to_all(callback_query: CallbackQuery, state: FSMContext): builder = InlineKeyboardBuilder() diff --git a/handlers/admin/admin_user_editor.py b/handlers/admin/admin_user_editor.py index 63c87ebe..4e758f2d 100644 --- a/handlers/admin/admin_user_editor.py +++ b/handlers/admin/admin_user_editor.py @@ -161,8 +161,9 @@ async def handle_restore_trial(callback_query: types.CallbackQuery, session: Any async def process_balance_change(callback_query: CallbackQuery, state: FSMContext): tg_id = int(callback_query.data.split("_")[2]) await state.update_data(tg_id=tg_id) - - await callback_query.message.answer("💸 Введите новую сумму баланса:") + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) + await callback_query.message.answer("💸 Введите новую сумму баланса:", reply_markup=builder.as_markup()) await state.set_state(UserEditorState.waiting_for_new_balance) @@ -264,6 +265,12 @@ async def process_key_edit(callback_query: CallbackQuery, session: Any): ) builder = InlineKeyboardBuilder() + builder.row( + InlineKeyboardButton( + text="ℹ️ Получить информацию о юзере", + callback_data=f"user_info|{key_details['tg_id']}", + ) + ) builder.row( InlineKeyboardButton( text="⏳ Изменить время истечения", @@ -283,7 +290,9 @@ async def process_key_edit(callback_query: CallbackQuery, session: Any): @router.callback_query(F.data == "search_by_key_name", IsAdminFilter()) async def prompt_key_name(callback_query: CallbackQuery, state: FSMContext): - await callback_query.message.answer("🔑 Введите имя ключа:") + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) + await callback_query.message.answer("🔑 Введите имя ключа:", reply_markup=builder.as_markup()) await state.set_state(UserEditorState.waiting_for_key_name) @@ -310,6 +319,12 @@ async def handle_key_name_input(message: types.Message, state: FSMContext, sessi ) key_buttons = InlineKeyboardBuilder() + key_buttons.row( + InlineKeyboardButton( + text="ℹ️ Получить информацию о юзере", + callback_data=f"user_info|{key_details['tg_id']}", + ) + ) key_buttons.row( InlineKeyboardButton( text="⏳ Изменить время истечения", @@ -458,3 +473,44 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery, s builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys")) 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): + 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) + 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) + + builder = InlineKeyboardBuilder() + + for (email,) in key_records: + 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="user_editor")) + + user_info = ( + f"📊 Информация о пользователе:\n\n" + f"🆔 ID пользователя: {tg_id}\n" + f"👤 Логин пользователя: @{username}\n" + f"💰 Баланс: {balance}\n" + f"👥 Количество рефералов: {referral_count}\n" + f"🔑 Ключи (для редактирования нажмите на ключ):" + ) + await callback_query.message.answer(user_info, reply_markup=builder.as_markup()) + await state.set_state(UserEditorState.displaying_user_info) diff --git a/handlers/keys/key_management.py b/handlers/keys/key_management.py index 3cb78646..27859099 100644 --- a/handlers/keys/key_management.py +++ b/handlers/keys/key_management.py @@ -17,6 +17,7 @@ from config import ( PUBLIC_LINK, RENEWAL_PLANS, SUPPORT_CHAT_URL, + TRIAL_TIME, ) from database import ( add_connection, @@ -125,7 +126,7 @@ async def handle_key_name_input(message: Message, state: FSMContext, session: An trial_status = await get_trial(message.chat.id, session) if trial_status == 0: - expiry_time = current_time + timedelta(days=1, hours=3) + expiry_time = current_time + timedelta(days=TRIAL_TIME) logger.info(f"Assigned 1-day trial to user {tg_id}.") else: balance = await get_balance(tg_id) @@ -141,7 +142,7 @@ async def handle_key_name_input(message: Message, state: FSMContext, session: An return await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"]) - expiry_time = current_time + timedelta(days=30, hours=3) + expiry_time = current_time + timedelta(days=30) logger.info(f"User {tg_id} balance deducted for key creation.") expiry_timestamp = int(expiry_time.timestamp() * 1000) diff --git a/handlers/keys/trial_key.py b/handlers/keys/trial_key.py index 70d5adfe..c8f81b3c 100644 --- a/handlers/keys/trial_key.py +++ b/handlers/keys/trial_key.py @@ -18,7 +18,7 @@ async def create_trial_key(tg_id: int, session: Any): instructions = INSTRUCTIONS result = {"key": public_link, "instructions": instructions} current_time = datetime.utcnow() - expiry_time = current_time + timedelta(days=TRIAL_TIME, hours=3) + expiry_time = current_time + timedelta(days=TRIAL_TIME) expiry_timestamp = int(expiry_time.timestamp() * 1000) least_loaded_cluster = await get_least_loaded_cluster() diff --git a/handlers/notifications.py b/handlers/notifications.py index 6e92e053..10f90b6c 100644 --- a/handlers/notifications.py +++ b/handlers/notifications.py @@ -7,7 +7,7 @@ import asyncpg from py3xui import AsyncApi from client import delete_client -from config import ADMIN_PASSWORD, ADMIN_USERNAME, CLUSTERS, DATABASE_URL, RENEWAL_PLANS, TOTAL_GB, TRIAL_TIME +from config import ADMIN_PASSWORD, ADMIN_USERNAME, CLUSTERS, DATABASE_URL, DEV_MODE, RENEWAL_PLANS, TOTAL_GB, TRIAL_TIME from database import ( add_notification, check_notification_time, @@ -29,15 +29,17 @@ async def notify_expiring_keys(bot: Bot): conn = await asyncpg.connect(DATABASE_URL) logger.info("Подключение к базе данных успешно.") - current_time = datetime.utcnow().timestamp() * 1000 - threshold_time_10h = (datetime.utcnow() + timedelta(hours=10)).timestamp() * 1000 - threshold_time_24h = (datetime.utcnow() + timedelta(days=1)).timestamp() * 1000 + current_time = int(datetime.utcnow().timestamp() * 1000) + threshold_time_10h = int((datetime.utcnow() + timedelta(hours=10)).timestamp() * 1000) + threshold_time_24h = int((datetime.utcnow() + timedelta(days=1)).timestamp() * 1000) logger.info("Начало обработки уведомлений.") # TODO # await notify_inactive_trial_users(bot, conn) # await asyncio.sleep(1) + await check_online_users() + await asyncio.sleep(1) await notify_10h_keys(bot, conn, current_time, threshold_time_10h) await asyncio.sleep(1) await notify_24h_keys(bot, conn, current_time, threshold_time_24h) @@ -54,6 +56,8 @@ async def notify_expiring_keys(bot: Bot): 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" @@ -104,7 +108,7 @@ async def notify_10h_keys( price=RENEWAL_PLANS["1"]["price"], ) - if not await is_bot_blocked(bot, tg_id): + if not await is_bot_blocked(bot, tg_id) and not DEV_MODE: try: keyboard = InlineKeyboardBuilder() keyboard.button(text="🔄 Продлить VPN", callback_data=f'renew_key|{record["client_id"]}') @@ -168,7 +172,7 @@ async def notify_24h_keys( expiry_date=expiry_date.strftime("%Y-%m-%d %H:%M:%S"), ) - if not await is_bot_blocked(bot, tg_id): + if not await is_bot_blocked(bot, tg_id) and not DEV_MODE: try: builder = InlineKeyboardBuilder() builder.row( @@ -260,89 +264,86 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection): async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time: float): logger.info("Проверка истекших ключей...") - - adjusted_current_time = current_time + (3 * 60 * 60 * 1000) expiring_keys = await conn.fetch( """ SELECT tg_id, client_id, expiry_time, email FROM keys WHERE expiry_time <= $1 """, - adjusted_current_time, + current_time, ) + logger.info(f"current_time {current_time}") logger.info(f"Найдено {len(expiring_keys)} истекающих ключей.") - async def process_key(record): - tg_id = record["tg_id"] - client_id = record["client_id"] - email = record["email"] - balance = await get_balance(tg_id) - expiry_time = record["expiry_time"] - expiry_date = datetime.utcfromtimestamp(expiry_time / 1000) - current_date = datetime.utcnow() - time_left = expiry_date - current_date - - logger.info( - f"Время истечения ключа: {expiry_time} (дата: {expiry_date}), Текущее время: {current_date}, Оставшееся время: {time_left}" - ) - - message_expired = ( - f"❌ Ваша подписка {email} истекла и была удалена!\n\n" - "🔍 Перейдите в профиль для создания новой подписки.\n" - "💡 Не откладывайте подключение VPN!" - ) - keyboard = types.InlineKeyboardMarkup( - inline_keyboard=[[types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")]] - ) - - try: - if balance >= RENEWAL_PLANS["1"]["price"]: - await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"]) - new_expiry_time = int((datetime.utcnow() + timedelta(days=30)).timestamp() * 1000) - await update_key_expiry(client_id, new_expiry_time) - - for cluster_id in CLUSTERS: - await renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, TOTAL_GB) - logger.info(f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}.") - - await conn.execute( - """ - UPDATE keys - SET notified = FALSE, notified_24h = FALSE - WHERE client_id = $1 - """, - client_id, - ) - logger.info(f"Флаги notified и notified_24 сброшены для клиента с ID {client_id}.") - try: - await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard) - logger.info(f"Уведомление об успешном продлении отправлено клиенту {tg_id}.") - except Exception as e: - logger.error(f"Ошибка при отправке уведомления клиенту {tg_id}: {e}") - - else: - await safe_send_message(bot, tg_id, message_expired, reply_markup=keyboard) - await delete_key(client_id) - - for cluster_id, cluster in CLUSTERS.items(): - for server_id, server in cluster.items(): - xui = AsyncApi( - server["API_URL"], - username=ADMIN_USERNAME, - password=ADMIN_PASSWORD, - ) - await delete_client(xui, email, client_id) - - except Exception as e: - logger.error(f"Ошибка при обработке ключа для клиента {tg_id}: {e}") - - await asyncio.gather(*[process_key(record) for record in expiring_keys]) + await asyncio.gather(*[process_key(record, bot, conn) for record in expiring_keys]) -async def safe_send_message(bot, tg_id, text, reply_markup=None): +async def process_key(record, bot, conn): + tg_id = record["tg_id"] + client_id = record["client_id"] + email = record["email"] + balance = await get_balance(tg_id) + expiry_time = record["expiry_time"] + expiry_date = datetime.utcfromtimestamp(expiry_time / 1000) + current_date = datetime.utcnow() + time_left = expiry_date - current_date + + logger.info( + f"Время истечения ключа: {expiry_time} (дата: {expiry_date}), Текущее время: {current_date}, Оставшееся время: {time_left}" + ) + keyboard = types.InlineKeyboardMarkup( + inline_keyboard=[[types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")]] + ) + try: - await bot.send_message(tg_id, text, reply_markup=reply_markup) - except Exception as e: - if "chat not found" in str(e): - logger.warning(f"Чат для клиента {tg_id} не найден.") + if balance >= RENEWAL_PLANS["1"]["price"]: + await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"]) + new_expiry_time = int((datetime.utcnow() + timedelta(days=30)).timestamp() * 1000) + await update_key_expiry(client_id, new_expiry_time) + + for cluster_id in CLUSTERS: + await renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, TOTAL_GB) + logger.info(f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}.") + + await conn.execute( + """ + UPDATE keys + SET notified = FALSE, notified_24h = FALSE + WHERE client_id = $1 + """, + client_id, + ) + logger.info(f"Флаги notified и notified_24 сброшены для клиента с ID {client_id}.") + try: + await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard) + logger.info(f"Уведомление об успешном продлении отправлено клиенту {tg_id}.") + except Exception as e: + logger.error(f"Ошибка при отправке уведомления клиенту {tg_id}: {e}") + else: - logger.error(f"Ошибка при отправке сообщения клиенту {tg_id}: {e}") + await delete_key(client_id) + + for cluster_id, cluster in CLUSTERS.items(): + for server_id, server in cluster.items(): + xui = AsyncApi( + server["API_URL"], + username=ADMIN_USERNAME, + password=ADMIN_PASSWORD, + ) + await delete_client(xui, email, client_id) + + except Exception as e: + logger.error(f"Ошибка при обработке ключа для клиента {tg_id}: {e}") + + +async def check_online_users(): + for cluster_id, cluster in CLUSTERS.items(): + for server_id, server in cluster.items(): + xui = AsyncApi(server["API_URL"], username=ADMIN_USERNAME, password=ADMIN_PASSWORD, logger=logger) + await xui.login() + try: + online_users = len(await xui.client.online()) + logger.info( + f"Сервер '{server['name']}' доступен, текущее количество активных пользователей: {online_users}." + ) + except Exception as e: + logger.error(f"Не удалось проверить пользователей на сервере {server_id}: {e}") diff --git a/handlers/start.py b/handlers/start.py index 0a2ace43..c5e240e8 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -22,6 +22,7 @@ async def handle_start_callback_query(callback_query: CallbackQuery, state: FSMC @router.message(Command("start")) async def start_command(message: Message, state: FSMContext, session: Any, admin: bool): + await state.clear() if message.text: try: referrer_tg_id = int(message.text.split("referral_")[1]) diff --git a/main.py b/main.py index 54d2efd0..6c23e88d 100644 --- a/main.py +++ b/main.py @@ -71,6 +71,7 @@ async def main(): logger.info("Запуск в режиме разработки...") await bot.delete_webhook() await init_db() + asyncio.create_task(periodic_notifications()) await dp.start_polling(bot) else: logger.info("Запуск в production режиме...") From a6e07c80806ee55bd76b2365bc66f3174c10e964 Mon Sep 17 00:00:00 2001 From: Zakhar Izmaylov Date: Tue, 26 Nov 2024 02:54:32 +0300 Subject: [PATCH 7/8] Small fix --- handlers/notifications.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/handlers/notifications.py b/handlers/notifications.py index 10f90b6c..c83a8c6b 100644 --- a/handlers/notifications.py +++ b/handlers/notifications.py @@ -320,8 +320,6 @@ async def process_key(record, bot, conn): logger.error(f"Ошибка при отправке уведомления клиенту {tg_id}: {e}") else: - await delete_key(client_id) - for cluster_id, cluster in CLUSTERS.items(): for server_id, server in cluster.items(): xui = AsyncApi( @@ -330,7 +328,7 @@ async def process_key(record, bot, conn): password=ADMIN_PASSWORD, ) await delete_client(xui, email, client_id) - + await delete_key(client_id) except Exception as e: logger.error(f"Ошибка при обработке ключа для клиента {tg_id}: {e}") @@ -338,7 +336,7 @@ async def process_key(record, bot, conn): async def check_online_users(): for cluster_id, cluster in CLUSTERS.items(): for server_id, server in cluster.items(): - xui = AsyncApi(server["API_URL"], username=ADMIN_USERNAME, password=ADMIN_PASSWORD, logger=logger) + xui = AsyncApi(server["API_URL"], username=ADMIN_USERNAME, password=ADMIN_PASSWORD) await xui.login() try: online_users = len(await xui.client.online()) From f6af8e321220a71bc97d7d1de3fa6a7a5ab6a0c4 Mon Sep 17 00:00:00 2001 From: Zakhar Izmaylov Date: Tue, 26 Nov 2024 03:21:43 +0300 Subject: [PATCH 8/8] Fix start --- handlers/admin/admin_user_editor.py | 8 ++++++-- handlers/notifications.py | 1 + middlewares/delete.py | 11 ++++++----- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/handlers/admin/admin_user_editor.py b/handlers/admin/admin_user_editor.py index 4e758f2d..a9605053 100644 --- a/handlers/admin/admin_user_editor.py +++ b/handlers/admin/admin_user_editor.py @@ -29,13 +29,17 @@ class UserEditorState(StatesGroup): @router.callback_query(F.data == "search_by_tg_id", IsAdminFilter()) async def prompt_tg_id(callback_query: CallbackQuery, state: FSMContext): - await callback_query.message.answer("🔍 Введите Telegram ID клиента:") + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) + await callback_query.message.answer("🔍 Введите Telegram ID клиента:", reply_markup=builder.as_markup()) await state.set_state(UserEditorState.waiting_for_tg_id) @router.callback_query(F.data == "search_by_username", IsAdminFilter()) async def prompt_username(callback_query: CallbackQuery, state: FSMContext): - await callback_query.message.answer("🔍 Введите Username клиента:") + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) + await callback_query.message.answer("🔍 Введите Username клиента:", reply_markup=builder.as_markup()) await state.set_state(UserEditorState.waiting_for_username) diff --git a/handlers/notifications.py b/handlers/notifications.py index c83a8c6b..42e11761 100644 --- a/handlers/notifications.py +++ b/handlers/notifications.py @@ -328,6 +328,7 @@ async def process_key(record, bot, conn): password=ADMIN_PASSWORD, ) await delete_client(xui, email, client_id) + # await xui.client.delete_depleted(-1) await delete_key(client_id) except Exception as e: logger.error(f"Ошибка при обработке ключа для клиента {tg_id}: {e}") diff --git a/middlewares/delete.py b/middlewares/delete.py index e3911cf8..ae3d7198 100644 --- a/middlewares/delete.py +++ b/middlewares/delete.py @@ -13,11 +13,12 @@ class DeleteMessageMiddleware(BaseMiddleware): ) -> Any: if isinstance(event, (Message, CallbackQuery)): if isinstance(event, Message): - try: - await event.bot.delete_message(event.chat.id, event.message_id - 1) - except Exception: - pass - await event.delete() + if not event.entities[0].type == "bot_command" and event.text == "/start": + try: + await event.bot.delete_message(event.chat.id, event.message_id - 1) + except Exception: + pass + await event.delete() elif isinstance(event, CallbackQuery): await event.answer() await event.message.delete()