import logging import re from aiogram import Router, F, types, Bot from aiogram.fsm.context import FSMContext from typing import Optional, Tuple, List, Any import math import aiosqlite from datetime import datetime from config.settings import Settings from db.database import (get_user, set_user_ban_status_db, get_user_by_telegram_username, get_banned_users_list_paginated, get_user_active_subscription_end_date) from bot.services.panel_api_service import PanelApiService from bot.states.admin_states import AdminStates from bot.keyboards.inline.admin_keyboards import ( get_back_to_admin_panel_keyboard, get_user_card_keyboard, get_banned_users_keyboard, get_confirmation_keyboard, get_admin_panel_keyboard) from bot.middlewares.i18n import JsonI18n router = Router(name="admin_user_management_router") USERNAME_REGEX = re.compile(r"^[a-zA-Z0-9_]{5,32}$") async def ban_user_prompt_handler(callback: types.CallbackQuery, state: FSMContext, i18n_data: dict, settings: Settings): current_lang = i18n_data.get("current_language", getattr(settings, 'DEFAULT_LANGUAGE', 'en')) i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") if not i18n: logging.error("i18n missing") await callback.answer("Language error.", show_alert=True) return _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) prompt_text = _("admin_ban_user_prompt") if callback.message: try: await callback.message.edit_text( prompt_text, reply_markup=get_back_to_admin_panel_keyboard( current_lang, i18n)) except Exception as e: logging.warning(f"Edit failed: {e}") await callback.message.answer( prompt_text, reply_markup=get_back_to_admin_panel_keyboard( current_lang, i18n)) await callback.answer() await state.set_state(AdminStates.waiting_for_user_id_to_ban) @router.message(AdminStates.waiting_for_user_id_to_ban, F.text) async def process_user_input_to_ban_handler(message: types.Message, state: FSMContext, i18n_data: dict, settings: Settings, panel_service: PanelApiService): current_lang = i18n_data.get("current_language", getattr(settings, 'DEFAULT_LANGUAGE', 'en')) i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") if not i18n: logging.error("i18n missing") await message.reply("Language error.") return _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) input_text = message.text.strip() user_to_ban_data: Optional[aiosqlite.Row] = None user_id_or_username_for_msg = input_text if input_text.isdigit(): try: user_id_to_ban_val = int(input_text) user_to_ban_data = await get_user(user_id_to_ban_val) except ValueError: await message.answer(_("admin_invalid_user_id_format")) return elif input_text.startswith("@") and USERNAME_REGEX.match(input_text[1:]): user_to_ban_data = await get_user_by_telegram_username(input_text[1:]) elif USERNAME_REGEX.match(input_text): user_to_ban_data = await get_user_by_telegram_username(input_text) else: await message.answer( _("admin_invalid_user_id_format") + " " + _("admin_invalid_username_format")) return if not user_to_ban_data: await message.answer( _("admin_user_not_found_in_bot_db", user_id=input_text)) await state.clear() return user_id_to_ban = user_to_ban_data['user_id'] user_id_or_username_for_msg = f"@{user_to_ban_data['username']}" if user_to_ban_data.get( 'username') else str(user_id_to_ban) if user_id_to_ban == message.from_user.id or user_id_to_ban in settings.ADMIN_IDS: await message.answer(_("admin_cannot_ban_self_or_admin")) await state.clear() return if user_to_ban_data['is_banned']: await message.answer( _("admin_user_already_banned", user_id_or_username=user_id_or_username_for_msg)) await state.clear() return panel_user_uuid = user_to_ban_data[ 'panel_user_uuid'] if user_to_ban_data and 'panel_user_uuid' in user_to_ban_data.keys( ) and user_to_ban_data['panel_user_uuid'] else None await set_user_ban_status_db(user_id_to_ban, is_banned=True) reply_markup_val = get_back_to_admin_panel_keyboard(current_lang, i18n) if panel_user_uuid: panel_ban_success = await panel_service.update_user_status_on_panel( panel_user_uuid, enable=False) if panel_ban_success: await message.answer(_( "admin_user_banned_success_panel_too", user_id_or_username=user_id_or_username_for_msg), reply_markup=reply_markup_val) else: await message.answer(_( "admin_user_banned_local_panel_fail", user_id_or_username=user_id_or_username_for_msg), reply_markup=reply_markup_val) else: await message.answer(_( "admin_user_banned_local_no_panel_uuid", user_id_or_username=user_id_or_username_for_msg), reply_markup=reply_markup_val) await state.clear() async def unban_user_prompt_handler(callback: types.CallbackQuery, state: FSMContext, i18n_data: dict, settings: Settings): current_lang = i18n_data.get("current_language", getattr(settings, 'DEFAULT_LANGUAGE', 'en')) i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") if not i18n: logging.error("i18n missing") await callback.answer("Language error.", show_alert=True) return _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) prompt_text = _("admin_unban_user_prompt") if callback.message: try: await callback.message.edit_text( prompt_text, reply_markup=get_back_to_admin_panel_keyboard( current_lang, i18n)) except: await callback.message.answer( prompt_text, reply_markup=get_back_to_admin_panel_keyboard( current_lang, i18n)) await callback.answer() await state.set_state(AdminStates.waiting_for_user_id_to_unban) @router.message(AdminStates.waiting_for_user_id_to_unban, F.text) async def process_user_input_to_unban_handler(message: types.Message, state: FSMContext, i18n_data: dict, settings: Settings, panel_service: PanelApiService): current_lang = i18n_data.get("current_language", getattr(settings, 'DEFAULT_LANGUAGE', 'en')) i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") if not i18n: logging.error("i18n missing") await message.reply("Language error.") return _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) input_text = message.text.strip() user_to_unban_data: Optional[aiosqlite.Row] = None user_id_or_username_for_msg = input_text if input_text.isdigit(): try: user_id_to_unban_val = int(input_text) user_to_unban_data = await get_user(user_id_to_unban_val) except ValueError: await message.answer(_("admin_invalid_user_id_format")) return elif input_text.startswith("@") and USERNAME_REGEX.match(input_text[1:]): user_to_unban_data = await get_user_by_telegram_username(input_text[1:] ) elif USERNAME_REGEX.match(input_text): user_to_unban_data = await get_user_by_telegram_username(input_text) else: await message.answer( _("admin_invalid_user_id_format") + " " + _("admin_invalid_username_format")) return if not user_to_unban_data: await message.answer( _("admin_user_not_found_in_bot_db", user_id=input_text)) await state.clear() return user_id_to_unban = user_to_unban_data['user_id'] user_id_or_username_for_msg = f"@{user_to_unban_data['username']}" if user_to_unban_data.get( 'username') else str(user_id_to_unban) if not user_to_unban_data['is_banned']: await message.answer( _("admin_user_not_banned", user_id_or_username=user_id_or_username_for_msg)) await state.clear() return panel_user_uuid = user_to_unban_data[ 'panel_user_uuid'] if user_to_unban_data and 'panel_user_uuid' in user_to_unban_data.keys( ) and user_to_unban_data['panel_user_uuid'] else None await set_user_ban_status_db(user_id_to_unban, is_banned=False) reply_markup_val = get_back_to_admin_panel_keyboard(current_lang, i18n) if panel_user_uuid: panel_unban_success = await panel_service.update_user_status_on_panel( panel_user_uuid, enable=True) if panel_unban_success: await message.answer(_( "admin_user_unbanned_success_panel_too", user_id_or_username=user_id_or_username_for_msg), reply_markup=reply_markup_val) else: await message.answer(_( "admin_user_unbanned_local_panel_fail", user_id_or_username=user_id_or_username_for_msg), reply_markup=reply_markup_val) else: await message.answer(_( "admin_user_unbanned_local_no_panel_uuid", user_id_or_username=user_id_or_username_for_msg), reply_markup=reply_markup_val) await state.clear() async def view_banned_users_handler(callback: types.CallbackQuery, i18n_data: dict, settings: Settings, state: FSMContext): await state.clear() current_page = 0 if ":" in callback.data and callback.data.count(":") == 2: try: current_page = int(callback.data.split(":")[-1]) except ValueError: current_page = 0 current_lang = i18n_data.get("current_language", getattr(settings, 'DEFAULT_LANGUAGE', 'en')) i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") if not i18n: await callback.answer("Language error.", show_alert=True) return _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) banned_users, total_banned = await get_banned_users_list_paginated( limit=settings.LOGS_PAGE_SIZE, offset=current_page * settings.LOGS_PAGE_SIZE) if not callback.message: await callback.answer("Error.") return if total_banned == 0: await callback.message.edit_text( _("admin_no_banned_users"), reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n)) else: total_pages = math.ceil( total_banned / settings.LOGS_PAGE_SIZE) if settings.LOGS_PAGE_SIZE > 0 else 1 await callback.message.edit_text( text=_("admin_banned_list_title", current_page=current_page + 1, total_pages=max(1, total_pages)), reply_markup=get_banned_users_keyboard(banned_users, current_page, total_banned, i18n, current_lang, settings)) await callback.answer() @router.callback_query(F.data.startswith("admin_user_card:")) async def show_user_card_handler(callback: types.CallbackQuery, i18n_data: dict, settings: Settings, panel_service: PanelApiService, state: FSMContext, force_user_id: Optional[int] = None, force_page: Optional[int] = None): await state.clear() user_id_to_show = 0 banned_list_page_to_return = 0 if force_user_id is not None and force_page is not None: user_id_to_show = force_user_id banned_list_page_to_return = force_page else: try: parts = callback.data.split(":") user_id_to_show = int(parts[1]) banned_list_page_to_return = int(parts[2]) if len(parts) > 2 else 0 except (IndexError, ValueError): await callback.answer("Invalid user card data.", show_alert=True) return current_lang = i18n_data.get("current_language", getattr(settings, 'DEFAULT_LANGUAGE', 'en')) i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") if not i18n: await callback.answer("Language error.", show_alert=True) return _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) user_data = await get_user(user_id_to_show) if not callback.message: await callback.answer("Error.") return if not user_data: await callback.message.edit_text( _("admin_user_not_found_in_bot_db", user_id=user_id_to_show), reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n)) await callback.answer() return user_display_name = user_data['first_name'] or ( f"@{user_data['username']}" if user_data.get('username') else f"ID: {user_id_to_show}") sub_end_date_str = await get_user_active_subscription_end_date( user_id_to_show) or _("user_card_sub_na") reg_date_from_db = user_data[ 'registration_date_str'] if 'registration_date_str' in user_data.keys( ) else "N/A" reg_date_display = reg_date_from_db[: 10] if reg_date_from_db and reg_date_from_db != "N/A" else "N/A" card_text = _( "user_card_info", user_id=user_data['user_id'], username=user_data.get('username', "N/A"), first_name=user_data.get('first_name', ""), last_name=user_data.get('last_name', ""), language_code=user_data.get('language_code', "N/A"), panel_user_uuid=user_data.get('panel_user_uuid', "N/A"), ban_status=_(key="user_card_banned") if user_data['is_banned'] else _( key="user_card_active"), reg_date=reg_date_display, sub_end_date=sub_end_date_str) await callback.message.edit_text( text= f"{_('admin_user_card_title', user_display=user_display_name)}\n\n{card_text}", reply_markup=get_user_card_keyboard(user_id_to_show, bool(user_data['is_banned']), i18n, current_lang, banned_list_page_to_return), parse_mode="HTML") if force_user_id is None: await callback.answer() @router.callback_query(F.data.startswith("admin_unban_confirm:")) async def confirm_unban_handler(callback: types.CallbackQuery, i18n_data: dict, settings: Settings): try: _, user_id_str, page_str = callback.data.split(":") user_id = int(user_id_str) banned_list_page = int(page_str) except: await callback.answer("Invalid data", show_alert=True) return current_lang = i18n_data.get("current_language", getattr(settings, 'DEFAULT_LANGUAGE', 'en')) i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") if not i18n: await callback.answer("Language error.", show_alert=True) return _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) user_data = await get_user(user_id) user_display = (user_data['first_name'] or (f"@{user_data['username']}" if user_data.get('username') else f"ID {user_id}")) if user_data else f"ID {user_id}" if callback.message: await callback.message.edit_text( text=_("admin_confirm_unban_prompt", user_display=user_display, user_id=user_id), reply_markup=get_confirmation_keyboard( yes_callback_data= f"admin_unban_do:{user_id}:{banned_list_page}", no_callback_data= f"admin_user_card:{user_id}:{banned_list_page}", i18n_instance=i18n, lang=current_lang), parse_mode="HTML") await callback.answer() @router.callback_query(F.data.startswith("admin_ban_confirm:")) async def confirm_ban_handler(callback: types.CallbackQuery, i18n_data: dict, settings: Settings): try: _, user_id_str, page_str = callback.data.split(":") user_id = int(user_id_str) banned_list_page = int(page_str) except: await callback.answer("Invalid data", show_alert=True) return current_lang = i18n_data.get("current_language", getattr(settings, 'DEFAULT_LANGUAGE', 'en')) i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") if not i18n: await callback.answer("Language error.", show_alert=True) return _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) user_data = await get_user(user_id) user_display = (user_data['first_name'] or (f"@{user_data['username']}" if user_data.get('username') else f"ID {user_id}")) if user_data else f"ID {user_id}" if callback.message: await callback.message.edit_text( text=_("admin_confirm_ban_prompt", user_display=user_display, user_id=user_id), reply_markup=get_confirmation_keyboard( yes_callback_data=f"admin_ban_do:{user_id}:{banned_list_page}", no_callback_data= f"admin_user_card:{user_id}:{banned_list_page}", i18n_instance=i18n, lang=current_lang), parse_mode="HTML") await callback.answer() @router.callback_query(F.data.startswith("admin_unban_do:")) async def do_unban_user_handler(callback: types.CallbackQuery, i18n_data: dict, settings: Settings, panel_service: PanelApiService, state: FSMContext): try: _, user_id_str, page_str = callback.data.split(":") user_id_to_unban = int(user_id_str) banned_list_page = int(page_str) except: await callback.answer("Invalid data", show_alert=True) return current_lang = i18n_data.get("current_language", getattr(settings, 'DEFAULT_LANGUAGE', 'en')) i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") if not i18n: await callback.answer("Language error.", show_alert=True) return _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) user_to_unban_data = await get_user(user_id_to_unban) user_display_name = ( user_to_unban_data['first_name'] or (f"@{user_to_unban_data['username']}" if user_to_unban_data.get('username') else f"ID {user_id_to_unban}") ) if user_to_unban_data else f"ID {user_id_to_unban}" if not user_to_unban_data or not user_to_unban_data['is_banned']: await callback.answer(_("admin_user_not_banned", user_id_or_username=user_display_name), show_alert=True) if callback.message: await show_user_card_handler(callback, i18n_data, settings, panel_service, state, force_user_id=user_id_to_unban, force_page=banned_list_page) return await set_user_ban_status_db(user_id_to_unban, is_banned=False) panel_user_uuid = user_to_unban_data[ 'panel_user_uuid'] if user_to_unban_data and 'panel_user_uuid' in user_to_unban_data.keys( ) and user_to_unban_data['panel_user_uuid'] else None if panel_user_uuid: if not await panel_service.update_user_status_on_panel(panel_user_uuid, enable=True): logging.warning( f"Panel status update fail for unban {user_id_to_unban}") await callback.answer(_("admin_user_unbanned_from_card", user_display=user_display_name, user_id=user_id_to_unban), show_alert=False) if callback.message: await show_user_card_handler(callback, i18n_data, settings, panel_service, state, force_user_id=user_id_to_unban, force_page=banned_list_page) @router.callback_query(F.data.startswith("admin_ban_do:")) async def do_ban_user_handler(callback: types.CallbackQuery, i18n_data: dict, settings: Settings, panel_service: PanelApiService, state: FSMContext): try: _, user_id_str, page_str = callback.data.split(":") user_id_to_ban = int(user_id_str) banned_list_page = int(page_str) except: await callback.answer("Invalid data", show_alert=True) return current_lang = i18n_data.get("current_language", getattr(settings, 'DEFAULT_LANGUAGE', 'en')) i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") if not i18n: await callback.answer("Language error.", show_alert=True) return _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) user_to_ban_data = await get_user(user_id_to_ban) user_display_name = ( user_to_ban_data['first_name'] or (f"@{user_to_ban_data['username']}" if user_to_ban_data.get('username') else f"ID {user_id_to_ban}") ) if user_to_ban_data else f"ID {user_id_to_ban}" if not user_to_ban_data: await callback.answer(_("admin_user_not_found_in_bot_db", user_id=user_id_to_ban), show_alert=True) return if user_to_ban_data['is_banned']: await callback.answer(_("admin_user_already_banned", user_id_or_username=user_display_name), show_alert=True) return if user_id_to_ban == callback.from_user.id or user_id_to_ban in settings.ADMIN_IDS: await callback.answer(_("admin_cannot_ban_self_or_admin"), show_alert=True) return await set_user_ban_status_db(user_id_to_ban, is_banned=True) panel_user_uuid = user_to_ban_data[ 'panel_user_uuid'] if user_to_ban_data and 'panel_user_uuid' in user_to_ban_data.keys( ) and user_to_ban_data['panel_user_uuid'] else None if panel_user_uuid: if not await panel_service.update_user_status_on_panel(panel_user_uuid, enable=False): logging.warning( f"Panel status update fail for ban {user_id_to_ban}") await callback.answer(_("admin_user_banned_from_card", user_display=user_display_name, user_id=user_id_to_ban), show_alert=False) if callback.message: await show_user_card_handler(callback, i18n_data, settings, panel_service, state, force_user_id=user_id_to_ban, force_page=banned_list_page) @router.callback_query(F.data == "admin_action:main", AdminStates.waiting_for_user_id_to_ban) @router.callback_query(F.data == "admin_action:main", AdminStates.waiting_for_user_id_to_unban) async def cancel_user_management_input_state(callback: types.CallbackQuery, state: FSMContext, settings: Settings, i18n_data: dict, bot: Bot): current_lang = i18n_data.get("current_language", getattr(settings, 'DEFAULT_LANGUAGE', 'en')) i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") if not i18n: await callback.answer("Language error.", show_alert=True) return _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) if callback.message: try: await callback.message.edit_text( _("admin_action_cancelled_default"), reply_markup=get_admin_panel_keyboard(i18n, current_lang, settings)) except: await callback.message.answer( _("admin_action_cancelled_default"), reply_markup=get_admin_panel_keyboard(i18n, current_lang, settings)) await callback.answer(_("admin_action_cancelled_default")) await state.clear()