diff --git a/app/handlers/admin/bot_configuration.py b/app/handlers/admin/bot_configuration.py index b2b5281e..76120f61 100644 --- a/app/handlers/admin/bot_configuration.py +++ b/app/handlers/admin/bot_configuration.py @@ -1,5 +1,5 @@ import math -from typing import Iterable, List, Tuple +from typing import Tuple from aiogram import Dispatcher, F, types from aiogram.fsm.context import FSMContext @@ -16,141 +16,22 @@ CATEGORY_PAGE_SIZE = 10 SETTINGS_PAGE_SIZE = 8 -CATEGORY_GROUP_DEFINITIONS: Tuple[Tuple[str, str, Tuple[str, ...]], ...] = ( - ( - "core", - "⚙️ Основные настройки", - ( - "DEFAULT", - "ADMIN", - "SUPPORT", - "REMNAWAVE", - "VERSION", - "DEBUG", - "LOG", - "BACKUP", - ), - ), - ( - "infrastructure", - "🗄️ Инфраструктура", - ("DATABASE", "POSTGRES", "SQLITE", "REDIS", "WEBHOOK", "SERVER", "MONITORING", "MAINTENANCE"), - ), - ( - "billing", - "💳 Оплаты и тарифы", - ("PRICE", "PAYMENT", "YOOKASSA", "CRYPTOBOT", "MULENPAY", "PAL24", "AUTOPAY", "TRIAL"), - ), - ( - "communication", - "📡 Подключение и коммуникации", - ("CONNECT", "CHANNEL", "TELEGRAM", "HAPP"), - ), - ( - "loyalty", - "🎁 Рефералы и лояльность", - ("REFERRAL", "TRIBUTE"), - ), -) - -CATEGORY_FALLBACK_KEY = "other" -CATEGORY_FALLBACK_TITLE = "📦 Прочие настройки" - - -def _chunk(buttons: Iterable[types.InlineKeyboardButton], size: int) -> Iterable[List[types.InlineKeyboardButton]]: - buttons_list = list(buttons) - for index in range(0, len(buttons_list), size): - yield buttons_list[index : index + size] - - -def _parse_category_payload(payload: str) -> Tuple[str, str, int, int]: +def _parse_category_payload(payload: str) -> Tuple[str, int]: parts = payload.split(":") - group_key = parts[1] if len(parts) > 1 else CATEGORY_FALLBACK_KEY - category_key = parts[2] if len(parts) > 2 else "" - - def _safe_int(value: str, default: int = 1) -> int: + if len(parts) == 3: + _, category_key, page_raw = parts try: - return max(1, int(value)) - except (TypeError, ValueError): - return default - - category_page = _safe_int(parts[3]) if len(parts) > 3 else 1 - settings_page = _safe_int(parts[4]) if len(parts) > 4 else 1 - return group_key, category_key, category_page, settings_page + return category_key, max(1, int(page_raw)) + except ValueError: + return category_key, 1 + if len(parts) == 2: + _, category_key = parts + return category_key, 1 + return "", 1 -def _parse_group_payload(payload: str) -> Tuple[str, int]: - parts = payload.split(":") - group_key = parts[1] if len(parts) > 1 else CATEGORY_FALLBACK_KEY - try: - page = max(1, int(parts[2])) - except (IndexError, ValueError): - page = 1 - return group_key, page - - -def _get_grouped_categories() -> List[Tuple[str, str, List[Tuple[str, str, int]]]]: +def _build_categories_keyboard(language: str, page: int = 1) -> types.InlineKeyboardMarkup: categories = bot_configuration_service.get_categories() - categories_map = {key: (label, count) for key, label, count in categories} - used: set[str] = set() - grouped: List[Tuple[str, str, List[Tuple[str, str, int]]]] = [] - - for group_key, title, category_keys in CATEGORY_GROUP_DEFINITIONS: - items: List[Tuple[str, str, int]] = [] - for category_key in category_keys: - if category_key in categories_map: - label, count = categories_map[category_key] - items.append((category_key, label, count)) - used.add(category_key) - if items: - grouped.append((group_key, title, items)) - - remaining = [ - (key, label, count) - for key, (label, count) in categories_map.items() - if key not in used - ] - - if remaining: - remaining.sort(key=lambda item: item[1]) - grouped.append((CATEGORY_FALLBACK_KEY, CATEGORY_FALLBACK_TITLE, remaining)) - - return grouped - - -def _build_groups_keyboard() -> types.InlineKeyboardMarkup: - grouped = _get_grouped_categories() - rows: list[list[types.InlineKeyboardButton]] = [] - - for group_key, title, items in grouped: - total = sum(count for _, _, count in items) - rows.append( - [ - types.InlineKeyboardButton( - text=f"{title} ({total})", - callback_data=f"botcfg_group:{group_key}:1", - ) - ] - ) - - rows.append( - [ - types.InlineKeyboardButton( - text="⬅️ Назад", - callback_data="admin_submenu_settings", - ) - ] - ) - - return types.InlineKeyboardMarkup(inline_keyboard=rows) - - -def _build_categories_keyboard( - group_key: str, - group_title: str, - categories: List[Tuple[str, str, int]], - page: int = 1, -) -> types.InlineKeyboardMarkup: total_pages = max(1, math.ceil(len(categories) / CATEGORY_PAGE_SIZE)) page = max(1, min(page, total_pages)) @@ -159,68 +40,45 @@ def _build_categories_keyboard( sliced = categories[start:end] rows: list[list[types.InlineKeyboardButton]] = [] - rows.append( - [ - types.InlineKeyboardButton( - text=f"— {group_title} —", - callback_data="botcfg_group:noop", - ) - ] - ) - - buttons: List[types.InlineKeyboardButton] = [] for category_key, label, count in sliced: button_text = f"{label} ({count})" - buttons.append( + rows.append([ types.InlineKeyboardButton( text=button_text, - callback_data=f"botcfg_cat:{group_key}:{category_key}:{page}:1", + callback_data=f"botcfg_cat:{category_key}:1", ) - ) - - for chunk in _chunk(buttons, 2): - rows.append(list(chunk)) + ]) if total_pages > 1: nav_row: list[types.InlineKeyboardButton] = [] if page > 1: nav_row.append( types.InlineKeyboardButton( - text="⬅️", - callback_data=f"botcfg_group:{group_key}:{page - 1}", + text="⬅️", callback_data=f"botcfg_categories:{page - 1}" ) ) nav_row.append( types.InlineKeyboardButton( - text=f"{page}/{total_pages}", - callback_data="botcfg_group:noop", + text=f"{page}/{total_pages}", callback_data="botcfg_categories:noop" ) ) if page < total_pages: nav_row.append( types.InlineKeyboardButton( - text="➡️", - callback_data=f"botcfg_group:{group_key}:{page + 1}", + text="➡️", callback_data=f"botcfg_categories:{page + 1}" ) ) rows.append(nav_row) - rows.append( - [ - types.InlineKeyboardButton( - text="⬅️ К разделам", - callback_data="admin_bot_config", - ) - ] - ) + rows.append([ + types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_submenu_settings") + ]) return types.InlineKeyboardMarkup(inline_keyboard=rows) def _build_settings_keyboard( category_key: str, - group_key: str, - category_page: int, language: str, page: int = 1, ) -> types.InlineKeyboardMarkup: @@ -236,19 +94,13 @@ def _build_settings_keyboard( for definition in sliced: value_preview = bot_configuration_service.format_value_for_list(definition.key) - button_text = f"{definition.display_name} · {value_preview}" - if len(button_text) > 64: - button_text = button_text[:63] + "…" - rows.append( - [ - types.InlineKeyboardButton( - text=button_text, - callback_data=( - f"botcfg_setting:{group_key}:{category_page}:{page}:{definition.key}" - ), - ) - ] - ) + button_text = f"{definition.key} = {value_preview}" + rows.append([ + types.InlineKeyboardButton( + text=button_text, + callback_data=f"botcfg_setting:{definition.key}", + ) + ]) if total_pages > 1: nav_row: list[types.InlineKeyboardButton] = [] @@ -256,9 +108,7 @@ def _build_settings_keyboard( nav_row.append( types.InlineKeyboardButton( text="⬅️", - callback_data=( - f"botcfg_cat:{group_key}:{category_key}:{category_page}:{page - 1}" - ), + callback_data=f"botcfg_cat:{category_key}:{page - 1}", ) ) nav_row.append( @@ -270,9 +120,7 @@ def _build_settings_keyboard( nav_row.append( types.InlineKeyboardButton( text="➡️", - callback_data=( - f"botcfg_cat:{group_key}:{category_key}:{category_page}:{page + 1}" - ), + callback_data=f"botcfg_cat:{category_key}:{page + 1}", ) ) rows.append(nav_row) @@ -280,19 +128,14 @@ def _build_settings_keyboard( rows.append([ types.InlineKeyboardButton( text="⬅️ К категориям", - callback_data=f"botcfg_group:{group_key}:{category_page}", + callback_data="admin_bot_config", ) ]) return types.InlineKeyboardMarkup(inline_keyboard=rows) -def _build_setting_keyboard( - key: str, - group_key: str, - category_page: int, - settings_page: int, -) -> types.InlineKeyboardMarkup: +def _build_setting_keyboard(key: str) -> types.InlineKeyboardMarkup: definition = bot_configuration_service.get_definition(key) rows: list[list[types.InlineKeyboardButton]] = [] @@ -300,18 +143,14 @@ def _build_setting_keyboard( rows.append([ types.InlineKeyboardButton( text="🔁 Переключить", - callback_data=( - f"botcfg_toggle:{group_key}:{category_page}:{settings_page}:{key}" - ), + callback_data=f"botcfg_toggle:{key}", ) ]) rows.append([ types.InlineKeyboardButton( text="✏️ Изменить", - callback_data=( - f"botcfg_edit:{group_key}:{category_page}:{settings_page}:{key}" - ), + callback_data=f"botcfg_edit:{key}", ) ]) @@ -319,18 +158,14 @@ def _build_setting_keyboard( rows.append([ types.InlineKeyboardButton( text="♻️ Сбросить", - callback_data=( - f"botcfg_reset:{group_key}:{category_page}:{settings_page}:{key}" - ), + callback_data=f"botcfg_reset:{key}", ) ]) rows.append([ types.InlineKeyboardButton( text="⬅️ Назад", - callback_data=( - f"botcfg_cat:{group_key}:{definition.category_key}:{category_page}:{settings_page}" - ), + callback_data=f"botcfg_cat:{definition.category_key}:1", ) ]) @@ -342,9 +177,7 @@ def _render_setting_text(key: str) -> str: lines = [ "🧩 Настройка", - f"Название: {summary['name']}", f"Ключ: {summary['key']}", - f"Категория: {summary['category_label']}", f"Тип: {summary['type']}", f"Текущее значение: {summary['current']}", f"Значение по умолчанию: {summary['original']}", @@ -361,9 +194,9 @@ async def show_bot_config_menu( db_user: User, db: AsyncSession, ): - keyboard = _build_groups_keyboard() + keyboard = _build_categories_keyboard(db_user.language) await callback.message.edit_text( - "🧩 Конфигурация бота\n\nВыберите раздел настроек:", + "🧩 Конфигурация бота\n\nВыберите категорию настроек:", reply_markup=keyboard, ) await callback.answer() @@ -371,23 +204,20 @@ async def show_bot_config_menu( @admin_required @error_handler -async def show_bot_config_group( +async def show_bot_config_categories_page( callback: types.CallbackQuery, db_user: User, db: AsyncSession, ): - group_key, page = _parse_group_payload(callback.data) - grouped = _get_grouped_categories() - group_lookup = {key: (title, items) for key, title, items in grouped} + parts = callback.data.split(":") + try: + page = int(parts[1]) + except (IndexError, ValueError): + page = 1 - if group_key not in group_lookup: - await callback.answer("Эта группа больше недоступна", show_alert=True) - return - - group_title, items = group_lookup[group_key] - keyboard = _build_categories_keyboard(group_key, group_title, items, page) + keyboard = _build_categories_keyboard(db_user.language, page) await callback.message.edit_text( - f"🧩 {group_title}\n\nВыберите категорию настроек:", + "🧩 Конфигурация бота\n\nВыберите категорию настроек:", reply_markup=keyboard, ) await callback.answer() @@ -400,9 +230,7 @@ async def show_bot_config_category( db_user: User, db: AsyncSession, ): - group_key, category_key, category_page, settings_page = _parse_category_payload( - callback.data - ) + category_key, page = _parse_category_payload(callback.data) definitions = bot_configuration_service.get_settings_for_category(category_key) if not definitions: @@ -410,13 +238,7 @@ async def show_bot_config_category( return category_label = definitions[0].category_label - keyboard = _build_settings_keyboard( - category_key, - group_key, - category_page, - db_user.language, - settings_page, - ) + keyboard = _build_settings_keyboard(category_key, db_user.language, page) await callback.message.edit_text( f"🧩 {category_label}\n\nВыберите настройку для просмотра:", reply_markup=keyboard, @@ -431,19 +253,9 @@ async def show_bot_config_setting( db_user: User, db: AsyncSession, ): - parts = callback.data.split(":", 4) - group_key = parts[1] if len(parts) > 1 else CATEGORY_FALLBACK_KEY - try: - category_page = max(1, int(parts[2])) if len(parts) > 2 else 1 - except ValueError: - category_page = 1 - try: - settings_page = max(1, int(parts[3])) if len(parts) > 3 else 1 - except ValueError: - settings_page = 1 - key = parts[4] if len(parts) > 4 else "" + key = callback.data.split(":", 1)[1] text = _render_setting_text(key) - keyboard = _build_setting_keyboard(key, group_key, category_page, settings_page) + keyboard = _build_setting_keyboard(key) await callback.message.edit_text(text, reply_markup=keyboard) await callback.answer() @@ -456,17 +268,7 @@ async def start_edit_setting( db: AsyncSession, state: FSMContext, ): - parts = callback.data.split(":", 4) - group_key = parts[1] if len(parts) > 1 else CATEGORY_FALLBACK_KEY - try: - category_page = max(1, int(parts[2])) if len(parts) > 2 else 1 - except ValueError: - category_page = 1 - try: - settings_page = max(1, int(parts[3])) if len(parts) > 3 else 1 - except ValueError: - settings_page = 1 - key = parts[4] if len(parts) > 4 else "" + key = callback.data.split(":", 1)[1] definition = bot_configuration_service.get_definition(key) summary = bot_configuration_service.get_setting_summary(key) @@ -474,7 +276,6 @@ async def start_edit_setting( instructions = [ "✏️ Редактирование настройки", - f"Название: {summary['name']}", f"Ключ: {summary['key']}", f"Тип: {summary['type']}", f"Текущее значение: {summary['current']}", @@ -492,22 +293,14 @@ async def start_edit_setting( inline_keyboard=[ [ types.InlineKeyboardButton( - text=texts.BACK, - callback_data=( - f"botcfg_setting:{group_key}:{category_page}:{settings_page}:{key}" - ), + text=texts.BACK, callback_data=f"botcfg_setting:{key}" ) ] ] ), ) - await state.update_data( - setting_key=key, - setting_group_key=group_key, - setting_category_page=category_page, - setting_settings_page=settings_page, - ) + await state.update_data(setting_key=key) await state.set_state(BotConfigStates.waiting_for_value) await callback.answer() @@ -522,9 +315,6 @@ async def handle_edit_setting( ): data = await state.get_data() key = data.get("setting_key") - group_key = data.get("setting_group_key", CATEGORY_FALLBACK_KEY) - category_page = data.get("setting_category_page", 1) - settings_page = data.get("setting_settings_page", 1) if not key: await message.answer("Не удалось определить редактируемую настройку. Попробуйте снова.") @@ -541,7 +331,7 @@ async def handle_edit_setting( await db.commit() text = _render_setting_text(key) - keyboard = _build_setting_keyboard(key, group_key, category_page, settings_page) + keyboard = _build_setting_keyboard(key) await message.answer("✅ Настройка обновлена") await message.answer(text, reply_markup=keyboard) await state.clear() @@ -554,22 +344,12 @@ async def reset_setting( db_user: User, db: AsyncSession, ): - parts = callback.data.split(":", 4) - group_key = parts[1] if len(parts) > 1 else CATEGORY_FALLBACK_KEY - try: - category_page = max(1, int(parts[2])) if len(parts) > 2 else 1 - except ValueError: - category_page = 1 - try: - settings_page = max(1, int(parts[3])) if len(parts) > 3 else 1 - except ValueError: - settings_page = 1 - key = parts[4] if len(parts) > 4 else "" + key = callback.data.split(":", 1)[1] await bot_configuration_service.reset_value(db, key) await db.commit() text = _render_setting_text(key) - keyboard = _build_setting_keyboard(key, group_key, category_page, settings_page) + keyboard = _build_setting_keyboard(key) await callback.message.edit_text(text, reply_markup=keyboard) await callback.answer("Сброшено к значению по умолчанию") @@ -581,24 +361,14 @@ async def toggle_setting( db_user: User, db: AsyncSession, ): - parts = callback.data.split(":", 4) - group_key = parts[1] if len(parts) > 1 else CATEGORY_FALLBACK_KEY - try: - category_page = max(1, int(parts[2])) if len(parts) > 2 else 1 - except ValueError: - category_page = 1 - try: - settings_page = max(1, int(parts[3])) if len(parts) > 3 else 1 - except ValueError: - settings_page = 1 - key = parts[4] if len(parts) > 4 else "" + key = callback.data.split(":", 1)[1] current = bot_configuration_service.get_current_value(key) new_value = not bool(current) await bot_configuration_service.set_value(db, key, new_value) await db.commit() text = _render_setting_text(key) - keyboard = _build_setting_keyboard(key, group_key, category_page, settings_page) + keyboard = _build_setting_keyboard(key) await callback.message.edit_text(text, reply_markup=keyboard) await callback.answer("Обновлено") @@ -609,8 +379,9 @@ def register_handlers(dp: Dispatcher) -> None: F.data == "admin_bot_config", ) dp.callback_query.register( - show_bot_config_group, - F.data.startswith("botcfg_group:") & (~F.data.endswith(":noop")), + show_bot_config_categories_page, + F.data.startswith("botcfg_categories:") + & (~F.data.endswith(":noop")), ) dp.callback_query.register( show_bot_config_category,