Merge pull request #86 from izzzzzi/main

Refactoring admin
This commit is contained in:
Vladislav Lisitsyn
2024-11-12 13:11:09 +03:00
committed by GitHub
5 changed files with 259 additions and 319 deletions
+3 -3
View File
@@ -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)
-31
View File
@@ -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 <ID клиента> <сумма пополнения>"
)
except Exception as e:
await message.reply(f"🚨 Произошла непредвиденная ошибка: {e}")
+160
View File
@@ -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 <ID клиента> <сумма пополнения>"
)
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()
@@ -5,7 +5,9 @@ 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 aiogram.types import CallbackQuery, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from filters.admin import IsAdminFilter
from loguru import logger
from bot import bot
@@ -26,13 +28,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)
@@ -51,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 = (
@@ -78,14 +81,16 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext):
f"👥 Количество рефералов: <b>{referral_count}</b>\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_"))
@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 +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)
@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(
@@ -115,12 +120,16 @@ async def handle_new_balance_input(message: types.Message, state: FSMContext):
response_message = f"✅ Баланс успешно изменен на <b>{new_balance}</b>."
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()
@@ -128,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_"))
@router.callback_query(F.data.startswith("edit_key_"), IsAdminFilter())
async def process_key_edit(callback_query: CallbackQuery):
email = callback_query.data.split("_", 2)[2]
@@ -173,27 +182,26 @@ async def process_key_edit(callback_query: CallbackQuery):
f"Сервер: <b>{server_name}</b>"
)
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(
@@ -204,22 +212,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 +244,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 +265,20 @@ async def handle_key_name_input(message: types.Message, state: FSMContext):
f"🌐 Сервер: <b>{server_name}</b>"
)
change_expiry_button = InlineKeyboardButton(
key_buttons.row(
text="⏳ Изменить время истечения",
callback_data=f"change_expiry|{email}",
)
delete_button = InlineKeyboardButton(
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")]
)
keyboard = InlineKeyboardMarkup(inline_keyboard=key_buttons)
key_buttons.row(text="🔙 Назад", callback_data="back_to_user_editor")
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 +287,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 +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)
@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 +346,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 +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|"))
@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 +386,22 @@ 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(
"<b>❓ Вы уверены, что хотите удалить ключ?</b>",
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 +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|"))
@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 +425,10 @@ 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 +445,19 @@ 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 +487,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",
)
-174
View File
@@ -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)