Files
Roblox-Buyer/handlers/admin.py
T
2026-07-20 08:58:10 +05:00

522 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from aiogram import F, Router
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.types import (
CallbackQuery,
FSInputFile,
InlineKeyboardButton,
InlineKeyboardMarkup,
Message,
)
from config import ADMIN_ID, ROBSEC_FILE
from db.storage import load_config
from db.users import get
from keyboards.admin import admin_menu_kb
from keyboards.user import back_kb
from services.admin import (
add_balance,
add_treasury,
ban_user,
broadcast,
clear_robsec,
find_user,
is_admin,
robsec_info,
sub_balance,
toggle_bot,
toggle_shop,
toggle_user_admin,
update_min_withdraw,
update_rate,
)
from services.withdrawals import check_treasury_invoice, create_treasury_invoice
from states.admin import Form
from runtime import get_bot
from utils.logging import safe_edit
router = Router(name="admin")
def _admin(user_id: int) -> bool:
return user_id == ADMIN_ID or is_admin(user_id)
def _user_card(uid: int) -> tuple[str, InlineKeyboardMarkup] | None:
profile = get(uid)
if not profile:
return None
text = (
"👤 <b>Пользователь</b>\n\n"
f"🆔 ID: <code>{uid}</code>\n"
f"👤 @{profile.username or ''}\n"
f"📅 Регистрация: {profile.registered or '?'}\n"
f"💳 Баланс: {profile.balance:.2f}\n"
f"💰 Всего заработано: {profile.total_earned:.2f}\n"
f"🍪 Cookie: {profile.cookies_loaded}\n"
f"👥 Рефералов: {len(profile.referrals)}\n"
f"👑 Админ: {'' if profile.is_admin else ''}\n"
f"🚫 Бан: {'' + profile.ban_reason if profile.is_banned else ''}"
)
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text=" Баланс", callback_data=f"u_bal_add_{uid}"
),
InlineKeyboardButton(
text=" Баланс", callback_data=f"u_bal_sub_{uid}"
),
],
[
InlineKeyboardButton(
text="🚫 Разбанить" if profile.is_banned else "🚫 Забанить",
callback_data=f"u_ban_{uid}",
)
],
[
InlineKeyboardButton(
text="👑 Убрать админку" if profile.is_admin else "👑 Выдать админку",
callback_data=f"u_admin_{uid}",
)
],
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_back")],
]
)
return text, keyboard
@router.message(Command("admin"))
async def cmd_admin(message: Message, state: FSMContext):
if not _admin(message.from_user.id):
return
await state.clear()
await message.answer(
"🛠 <b>Админ-панель</b>", parse_mode="HTML", reply_markup=admin_menu_kb()
)
@router.callback_query(F.data == "admin_close")
async def admin_close(callback: CallbackQuery, state: FSMContext):
if not _admin(callback.from_user.id):
return
await state.clear()
try:
await callback.message.delete()
except Exception:
pass
await callback.answer()
@router.callback_query(F.data == "admin_back")
async def admin_back(callback: CallbackQuery, state: FSMContext):
if not _admin(callback.from_user.id):
return
await state.clear()
await safe_edit(callback, "🛠 <b>Админ-панель</b>", reply_markup=admin_menu_kb())
await callback.answer()
@router.callback_query(F.data == "admin_toggle_shop")
async def admin_toggle_shop(callback: CallbackQuery):
if not _admin(callback.from_user.id):
return
enabled = toggle_shop()
await callback.message.edit_reply_markup(reply_markup=admin_menu_kb())
await callback.answer(f"Скупка: {'ВКЛ' if enabled else 'ВЫКЛ'}")
@router.callback_query(F.data == "admin_toggle_bot")
async def admin_toggle_bot(callback: CallbackQuery):
if not _admin(callback.from_user.id):
return
enabled = toggle_bot()
await callback.message.edit_reply_markup(reply_markup=admin_menu_kb())
await callback.answer(f"Бот: {'ВКЛ' if enabled else 'ВЫКЛ'}")
@router.callback_query(F.data == "admin_robsec_get")
async def admin_robsec_get(callback: CallbackQuery):
if not _admin(callback.from_user.id):
return
lines, size = robsec_info()
if not size:
await callback.answer("robsec.txt пуст", show_alert=True)
return
try:
bot = get_bot() or callback.message.bot
await bot.send_document(
callback.from_user.id,
FSInputFile(ROBSEC_FILE),
caption=f"📥 <b>robsec.txt</b>\nСтрок: <b>{lines}</b>\nРазмер: <b>{size} байт</b>",
)
await callback.answer("Отправлено")
except Exception as exc:
await callback.answer(f"Ошибка: {exc}", show_alert=True)
@router.callback_query(F.data == "admin_robsec_clear")
async def admin_robsec_clear_prompt(callback: CallbackQuery):
if not _admin(callback.from_user.id):
return
lines, _ = robsec_info()
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="✅ Да, очистить", callback_data="admin_robsec_clear_yes"
)
],
[InlineKeyboardButton(text="🔙 Отмена", callback_data="admin_back")],
]
)
await safe_edit(
callback,
f"🗑 <b>Очистить robsec.txt?</b>\n\nСтрок сейчас: <b>{lines}</b>\n\n⚠️ Данные будут удалены безвозвратно!",
reply_markup=keyboard,
)
@router.callback_query(F.data == "admin_robsec_clear_yes")
async def admin_robsec_clear_yes(callback: CallbackQuery):
if not _admin(callback.from_user.id):
return
clear_robsec()
await safe_edit(callback, "✅ <b>robsec.txt очищен</b>", reply_markup=admin_menu_kb())
await callback.answer("Очищено")
@router.callback_query(F.data == "admin_rates")
async def admin_rates(callback: CallbackQuery):
if not _admin(callback.from_user.id):
return
cfg = load_config()
rows = [
[
InlineKeyboardButton(
text=f"{rate.name}: {rate.price:.2f}",
callback_data=f"admin_rate_{key}",
)
]
for key, rate in cfg.rates.items()
]
rows.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_back")])
await safe_edit(
callback,
"💰 <b>Управление курсами</b>\nВыберите курс для изменения цены:",
reply_markup=InlineKeyboardMarkup(inline_keyboard=rows),
)
await callback.answer()
@router.callback_query(F.data.startswith("admin_rate_"))
async def admin_rate_select(callback: CallbackQuery, state: FSMContext):
if not _admin(callback.from_user.id):
return
key = callback.data.removeprefix("admin_rate_")
cfg = load_config()
rate = cfg.rates.get(key)
if not rate:
await callback.answer("Курс не найден", show_alert=True)
return
await state.update_data(rate_key=key)
await state.set_state(Form.admin_edit_rate)
await safe_edit(
callback,
f"💰 Курс: <b>{rate.name}</b>\nТекущая цена: {rate.price:.2f}\n\nОтправьте новую цену числом:",
reply_markup=back_kb("admin_back"),
)
@router.message(Form.admin_edit_rate)
async def admin_edit_rate_save(message: Message, state: FSMContext):
if not _admin(message.from_user.id):
return
try:
price = float((message.text or "").replace(",", "."))
if price < 0:
raise ValueError
except ValueError:
await message.answer("❌ Введите неотрицательное число.")
return
data = await state.get_data()
key = data.get("rate_key")
cfg = load_config()
if key not in cfg.rates:
await state.clear()
return
update_rate(key, price)
await state.clear()
await message.answer(
f"✅ Цена <b>{cfg.rates[key].name}</b> = {price:.2f}",
reply_markup=admin_menu_kb(),
)
@router.callback_query(F.data == "admin_min_withdraw")
async def admin_min_withdraw(callback: CallbackQuery, state: FSMContext):
if not _admin(callback.from_user.id):
return
await state.set_state(Form.admin_min_withdraw)
await safe_edit(
callback,
f"💳 Текущий минимальный вывод: {load_config().min_withdraw:.2f}\nОтправьте новое значение:",
reply_markup=back_kb("admin_back"),
)
@router.message(Form.admin_min_withdraw)
async def admin_min_withdraw_save(message: Message, state: FSMContext):
if not _admin(message.from_user.id):
return
try:
value = float((message.text or "").replace(",", "."))
if value <= 0:
raise ValueError
except ValueError:
await message.answer("❌ Введите положительное число.")
return
update_min_withdraw(value)
await state.clear()
await message.answer(
f"✅ Минимальный вывод: {value:.2f}", reply_markup=admin_menu_kb()
)
@router.callback_query(F.data == "admin_treasury")
async def admin_treasury(callback: CallbackQuery, state: FSMContext):
if not _admin(callback.from_user.id):
return
await state.set_state(Form.admin_treasury_amount)
await safe_edit(
callback,
"🏦 На какую сумму пополнить казну? (в ₽)\nОтправьте число:",
reply_markup=back_kb("admin_back"),
)
@router.message(Form.admin_treasury_amount)
async def admin_treasury_amount(message: Message, state: FSMContext):
if not _admin(message.from_user.id):
return
try:
amount = float((message.text or "").replace(",", "."))
if amount <= 0:
raise ValueError
except ValueError:
await message.answer("❌ Введите положительное число.")
return
await state.clear()
invoice = await create_treasury_invoice(amount)
if not invoice:
await message.answer(
"❌ Не удалось создать счет CryptoBot.", reply_markup=admin_menu_kb()
)
return
pay_url, invoice_id, amount_usdt = invoice
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(text="💳 Оплатить", url=pay_url)],
[
InlineKeyboardButton(
text="✅ Проверить оплату",
callback_data=f"treasury_check_{invoice_id}_{amount}",
)
],
]
)
await message.answer(
f"🏦 Счет на пополнение казны\n\n💰 Сумма: <b>{amount:.2f} ₽</b> (~{amount_usdt} USDT)\n\nПосле оплаты нажмите «Проверить оплату».",
parse_mode="HTML",
reply_markup=keyboard,
)
@router.callback_query(F.data.startswith("treasury_check_"))
async def treasury_check(callback: CallbackQuery):
if not _admin(callback.from_user.id):
return
_, _, invoice_id, amount = callback.data.split("_", 3)
try:
paid = await check_treasury_invoice(int(invoice_id))
amount_rub = float(amount)
except (ValueError, TypeError):
paid = False
amount_rub = 0.0
if not paid:
await callback.answer("Оплата еще не поступила", show_alert=True)
return
balance = add_treasury(amount_rub)
await safe_edit(
callback,
f"✅ Казна пополнена на {amount_rub:.2f}\n🏦 Всего: {balance:.2f}",
reply_markup=admin_menu_kb(),
)
await callback.answer("Оплачено")
@router.callback_query(F.data == "admin_broadcast")
async def admin_broadcast_prompt(callback: CallbackQuery, state: FSMContext):
if not _admin(callback.from_user.id):
return
await state.set_state(Form.admin_broadcast)
await safe_edit(
callback,
"📣 Отправьте сообщение для рассылки (текст или фото с подписью):",
reply_markup=back_kb("admin_back"),
)
@router.message(Form.admin_broadcast)
async def admin_broadcast_send(message: Message, state: FSMContext):
if not _admin(message.from_user.id):
return
await state.clear()
sent, failed = await broadcast(
message.bot,
text=message.text,
photo_id=message.photo[-1].file_id if message.photo else None,
caption=message.caption,
)
await message.answer(
f"✅ Рассылка завершена. Отправлено: {sent}\n❌ Ошибок: {failed}",
reply_markup=admin_menu_kb(),
)
@router.callback_query(F.data == "admin_users")
async def admin_users(callback: CallbackQuery, state: FSMContext):
if not _admin(callback.from_user.id):
return
await state.set_state(Form.admin_find_user)
await safe_edit(
callback, "Отправьте ID пользователя или @username.", reply_markup=back_kb("admin_back")
)
@router.message(Form.admin_find_user)
async def admin_find_user(message: Message, state: FSMContext):
if not _admin(message.from_user.id):
return
uid = find_user((message.text or "").strip())
await state.clear()
if uid is None:
await message.answer("❌ Пользователь не найден.", reply_markup=admin_menu_kb())
return
card = _user_card(uid)
if card:
await message.answer(card[0], parse_mode="HTML", reply_markup=card[1])
@router.callback_query(F.data.startswith("u_bal_add_"))
async def u_bal_add(callback: CallbackQuery, state: FSMContext):
if not _admin(callback.from_user.id):
return
uid = int(callback.data.removeprefix("u_bal_add_"))
await state.update_data(target_uid=uid)
await state.set_state(Form.admin_user_balance_add)
await safe_edit(
callback, "Отправьте сумму для начисления:", reply_markup=back_kb("admin_back")
)
@router.message(Form.admin_user_balance_add)
async def u_bal_add_save(message: Message, state: FSMContext):
if not _admin(message.from_user.id):
return
try:
amount = float((message.text or "").replace(",", "."))
if amount <= 0:
raise ValueError
except ValueError:
await message.answer("❌ Введите положительное число.")
return
uid = (await state.get_data()).get("target_uid")
add_balance(uid, amount)
await state.clear()
await message.answer(
f"✅ Пользователю {uid} начислено {amount:.2f} ₽.", reply_markup=admin_menu_kb()
)
@router.callback_query(F.data.startswith("u_bal_sub_"))
async def u_bal_sub(callback: CallbackQuery, state: FSMContext):
if not _admin(callback.from_user.id):
return
uid = int(callback.data.removeprefix("u_bal_sub_"))
await state.update_data(target_uid=uid)
await state.set_state(Form.admin_user_balance_sub)
await safe_edit(
callback, "Отправьте сумму для списания:", reply_markup=back_kb("admin_back")
)
@router.message(Form.admin_user_balance_sub)
async def u_bal_sub_save(message: Message, state: FSMContext):
if not _admin(message.from_user.id):
return
try:
amount = float((message.text or "").replace(",", "."))
if amount <= 0:
raise ValueError
except ValueError:
await message.answer("❌ Введите положительное число.")
return
uid = (await state.get_data()).get("target_uid")
sub_balance(uid, amount)
await state.clear()
await message.answer(
f"У пользователя {uid} списано {amount:.2f} ₽.", reply_markup=admin_menu_kb()
)
@router.callback_query(F.data.startswith("u_ban_"))
async def u_ban(callback: CallbackQuery, state: FSMContext):
if not _admin(callback.from_user.id):
return
uid = int(callback.data.removeprefix("u_ban_"))
profile = get(uid)
if not profile:
return
if profile.is_banned:
from services.admin import unban_user
unban_user(uid)
await callback.answer("Разблокирован")
else:
await state.update_data(target_uid=uid)
await state.set_state(Form.admin_user_ban_reason)
await safe_edit(
callback, "Отправьте причину блокировки:", reply_markup=back_kb("admin_back")
)
return
card = _user_card(uid)
if card:
await safe_edit(callback, card[0], reply_markup=card[1])
@router.message(Form.admin_user_ban_reason)
async def u_ban_reason(message: Message, state: FSMContext):
if not _admin(message.from_user.id):
return
uid = (await state.get_data()).get("target_uid")
ban_user(uid, (message.text or "Причина не указана").strip())
await state.clear()
card = _user_card(uid)
if card:
await message.answer(card[0], parse_mode="HTML", reply_markup=card[1])
@router.callback_query(F.data.startswith("u_admin_"))
async def u_admin(callback: CallbackQuery):
if not _admin(callback.from_user.id):
return
uid = int(callback.data.removeprefix("u_admin_"))
enabled = toggle_user_admin(uid)
await callback.answer(f"Админ: {'ВКЛ' if enabled else 'ВЫКЛ'}")
card = _user_card(uid)
if card:
await safe_edit(callback, card[0], reply_markup=card[1])