From a4ca53eda97ec6d71c31cbbb1278aeeaeab074a0 Mon Sep 17 00:00:00 2001 From: Zakhar Izmaylov Date: Tue, 12 Nov 2024 07:51:54 +0300 Subject: [PATCH 1/3] Refactoring admin --- bot.py | 6 +- handlers/admin/admin.py | 31 ---- handlers/admin/admin_commands.py | 160 ++++++++++++++++ .../{user_editor.py => admin_user_editor.py} | 0 handlers/commands.py | 174 ------------------ 5 files changed, 163 insertions(+), 208 deletions(-) delete mode 100644 handlers/admin/admin.py create mode 100644 handlers/admin/admin_commands.py rename handlers/admin/{user_editor.py => admin_user_editor.py} (100%) diff --git a/bot.py b/bot.py index 991c9bbd..69b5fcce 100644 --- a/bot.py +++ b/bot.py @@ -10,13 +10,13 @@ dp = Dispatcher(bot=bot, storage=storage) router = Router() from handlers import commands, notifications, pay, profile, start -from handlers.admin import admin, admin_panel, user_editor +from handlers.admin import admin_commands, admin_panel, admin_user_editor from handlers.keys import key_management, keys from handlers.payments import cryprobot_pay, freekassa_pay, stars_pay, yookassa_pay -dp.include_router(admin.router) +dp.include_router(admin_commands.router) dp.include_router(admin_panel.router) -dp.include_router(user_editor.router) +dp.include_router(admin_user_editor.router) dp.include_router(commands.router) dp.include_router(start.router) dp.include_router(profile.router) diff --git a/handlers/admin/admin.py b/handlers/admin/admin.py deleted file mode 100644 index 309ad629..00000000 --- a/handlers/admin/admin.py +++ /dev/null @@ -1,31 +0,0 @@ -from aiogram import Router, types -from aiogram.filters import Command -from filters.admin import IsAdminFilter - -from database import add_balance_to_client, check_connection_exists - -router = Router() - - -@router.message(Command("add_balance"), IsAdminFilter()) -async def cmd_add_balance(message: types.Message): - try: - _, client_id, amount = message.text.split() - amount = float(amount) - - if not await check_connection_exists(int(client_id)): - await message.reply(f"❌ Клиент с ID {client_id} не найден в базе данных.") - return - - await add_balance_to_client(int(client_id), amount) - await message.reply( - f"✅ Баланс клиента {client_id} успешно пополнен на {amount}" - ) - except ValueError: - await message.reply( - "❓ Неверный формат команды!\n" - "Пожалуйста, используйте следующий шаблон:\n" - "/add_balance <сумма пополнения>" - ) - except Exception as e: - await message.reply(f"🚨 Произошла непредвиденная ошибка: {e}") diff --git a/handlers/admin/admin_commands.py b/handlers/admin/admin_commands.py new file mode 100644 index 00000000..ebae8b95 --- /dev/null +++ b/handlers/admin/admin_commands.py @@ -0,0 +1,160 @@ +import asyncpg +from aiogram import Router, types +from aiogram.filters import Command +from aiogram.fsm.context import FSMContext +from aiogram.fsm.state import State, StatesGroup +from filters.admin import IsAdminFilter +from loguru import logger + +from bot import bot +from config import DATABASE_URL +from database import add_balance_to_client, check_connection_exists +from handlers.texts import TRIAL + +router = Router() + + +class Form(StatesGroup): + waiting_for_server_selection = State() + waiting_for_key_name = State() + viewing_profile = State() + waiting_for_message = State() + + +@router.message(Command("add_balance"), IsAdminFilter()) +async def cmd_add_balance(message: types.Message): + try: + _, client_id, amount = message.text.split() + amount = float(amount) + + if not await check_connection_exists(int(client_id)): + await message.reply(f"❌ Клиент с ID {client_id} не найден в базе данных.") + return + + await add_balance_to_client(int(client_id), amount) + await message.reply( + f"✅ Баланс клиента {client_id} успешно пополнен на {amount}" + ) + except ValueError: + await message.reply( + "❓ Неверный формат команды!\n" + "Пожалуйста, используйте следующий шаблон:\n" + "/add_balance <сумма пополнения>" + ) + except Exception as e: + await message.reply(f"🚨 Произошла непредвиденная ошибка: {e}") + + +@router.message(Command("backup"), IsAdminFilter()) +async def backup_command(message: types.Message): + from backup import backup_database + + await message.answer("🔄 Инициализация резервного копирования базы данных...") + await backup_database() + await message.answer( + "✅ Бэкап базы данных успешно завершен и отправлен администратору." + ) + + +@router.message(Command("send_trial"), IsAdminFilter()) +async def handle_send_trial_command(message: types.Message, state: FSMContext): + try: + conn = await asyncpg.connect(DATABASE_URL) + try: + records = await conn.fetch( + """ + SELECT tg_id FROM connections WHERE trial = 0 + """ + ) + + if records: + success_count = 0 + error_count = 0 + blocked_count = 0 + + for record in records: + tg_id = record["tg_id"] + trial_message = TRIAL + try: + await bot.send_message(chat_id=tg_id, text=trial_message) + success_count += 1 + except Exception as e: + if "Forbidden: bot was blocked by the user" in str(e): + blocked_count += 1 + logger.info( + f"🚫 Бот заблокирован пользователем с tg_id: {tg_id}" + ) + else: + error_count += 1 + logger.error( + f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}" + ) + + await message.answer( + f"📊 Результаты рассылки пробных периодов:\n" + f"✅ Успешно отправлено: {success_count}\n" + f"🚫 Заблокировано: {blocked_count}\n" + f"❌ Ошибок: {error_count}" + ) + else: + await message.answer( + "📭 Нет пользователей с неиспользованными пробными ключами." + ) + + finally: + await conn.close() + + except Exception as e: + await message.answer(f"❗ Ошибка при отправке сообщений: {e}") + + +@router.message(Command("send_to_all"), IsAdminFilter()) +async def send_message_to_all_clients( + message: types.Message, state: FSMContext, from_panel=False +): + if from_panel: + await message.answer( + "✍️ Введите текст сообщения, который вы хотите отправить всем клиентам:" + ) + await state.set_state(Form.waiting_for_message) + + +@router.message(Form.waiting_for_message, IsAdminFilter()) +async def process_message_to_all( + message: types.Message, + state: FSMContext, +): + text_message = message.text + + try: + conn = await asyncpg.connect(DATABASE_URL) + tg_ids = await conn.fetch("SELECT tg_id FROM connections") + + total_users = len(tg_ids) + success_count = 0 + error_count = 0 + + for record in tg_ids: + tg_id = record["tg_id"] + try: + await bot.send_message(chat_id=tg_id, text=text_message) + success_count += 1 + except Exception as e: + error_count += 1 + logger.error( + f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}" + ) + + await message.answer( + f"📤 Рассылка завершена:\n" + f"👥 Всего пользователей: {total_users}\n" + f"✅ Успешно отправлено: {success_count}\n" + f"❌ Не доставлено: {error_count}" + ) + except Exception as e: + logger.error(f"❗ Ошибка при подключении к базе данных: {e}") + await message.answer("❌ Произошла ошибка при отправке сообщения.") + finally: + await conn.close() + + await state.clear() diff --git a/handlers/admin/user_editor.py b/handlers/admin/admin_user_editor.py similarity index 100% rename from handlers/admin/user_editor.py rename to handlers/admin/admin_user_editor.py diff --git a/handlers/commands.py b/handlers/commands.py index d1596c65..aa064a22 100644 --- a/handlers/commands.py +++ b/handlers/commands.py @@ -1,191 +1,17 @@ -import asyncpg from aiogram import Router, types from aiogram.filters import Command from aiogram.fsm.context import FSMContext -from aiogram.fsm.state import State, StatesGroup -from aiogram.types import Message -from filters.admin import IsAdminFilter -from loguru import logger -from bot import bot -from config import DATABASE_URL -from handlers.admin.admin import cmd_add_balance -from handlers.keys.key_management import handle_key_name_input -from handlers.payments.yookassa_pay import ReplenishBalanceState, process_custom_amount_input -from handlers.profile import process_callback_view_profile from handlers.start import start_command -from handlers.texts import TRIAL router = Router() -class Form(StatesGroup): - waiting_for_server_selection = State() - waiting_for_key_name = State() - viewing_profile = State() - waiting_for_message = State() - - -@router.message(Command("backup"), IsAdminFilter()) -async def backup_command(message: Message): - from backup import backup_database - - await message.answer("🔄 Инициализация резервного копирования базы данных...") - await backup_database() - await message.answer( - "✅ Бэкап базы данных успешно завершен и отправлен администратору." - ) - - @router.message(Command("start")) async def handle_start(message: types.Message, state: FSMContext): await start_command(message) -@router.message(Command("add_balance"), IsAdminFilter()) -async def handle_add_balance(message: types.Message, state: FSMContext): - await cmd_add_balance(message) - - @router.message(Command("menu")) async def handle_menu(message: types.Message, state: FSMContext): await start_command(message) - - -@router.message(Command("send_trial"), IsAdminFilter()) -async def handle_send_trial_command(message: types.Message, state: FSMContext): - try: - conn = await asyncpg.connect(DATABASE_URL) - try: - records = await conn.fetch( - """ - SELECT tg_id FROM connections WHERE trial = 0 - """ - ) - - if records: - success_count = 0 - error_count = 0 - blocked_count = 0 - - for record in records: - tg_id = record["tg_id"] - trial_message = TRIAL - try: - await bot.send_message(chat_id=tg_id, text=trial_message) - success_count += 1 - except Exception as e: - if "Forbidden: bot was blocked by the user" in str(e): - blocked_count += 1 - logger.info( - f"🚫 Бот заблокирован пользователем с tg_id: {tg_id}" - ) - else: - error_count += 1 - logger.error( - f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}" - ) - - await message.answer( - f"📊 Результаты рассылки пробных периодов:\n" - f"✅ Успешно отправлено: {success_count}\n" - f"🚫 Заблокировано: {blocked_count}\n" - f"❌ Ошибок: {error_count}" - ) - else: - await message.answer( - "📭 Нет пользователей с неиспользованными пробными ключами." - ) - - finally: - await conn.close() - - except Exception as e: - await message.answer(f"❗ Ошибка при отправке сообщений: {e}") - - -@router.message(Command("send_to_all"), IsAdminFilter()) -async def send_message_to_all_clients( - message: types.Message, state: FSMContext, from_panel=False -): - if from_panel: - await message.answer( - "✍️ Введите текст сообщения, который вы хотите отправить всем клиентам:" - ) - await state.set_state(Form.waiting_for_message) - - -@router.message(Form.waiting_for_message, IsAdminFilter()) -async def process_message_to_all( - message: types.Message, - state: FSMContext, -): - text_message = message.text - - try: - conn = await asyncpg.connect(DATABASE_URL) - tg_ids = await conn.fetch("SELECT tg_id FROM connections") - - total_users = len(tg_ids) - success_count = 0 - error_count = 0 - - for record in tg_ids: - tg_id = record["tg_id"] - try: - await bot.send_message(chat_id=tg_id, text=text_message) - success_count += 1 - except Exception as e: - error_count += 1 - logger.error( - f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}" - ) - - await message.answer( - f"📤 Рассылка завершена:\n" - f"👥 Всего пользователей: {total_users}\n" - f"✅ Успешно отправлено: {success_count}\n" - f"❌ Не доставлено: {error_count}" - ) - except Exception as e: - logger.error(f"❗ Ошибка при подключении к базе данных: {e}") - await message.answer("❌ Произошла ошибка при отправке сообщения.") - finally: - await conn.close() - - await state.clear() - - -@router.message() -async def handle_text(message: types.Message, state: FSMContext): - current_state = await state.get_state() - - if message.text in ["/send_to_all"]: - await send_message_to_all_clients(message, state) - return - - if message.text == "Мой профиль": - callback_query = types.CallbackQuery( - id="1", - from_user=message.from_user, - chat_instance="", - data="view_profile", - message=message, - ) - await process_callback_view_profile(callback_query, state) - return - - if current_state == ReplenishBalanceState.entering_custom_amount.state: - await process_custom_amount_input(message, state) - return - - if current_state == Form.waiting_for_key_name.state: - await handle_key_name_input(message, state) - return - - if message.text == "/backup": - await backup_command(message) - return - - elif current_state is None: - await start_command(message) From 3aeada3f339da0dab8e88dfa4d9515366effbe52 Mon Sep 17 00:00:00 2001 From: Zakhar Izmaylov Date: Tue, 12 Nov 2024 08:10:28 +0300 Subject: [PATCH 2/3] Refactoring --- handlers/admin/admin_user_editor.py | 124 ++++++++++++---------------- 1 file changed, 55 insertions(+), 69 deletions(-) diff --git a/handlers/admin/admin_user_editor.py b/handlers/admin/admin_user_editor.py index da27eb28..81527e01 100644 --- a/handlers/admin/admin_user_editor.py +++ b/handlers/admin/admin_user_editor.py @@ -7,13 +7,14 @@ from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup from loguru import logger - +from aiogram.utils.keyboard import InlineKeyboardBuilder from bot import bot from config import DATABASE_URL, SERVERS from database import get_client_id_by_email, update_key_expiry from handlers.admin.admin_panel import back_to_admin_menu from handlers.keys.key_utils import delete_key_from_server, renew_server_key from handlers.utils import sanitize_key_name +from filters.admin import IsAdminFilter router = Router() @@ -26,13 +27,13 @@ class UserEditorState(StatesGroup): waiting_for_expiry_time = State() -@router.callback_query(F.data == "search_by_tg_id") +@router.callback_query(F.data == "search_by_tg_id",IsAdminFilter()) async def prompt_tg_id(callback_query: CallbackQuery, state: FSMContext): await callback_query.message.edit_text("🔍 Введите Telegram ID клиента:") await state.set_state(UserEditorState.waiting_for_tg_id) -@router.message(UserEditorState.waiting_for_tg_id, F.text.isdigit()) +@router.message(UserEditorState.waiting_for_tg_id, F.text.isdigit(),IsAdminFilter()) async def handle_tg_id_input(message: types.Message, state: FSMContext): tg_id = int(message.text) @@ -85,7 +86,7 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext): await conn.close() -@router.callback_query(F.data.startswith("change_balance_")) +@router.callback_query(F.data.startswith("change_balance_"),IsAdminFilter()) 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) @@ -95,7 +96,7 @@ async def process_balance_change(callback_query: CallbackQuery, state: FSMContex await state.set_state(UserEditorState.waiting_for_new_balance) -@router.message(UserEditorState.waiting_for_new_balance) +@router.message(UserEditorState.waiting_for_new_balance,IsAdminFilter()) async def handle_new_balance_input(message: types.Message, state: FSMContext): if not message.text.isdigit() or int(message.text) < 0: await message.reply( @@ -128,7 +129,7 @@ async def handle_new_balance_input(message: types.Message, state: FSMContext): await state.clear() -@router.callback_query(F.data.startswith("edit_key_")) +@router.callback_query(F.data.startswith("edit_key_"),IsAdminFilter()) async def process_key_edit(callback_query: CallbackQuery): email = callback_query.data.split("_", 2)[2] @@ -204,22 +205,17 @@ async def process_key_edit(callback_query: CallbackQuery): await conn.close() except Exception as e: - await handle_error( - callback_query.from_user.id, - callback_query, - f"Ошибка при получении информации о ключе: {e}", - ) - + logger.error(f"Ошибка при получении информации о ключе: {e}") await callback_query.answer() -@router.callback_query(F.data == "search_by_key_name") +@router.callback_query(F.data == "search_by_key_name",IsAdminFilter()) async def prompt_key_name(callback_query: CallbackQuery, state: FSMContext): await callback_query.message.edit_text("🔑 Введите имя ключа:") await state.set_state(UserEditorState.waiting_for_key_name) -@router.message(UserEditorState.waiting_for_key_name) +@router.message(UserEditorState.waiting_for_key_name,IsAdminFilter()) async def handle_key_name_input(message: types.Message, state: FSMContext): key_name = sanitize_key_name(message.text) @@ -241,7 +237,7 @@ async def handle_key_name_input(message: types.Message, state: FSMContext): return response_messages = [] - key_buttons = [] + key_buttons = InlineKeyboardBuilder() for record in user_records: balance = record["balance"] @@ -262,24 +258,22 @@ async def handle_key_name_input(message: types.Message, state: FSMContext): f"🌐 Сервер: {server_name}" ) - change_expiry_button = InlineKeyboardButton( + key_buttons.row( text="⏳ Изменить время истечения", - callback_data=f"change_expiry|{email}", + callback_data=f"change_expiry|{email}" ) - delete_button = InlineKeyboardButton( - text="❌ Удалить ключ", callback_data=f"delete_key_admin|{email}" + key_buttons.row( + text="❌ Удалить ключ", + callback_data=f"delete_key_admin|{email}" ) - key_buttons.append([change_expiry_button, delete_button]) - - key_buttons.append( - [InlineKeyboardButton(text="Назад", callback_data="back_to_user_editor")] + key_buttons.row( + text="🔙 Назад", + callback_data="back_to_user_editor" ) - keyboard = InlineKeyboardMarkup(inline_keyboard=key_buttons) - await message.reply( - "\n".join(response_messages), reply_markup=keyboard, parse_mode="HTML" + "\n".join(response_messages), reply_markup=key_buttons.as_markup(), parse_mode="HTML" ) finally: @@ -288,7 +282,7 @@ async def handle_key_name_input(message: types.Message, state: FSMContext): await state.clear() -@router.callback_query(F.data.startswith("change_expiry|")) +@router.callback_query(F.data.startswith("change_expiry|"),IsAdminFilter()) async def prompt_expiry_change(callback_query: CallbackQuery, state: FSMContext): email = callback_query.data.split("|")[1] await callback_query.message.edit_text( @@ -299,7 +293,7 @@ async def prompt_expiry_change(callback_query: CallbackQuery, state: FSMContext) await state.set_state(UserEditorState.waiting_for_expiry_time) -@router.message(UserEditorState.waiting_for_expiry_time) +@router.message(UserEditorState.waiting_for_expiry_time,IsAdminFilter()) async def handle_expiry_time_input(message: types.Message, state: FSMContext): user_data = await state.get_data() email = user_data.get("email") @@ -347,13 +341,14 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext): response_message = f"✅ Время истечения ключа для клиента {client_id} ({email}) успешно обновлено на всех серверах." - back_button = InlineKeyboardButton( - text="Назад", callback_data="back_to_user_editor" + builder = InlineKeyboardBuilder() + builder.row( + InlineKeyboardButton( + text="🔙 Назад", callback_data="back_to_user_editor" + ) ) - keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]]) - await message.reply( - response_message, reply_markup=keyboard, parse_mode="HTML" + response_message, reply_markup=builder.as_markup(), parse_mode="HTML" ) finally: @@ -367,7 +362,7 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext): await state.clear() -@router.callback_query(F.data.startswith("delete_key_admin|")) +@router.callback_query(F.data.startswith("delete_key_admin|"),IsAdminFilter()) async def process_callback_delete_key(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id email = callback_query.data.split("|")[1] @@ -386,27 +381,24 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery): ) return - confirmation_keyboard = types.InlineKeyboardMarkup( - inline_keyboard=[ - [ - types.InlineKeyboardButton( - text="✅ Да, удалить", - callback_data=f"confirm_delete_admin|{client_id}", - ) - ], - [ - types.InlineKeyboardButton( - text="❌ Нет, отменить", callback_data="view_keys" - ) - ], - ] + builder = InlineKeyboardBuilder() + builder.row( + types.InlineKeyboardButton( + text="✅ Да, удалить", + callback_data=f"confirm_delete_admin|{client_id}" + ) + ) + builder.row( + types.InlineKeyboardButton( + text="❌ Нет, отменить", + callback_data="view_keys" + ) ) - await bot.edit_message_text( "❓ Вы уверены, что хотите удалить ключ?", chat_id=tg_id, message_id=callback_query.message.message_id, - reply_markup=confirmation_keyboard, + reply_markup=builder.as_markup(), parse_mode="HTML", ) finally: @@ -415,7 +407,7 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery): await callback_query.answer() -@router.callback_query(F.data.startswith("confirm_delete_admin|")) +@router.callback_query(F.data.startswith("confirm_delete_admin|"),IsAdminFilter()) async def process_callback_confirm_delete(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id client_id = callback_query.data.split("|")[1] @@ -430,10 +422,12 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery): if record: email = record["email"] response_message = "✅ Ключ успешно удален." - back_button = types.InlineKeyboardButton( - text="Назад", callback_data="view_keys" + builder = InlineKeyboardBuilder() + builder.row( + InlineKeyboardButton( + text="⬅️ Назад", callback_data="view_keys" + ) ) - keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]]) async def delete_key_from_servers(): tasks = [] @@ -450,20 +444,21 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery): response_message, chat_id=tg_id, message_id=callback_query.message.message_id, - reply_markup=keyboard, + reply_markup=builder.as_markup(), ) else: response_message = "🚫 Ключ не найден или уже удален." - back_button = types.InlineKeyboardButton( - text="Назад", callback_data="view_keys" + builder = InlineKeyboardBuilder() + builder.row( + InlineKeyboardButton( + text="⬅️ Назад", callback_data="view_keys" + ) ) - keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]]) - await bot.edit_message_text( response_message, chat_id=tg_id, message_id=callback_query.message.message_id, - reply_markup=keyboard, + reply_markup=builder.as_markup(), ) finally: @@ -493,12 +488,3 @@ async def delete_key_from_db(client_id): @router.callback_query(F.data == "back_to_user_editor") async def back_to_user_editor(callback_query: CallbackQuery): await back_to_admin_menu(callback_query) - - -async def handle_error(tg_id, callback_query, message): - await bot.edit_message_text( - message, - chat_id=tg_id, - message_id=callback_query.message.message_id, - parse_mode="HTML", - ) From 4df89ea7a95a06d65bb08da9f44ab8bd3bbfff85 Mon Sep 17 00:00:00 2001 From: Zakhar Izmaylov Date: Tue, 12 Nov 2024 08:10:40 +0300 Subject: [PATCH 3/3] Refactoring user editor --- handlers/admin/admin_user_editor.py | 149 ++++++++++++++-------------- 1 file changed, 74 insertions(+), 75 deletions(-) diff --git a/handlers/admin/admin_user_editor.py b/handlers/admin/admin_user_editor.py index 81527e01..305aae89 100644 --- a/handlers/admin/admin_user_editor.py +++ b/handlers/admin/admin_user_editor.py @@ -5,16 +5,17 @@ import asyncpg from aiogram import F, Router, types from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup -from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup -from loguru import logger +from aiogram.types import CallbackQuery, InlineKeyboardButton from aiogram.utils.keyboard import InlineKeyboardBuilder +from filters.admin import IsAdminFilter +from loguru import logger + from bot import bot from config import DATABASE_URL, SERVERS from database import get_client_id_by_email, update_key_expiry from handlers.admin.admin_panel import back_to_admin_menu from handlers.keys.key_utils import delete_key_from_server, renew_server_key from handlers.utils import sanitize_key_name -from filters.admin import IsAdminFilter router = Router() @@ -27,13 +28,13 @@ class UserEditorState(StatesGroup): waiting_for_expiry_time = State() -@router.callback_query(F.data == "search_by_tg_id",IsAdminFilter()) +@router.callback_query(F.data == "search_by_tg_id", IsAdminFilter()) async def prompt_tg_id(callback_query: CallbackQuery, state: FSMContext): await callback_query.message.edit_text("🔍 Введите Telegram ID клиента:") await state.set_state(UserEditorState.waiting_for_tg_id) -@router.message(UserEditorState.waiting_for_tg_id, F.text.isdigit(),IsAdminFilter()) +@router.message(UserEditorState.waiting_for_tg_id, F.text.isdigit(), IsAdminFilter()) async def handle_tg_id_input(message: types.Message, state: FSMContext): tg_id = int(message.text) @@ -52,25 +53,26 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext): await state.clear() return - key_buttons = [ - [InlineKeyboardButton(text=email, callback_data=f"edit_key_{email}")] - for email, in key_records - ] - keyboard = InlineKeyboardMarkup( - inline_keyboard=[ - *key_buttons, - [ - InlineKeyboardButton( - text="📝 Изменить баланс", - callback_data=f"change_balance_{tg_id}", - ) - ], - [ - InlineKeyboardButton( - text="Назад", callback_data="back_to_user_editor" - ) - ], - ] + 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="back_to_user_editor") ) user_info = ( @@ -79,14 +81,16 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext): f"👥 Количество рефералов: {referral_count}\n" f"🔑 Ключи (для редактирования нажмите на ключ):" ) - await message.reply(user_info, reply_markup=keyboard, parse_mode="HTML") + await message.reply( + user_info, reply_markup=builder.as_markup(), parse_mode="HTML" + ) await state.set_state(UserEditorState.displaying_user_info) finally: await conn.close() -@router.callback_query(F.data.startswith("change_balance_"),IsAdminFilter()) +@router.callback_query(F.data.startswith("change_balance_"), IsAdminFilter()) 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) @@ -96,7 +100,7 @@ async def process_balance_change(callback_query: CallbackQuery, state: FSMContex await state.set_state(UserEditorState.waiting_for_new_balance) -@router.message(UserEditorState.waiting_for_new_balance,IsAdminFilter()) +@router.message(UserEditorState.waiting_for_new_balance, IsAdminFilter()) async def handle_new_balance_input(message: types.Message, state: FSMContext): if not message.text.isdigit() or int(message.text) < 0: await message.reply( @@ -116,12 +120,16 @@ async def handle_new_balance_input(message: types.Message, state: FSMContext): response_message = f"✅ Баланс успешно изменен на {new_balance}." - back_button = InlineKeyboardButton( - text="Назад в меню админа", callback_data="back_to_user_editor" + builder = InlineKeyboardBuilder() + builder.row( + InlineKeyboardButton( + text="🔙 Назад в меню администратора", + callback_data="back_to_user_editor", + ) + ) + await message.reply( + response_message, reply_markup=builder.as_markup(), parse_mode="HTML" ) - keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]]) - - await message.reply(response_message, reply_markup=keyboard, parse_mode="HTML") finally: await conn.close() @@ -129,7 +137,7 @@ async def handle_new_balance_input(message: types.Message, state: FSMContext): await state.clear() -@router.callback_query(F.data.startswith("edit_key_"),IsAdminFilter()) +@router.callback_query(F.data.startswith("edit_key_"), IsAdminFilter()) async def process_key_edit(callback_query: CallbackQuery): email = callback_query.data.split("_", 2)[2] @@ -174,27 +182,26 @@ async def process_key_edit(callback_query: CallbackQuery): f"Сервер: {server_name}" ) - change_expiry_button = types.InlineKeyboardButton( - text="⏳ Изменить время истечения", - callback_data=f"change_expiry|{email}", + builder = InlineKeyboardBuilder() + builder.row( + InlineKeyboardButton( + text="⏳ Изменить время истечения", + callback_data=f"change_expiry|{email}", + ), + InlineKeyboardButton( + text="❌ Удалить ключ", + callback_data=f"delete_key_admin|{email}", + ), ) - delete_button = types.InlineKeyboardButton( - text="❌ Удалить ключ", callback_data=f"delete_key_admin|{email}" + builder.row( + InlineKeyboardButton( + text="🔙 Назад", callback_data="back_to_user_editor" + ) ) - - keyboard = types.InlineKeyboardMarkup( - inline_keyboard=[ - [change_expiry_button, delete_button], - [ - InlineKeyboardButton( - text="Назад", callback_data="back_to_user_editor" - ) - ], - ] - ) - await callback_query.message.edit_text( - response_message, reply_markup=keyboard, parse_mode="HTML" + response_message, + reply_markup=builder.as_markup(), + parse_mode="HTML", ) else: await callback_query.message.edit_text( @@ -209,13 +216,13 @@ async def process_key_edit(callback_query: CallbackQuery): await callback_query.answer() -@router.callback_query(F.data == "search_by_key_name",IsAdminFilter()) +@router.callback_query(F.data == "search_by_key_name", IsAdminFilter()) async def prompt_key_name(callback_query: CallbackQuery, state: FSMContext): await callback_query.message.edit_text("🔑 Введите имя ключа:") await state.set_state(UserEditorState.waiting_for_key_name) -@router.message(UserEditorState.waiting_for_key_name,IsAdminFilter()) +@router.message(UserEditorState.waiting_for_key_name, IsAdminFilter()) async def handle_key_name_input(message: types.Message, state: FSMContext): key_name = sanitize_key_name(message.text) @@ -260,20 +267,18 @@ async def handle_key_name_input(message: types.Message, state: FSMContext): key_buttons.row( text="⏳ Изменить время истечения", - callback_data=f"change_expiry|{email}" + callback_data=f"change_expiry|{email}", ) key_buttons.row( - text="❌ Удалить ключ", - callback_data=f"delete_key_admin|{email}" + text="❌ Удалить ключ", callback_data=f"delete_key_admin|{email}" ) - key_buttons.row( - text="🔙 Назад", - callback_data="back_to_user_editor" - ) + key_buttons.row(text="🔙 Назад", callback_data="back_to_user_editor") await message.reply( - "\n".join(response_messages), reply_markup=key_buttons.as_markup(), parse_mode="HTML" + "\n".join(response_messages), + reply_markup=key_buttons.as_markup(), + parse_mode="HTML", ) finally: @@ -282,7 +287,7 @@ async def handle_key_name_input(message: types.Message, state: FSMContext): await state.clear() -@router.callback_query(F.data.startswith("change_expiry|"),IsAdminFilter()) +@router.callback_query(F.data.startswith("change_expiry|"), IsAdminFilter()) async def prompt_expiry_change(callback_query: CallbackQuery, state: FSMContext): email = callback_query.data.split("|")[1] await callback_query.message.edit_text( @@ -293,7 +298,7 @@ async def prompt_expiry_change(callback_query: CallbackQuery, state: FSMContext) await state.set_state(UserEditorState.waiting_for_expiry_time) -@router.message(UserEditorState.waiting_for_expiry_time,IsAdminFilter()) +@router.message(UserEditorState.waiting_for_expiry_time, IsAdminFilter()) async def handle_expiry_time_input(message: types.Message, state: FSMContext): user_data = await state.get_data() email = user_data.get("email") @@ -362,7 +367,7 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext): await state.clear() -@router.callback_query(F.data.startswith("delete_key_admin|"),IsAdminFilter()) +@router.callback_query(F.data.startswith("delete_key_admin|"), IsAdminFilter()) async def process_callback_delete_key(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id email = callback_query.data.split("|")[1] @@ -384,14 +389,12 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery): builder = InlineKeyboardBuilder() builder.row( types.InlineKeyboardButton( - text="✅ Да, удалить", - callback_data=f"confirm_delete_admin|{client_id}" + text="✅ Да, удалить", callback_data=f"confirm_delete_admin|{client_id}" ) ) builder.row( types.InlineKeyboardButton( - text="❌ Нет, отменить", - callback_data="view_keys" + text="❌ Нет, отменить", callback_data="view_keys" ) ) await bot.edit_message_text( @@ -407,7 +410,7 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery): await callback_query.answer() -@router.callback_query(F.data.startswith("confirm_delete_admin|"),IsAdminFilter()) +@router.callback_query(F.data.startswith("confirm_delete_admin|"), IsAdminFilter()) async def process_callback_confirm_delete(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id client_id = callback_query.data.split("|")[1] @@ -424,9 +427,7 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery): response_message = "✅ Ключ успешно удален." builder = InlineKeyboardBuilder() builder.row( - InlineKeyboardButton( - text="⬅️ Назад", callback_data="view_keys" - ) + InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys") ) async def delete_key_from_servers(): @@ -450,9 +451,7 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery): response_message = "🚫 Ключ не найден или уже удален." builder = InlineKeyboardBuilder() builder.row( - InlineKeyboardButton( - text="⬅️ Назад", callback_data="view_keys" - ) + InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys") ) await bot.edit_message_text( response_message,