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 = ( "👤 Пользователь\n\n" f"🆔 ID: {uid}\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( "🛠 Админ-панель", 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, "🛠 Админ-панель", 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"📥 robsec.txt\nСтрок: {lines}\nРазмер: {size} байт", ) 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"🗑 Очистить robsec.txt?\n\nСтрок сейчас: {lines}\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, "✅ robsec.txt очищен", 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, "💰 Управление курсами\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"💰 Курс: {rate.name}\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"✅ Цена {cfg.rates[key].name} = {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💰 Сумма: {amount:.2f} ₽ (~{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])