from datetime import datetime, timedelta, timezone from aiogram import F, Router from aiogram.fsm.context import FSMContext from aiogram.types import CallbackQuery, Message from aiogram.utils.keyboard import InlineKeyboardBuilder from sqlalchemy import delete from sqlalchemy.dialects.postgresql import insert as pg_insert from sqlalchemy.ext.asyncio import AsyncSession from database.access.resolution import resolve_user_optional from database.models import ManualBan from filters.admin import IsAdminFilter from handlers.buttons import BACK from middlewares.ban_checker import invalidate_ban_cache from .keyboard import AdminUserEditorCallback, build_editor_btn, build_editor_kb, build_user_ban_type_kb from .users_states import BanUserStates router = Router() @router.callback_query( AdminUserEditorCallback.filter(F.action == "users_ban"), IsAdminFilter(), ) async def handle_user_ban(callback: CallbackQuery, callback_data: AdminUserEditorCallback, state: FSMContext): await state.clear() await state.update_data(tg_id=callback_data.tg_id) await callback.message.edit_text( text="🚫 Выберите тип блокировки пользователя:", reply_markup=build_user_ban_type_kb(callback_data.tg_id), ) @router.callback_query( AdminUserEditorCallback.filter(F.action == "users_ban_forever"), IsAdminFilter(), ) async def handle_ban_forever_start(callback: CallbackQuery, callback_data: AdminUserEditorCallback, state: FSMContext): await state.set_state(BanUserStates.waiting_for_forever_reason) await state.update_data(tg_id=callback_data.tg_id) kb = InlineKeyboardBuilder() kb.row(build_editor_btn(BACK, tg_id=callback_data.tg_id, edit=True)) await callback.message.edit_text( text="✏️ Введите причину постоянной блокировки (или -, чтобы пропустить):", reply_markup=kb.as_markup(), ) @router.message(BanUserStates.waiting_for_forever_reason, IsAdminFilter()) async def handle_ban_forever_reason_input(message: Message, state: FSMContext, session: AsyncSession): reason = message.text.strip() if reason == "-": reason = None user_data = await state.get_data() tg_id = user_data.get("tg_id") u = await resolve_user_optional(session, tg_id) if u is None: await message.answer("❌ Пользователь не найден.") await state.clear() return stmt = ( pg_insert(ManualBan) .values( user_id=u.id, tg_id=u.tg_id, reason=reason, banned_by=message.from_user.id, until=None, banned_at=datetime.now(timezone.utc), ) .on_conflict_do_update( index_elements=[ManualBan.user_id], set_={ "tg_id": u.tg_id, "reason": reason, "until": None, "banned_by": message.from_user.id, "banned_at": datetime.now(timezone.utc), }, ) ) await session.execute(stmt) if u.tg_id is not None: await invalidate_ban_cache(u.tg_id) await state.clear() await message.answer( text=(f"✅ Пользователь {tg_id} забанен навсегда.{f'\n📄 Причина: {reason}' if reason else ''}"), reply_markup=build_editor_kb(tg_id, edit=True), ) @router.callback_query( AdminUserEditorCallback.filter(F.action == "users_ban_temporary"), IsAdminFilter(), ) async def handle_ban_temporary(callback: CallbackQuery, callback_data: AdminUserEditorCallback, state: FSMContext): await state.set_state(BanUserStates.waiting_for_reason) await state.update_data(tg_id=callback_data.tg_id) kb = InlineKeyboardBuilder() kb.row(build_editor_btn(BACK, tg_id=callback_data.tg_id, edit=True)) await callback.message.edit_text( text="✏️ Введите причину временной блокировки (или -, чтобы пропустить):", reply_markup=kb.as_markup(), ) @router.message(BanUserStates.waiting_for_reason, IsAdminFilter()) async def handle_ban_reason_input(message: Message, state: FSMContext): await state.update_data(reason=message.text.strip()) await state.set_state(BanUserStates.waiting_for_ban_duration) user_data = await state.get_data() tg_id = user_data.get("tg_id") kb = InlineKeyboardBuilder() kb.row(build_editor_btn(BACK, tg_id=tg_id, edit=True)) await message.answer( "⏳ Введите срок блокировки в днях (0 — навсегда):", reply_markup=kb.as_markup(), ) @router.message(BanUserStates.waiting_for_ban_duration, IsAdminFilter()) async def handle_ban_duration_input(message: Message, state: FSMContext, session: AsyncSession): user_data = await state.get_data() tg_id = user_data.get("tg_id") reason = user_data.get("reason") if reason == "-": reason = None try: days = int(message.text.strip()) if days < 1: await message.answer("❗ Укажите срок минимум в 1 день.") return until = datetime.now(timezone.utc) + timedelta(days=days) u = await resolve_user_optional(session, tg_id) if u is None: await message.answer("❌ Пользователь не найден.") return stmt = ( pg_insert(ManualBan) .values( user_id=u.id, tg_id=u.tg_id, reason=reason, banned_by=message.from_user.id, until=until, banned_at=datetime.now(timezone.utc), ) .on_conflict_do_update( index_elements=[ManualBan.user_id], set_={ "tg_id": u.tg_id, "reason": reason, "until": until, "banned_at": datetime.now(timezone.utc), "banned_by": message.from_user.id, }, ) ) await session.execute(stmt) if u.tg_id is not None: await invalidate_ban_cache(u.tg_id) text = ( f"✅ Пользователь {tg_id} временно забанен до {until:%Y-%m-%d %H:%M} по UTC." f"{f'\n📄 Причина: {reason}' if reason else ''}" ) await message.answer(text=text, reply_markup=build_editor_kb(tg_id, edit=True)) except ValueError: await message.answer("❗ Введите корректное число дней.") finally: await state.clear() @router.callback_query( AdminUserEditorCallback.filter(F.action == "users_ban_shadow"), IsAdminFilter(), ) async def handle_ban_shadow(callback: CallbackQuery, callback_data: AdminUserEditorCallback, session: AsyncSession): u = await resolve_user_optional(session, callback_data.tg_id) if u is None: await callback.answer("Пользователь не найден", show_alert=True) return stmt = ( pg_insert(ManualBan) .values( user_id=u.id, tg_id=u.tg_id, reason="shadow", banned_by=callback.from_user.id, until=None, banned_at=datetime.now(timezone.utc), ) .on_conflict_do_update( index_elements=[ManualBan.user_id], set_={ "tg_id": u.tg_id, "reason": "shadow", "until": None, "banned_by": callback.from_user.id, "banned_at": datetime.now(timezone.utc), }, ) ) await session.execute(stmt) if u.tg_id is not None: await invalidate_ban_cache(u.tg_id) await callback.message.edit_text( text=f"👻 Пользователь {callback_data.tg_id} получил теневой бан.", reply_markup=build_editor_kb(callback_data.tg_id, edit=True), ) @router.callback_query( AdminUserEditorCallback.filter(F.action == "users_unban"), IsAdminFilter(), ) async def handle_user_unban( callback: CallbackQuery, callback_data: AdminUserEditorCallback, session: AsyncSession, ): u = await resolve_user_optional(session, callback_data.tg_id) if u is None: await callback.answer("Пользователь не найден", show_alert=True) return await session.execute(delete(ManualBan).where(ManualBan.user_id == u.id)) if u.tg_id is not None: await invalidate_ban_cache(u.tg_id) text = ( f"✅ Пользователь {callback_data.tg_id} разблокирован. Нажмите кнопку ниже для возврата в профиль." ) await callback.message.edit_text(text=text, reply_markup=build_editor_kb(callback_data.tg_id, edit=True))