From 7c38e672e3b4d2ea8ad8dffb557db83bc3301cf4 Mon Sep 17 00:00:00 2001 From: Egor Date: Thu, 25 Sep 2025 21:07:21 +0300 Subject: [PATCH] Revert "Fix bot config callbacks and add selectable options" --- app/handlers/admin/bot_configuration.py | 215 ++---------------------- app/services/system_settings_service.py | 125 +------------- 2 files changed, 13 insertions(+), 327 deletions(-) diff --git a/app/handlers/admin/bot_configuration.py b/app/handlers/admin/bot_configuration.py index 60e0f0ed..7fed1169 100644 --- a/app/handlers/admin/bot_configuration.py +++ b/app/handlers/admin/bot_configuration.py @@ -1,6 +1,6 @@ import math import time -from typing import Iterable, List, Optional, Tuple +from typing import Iterable, List, Tuple from aiogram import Dispatcher, F, types from aiogram.filters import BaseFilter, StateFilter @@ -142,17 +142,6 @@ def _parse_group_payload(payload: str) -> Tuple[str, int]: return group_key, page -async def _resolve_key_from_token( - callback: types.CallbackQuery, token: str -) -> Optional[str]: - key = bot_configuration_service.resolve_key_from_token(token) - if key: - return key - - await callback.answer("Эта настройка больше недоступна", show_alert=True) - return None - - def _get_grouped_categories() -> List[Tuple[str, str, List[Tuple[str, str, int]]]]: categories = bot_configuration_service.get_categories() categories_map = {key: (label, count) for key, label, count in categories} @@ -303,13 +292,12 @@ def _build_settings_keyboard( button_text = f"{definition.display_name} · {value_preview}" if len(button_text) > 64: button_text = button_text[:63] + "…" - token = bot_configuration_service.get_callback_token(definition.key) rows.append( [ types.InlineKeyboardButton( text=button_text, callback_data=( - f"botcfg_setting:{group_key}:{category_page}:{page}:{token}" + f"botcfg_setting:{group_key}:{category_page}:{page}:{definition.key}" ), ) ] @@ -360,24 +348,13 @@ def _build_setting_keyboard( ) -> types.InlineKeyboardMarkup: definition = bot_configuration_service.get_definition(key) rows: list[list[types.InlineKeyboardButton]] = [] - token = bot_configuration_service.get_callback_token(key) if definition.python_type is bool: rows.append([ types.InlineKeyboardButton( text="🔁 Переключить", callback_data=( - f"botcfg_toggle:{group_key}:{category_page}:{settings_page}:{token}" - ), - ) - ]) - - if definition.has_choices: - rows.append([ - types.InlineKeyboardButton( - text="📋 Выбрать", - callback_data=( - f"botcfg_choices:{group_key}:{category_page}:{settings_page}:{token}" + f"botcfg_toggle:{group_key}:{category_page}:{settings_page}:{key}" ), ) ]) @@ -386,7 +363,7 @@ def _build_setting_keyboard( types.InlineKeyboardButton( text="✏️ Изменить", callback_data=( - f"botcfg_edit:{group_key}:{category_page}:{settings_page}:{token}" + f"botcfg_edit:{group_key}:{category_page}:{settings_page}:{key}" ), ) ]) @@ -396,7 +373,7 @@ def _build_setting_keyboard( types.InlineKeyboardButton( text="♻️ Сбросить", callback_data=( - f"botcfg_reset:{group_key}:{category_page}:{settings_page}:{token}" + f"botcfg_reset:{group_key}:{category_page}:{settings_page}:{key}" ), ) ]) @@ -413,49 +390,6 @@ def _build_setting_keyboard( return types.InlineKeyboardMarkup(inline_keyboard=rows) -def _build_choices_keyboard( - key: str, - group_key: str, - category_page: int, - settings_page: int, -) -> types.InlineKeyboardMarkup: - token = bot_configuration_service.get_callback_token(key) - choices = bot_configuration_service.get_choices(key) - current_value = bot_configuration_service.get_current_value(key) - - rows: list[list[types.InlineKeyboardButton]] = [] - - for index, (value, label) in enumerate(choices, start=1): - if isinstance(value, str) and isinstance(current_value, str): - is_selected = value.lower() == current_value.lower() - else: - is_selected = value == current_value - prefix = "✅ " if is_selected else "" - rows.append( - [ - types.InlineKeyboardButton( - text=f"{prefix}{label}", - callback_data=( - f"botcfg_choice_set:{group_key}:{category_page}:{settings_page}:{token}:{index}" - ), - ) - ] - ) - - rows.append( - [ - types.InlineKeyboardButton( - text="⬅️ Назад", - callback_data=( - f"botcfg_setting:{group_key}:{category_page}:{settings_page}:{token}" - ), - ) - ] - ) - - return types.InlineKeyboardMarkup(inline_keyboard=rows) - - def _render_setting_text(key: str) -> str: summary = bot_configuration_service.get_setting_summary(key) @@ -561,10 +495,7 @@ async def show_bot_config_setting( settings_page = max(1, int(parts[3])) if len(parts) > 3 else 1 except ValueError: settings_page = 1 - token = parts[4] if len(parts) > 4 else "" - key = await _resolve_key_from_token(callback, token) - if not key: - return + key = parts[4] if len(parts) > 4 else "" text = _render_setting_text(key) keyboard = _build_setting_keyboard(key, group_key, category_page, settings_page) await callback.message.edit_text(text, reply_markup=keyboard) @@ -596,10 +527,7 @@ async def start_edit_setting( settings_page = max(1, int(parts[3])) if len(parts) > 3 else 1 except ValueError: settings_page = 1 - token = parts[4] if len(parts) > 4 else "" - key = await _resolve_key_from_token(callback, token) - if not key: - return + key = parts[4] if len(parts) > 4 else "" definition = bot_configuration_service.get_definition(key) summary = bot_configuration_service.get_setting_summary(key) @@ -627,7 +555,7 @@ async def start_edit_setting( types.InlineKeyboardButton( text=texts.BACK, callback_data=( - f"botcfg_setting:{group_key}:{category_page}:{settings_page}:{token}" + f"botcfg_setting:{group_key}:{category_page}:{settings_page}:{key}" ), ) ] @@ -748,10 +676,7 @@ async def reset_setting( settings_page = max(1, int(parts[3])) if len(parts) > 3 else 1 except ValueError: settings_page = 1 - token = parts[4] if len(parts) > 4 else "" - key = await _resolve_key_from_token(callback, token) - if not key: - return + key = parts[4] if len(parts) > 4 else "" await bot_configuration_service.reset_value(db, key) await db.commit() @@ -786,10 +711,7 @@ async def toggle_setting( settings_page = max(1, int(parts[3])) if len(parts) > 3 else 1 except ValueError: settings_page = 1 - token = parts[4] if len(parts) > 4 else "" - key = await _resolve_key_from_token(callback, token) - if not key: - return + key = parts[4] if len(parts) > 4 else "" current = bot_configuration_service.get_current_value(key) new_value = not bool(current) await bot_configuration_service.set_value(db, key, new_value) @@ -808,115 +730,6 @@ async def toggle_setting( await callback.answer("Обновлено") -@admin_required -@error_handler -async def show_setting_choices( - callback: types.CallbackQuery, - db_user: User, - db: AsyncSession, - state: FSMContext, -): - parts = callback.data.split(":", 5) - 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 - token = parts[4] if len(parts) > 4 else "" - key = await _resolve_key_from_token(callback, token) - if not key: - return - - choices = bot_configuration_service.get_choices(key) - if not choices: - await callback.answer("Для этой настройки недоступен выбор", show_alert=True) - return - - summary = bot_configuration_service.get_setting_summary(key) - keyboard = _build_choices_keyboard(key, group_key, category_page, settings_page) - - text = "\n".join( - [ - "📋 Выбор значения", - f"Название: {summary['name']}", - f"Текущий вариант: {summary['current']}", - "", - "Выберите значение из списка ниже.", - ] - ) - - await callback.message.edit_text(text, reply_markup=keyboard) - await _store_setting_context( - state, - key=key, - group_key=group_key, - category_page=category_page, - settings_page=settings_page, - ) - await callback.answer() - - -@admin_required -@error_handler -async def apply_setting_choice( - callback: types.CallbackQuery, - db_user: User, - db: AsyncSession, - state: FSMContext, -): - parts = callback.data.split(":", 6) - 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 - token = parts[4] if len(parts) > 4 else "" - key = await _resolve_key_from_token(callback, token) - if not key: - return - - choices = bot_configuration_service.get_choices(key) - if not choices: - await callback.answer("Варианты для этой настройки недоступны", show_alert=True) - return - - try: - index = max(1, int(parts[5])) if len(parts) > 5 else 1 - except ValueError: - await callback.answer("Некорректный вариант", show_alert=True) - return - - try: - selected_value, label = choices[index - 1] - except IndexError: - await callback.answer("Некорректный вариант", show_alert=True) - return - - cast_value = bot_configuration_service.cast_choice_value(key, selected_value) - await bot_configuration_service.set_value(db, key, cast_value) - await db.commit() - - text = _render_setting_text(key) - keyboard = _build_setting_keyboard(key, group_key, category_page, settings_page) - await callback.message.edit_text(text, reply_markup=keyboard) - await _store_setting_context( - state, - key=key, - group_key=group_key, - category_page=category_page, - settings_page=settings_page, - ) - await callback.answer(f"Установлено: {label}") - - def register_handlers(dp: Dispatcher) -> None: dp.callback_query.register( show_bot_config_menu, @@ -934,14 +747,6 @@ def register_handlers(dp: Dispatcher) -> None: show_bot_config_setting, F.data.startswith("botcfg_setting:"), ) - dp.callback_query.register( - show_setting_choices, - F.data.startswith("botcfg_choices:"), - ) - dp.callback_query.register( - apply_setting_choice, - F.data.startswith("botcfg_choice_set:"), - ) dp.callback_query.register( start_edit_setting, F.data.startswith("botcfg_edit:"), diff --git a/app/services/system_settings_service.py b/app/services/system_settings_service.py index eab78518..70a9d890 100644 --- a/app/services/system_settings_service.py +++ b/app/services/system_settings_service.py @@ -40,16 +40,11 @@ class SettingDefinition: python_type: Type[Any] type_label: str is_optional: bool - choices: Optional[List[Tuple[Any, str]]] = None @property def display_name(self) -> str: return _title_from_key(self.key) - @property - def has_choices(self) -> bool: - return bool(self.choices) - class BotConfigurationService: EXCLUDED_KEYS: set[str] = {"BOT_TOKEN", "ADMIN_IDS"} @@ -88,55 +83,9 @@ class BotConfigurationService: "TELEGRAM": "Telegram Stars", } - CHOICES: Dict[str, List[Tuple[Any, str]]] = { - "SUPPORT_SYSTEM_MODE": [ - ("tickets", "Только тикеты"), - ("contact", "Только контакт"), - ("both", "Тикеты и контакт"), - ], - "REMNAWAVE_AUTH_TYPE": [ - ("api_key", "API Key"), - ("basic_auth", "Basic Auth"), - ], - "REMNAWAVE_USER_DELETE_MODE": [ - ("delete", "Удалять пользователя"), - ("disable", "Деактивировать пользователя"), - ], - "DATABASE_MODE": [ - ("auto", "Определять автоматически"), - ("postgresql", "PostgreSQL"), - ("sqlite", "SQLite"), - ], - "TRAFFIC_SELECTION_MODE": [ - ("selectable", "Пользователь выбирает пакет"), - ("fixed", "Фиксированный лимит"), - ], - "DEFAULT_TRAFFIC_RESET_STRATEGY": [ - ("NO_RESET", "Без сброса"), - ("DAY", "Ежедневно"), - ("WEEK", "Еженедельно"), - ("MONTH", "Ежемесячно"), - ], - "CONNECT_BUTTON_MODE": [ - ("guide", "Открывать гайд"), - ("miniapp_subscription", "Мини-приложение с подпиской"), - ("miniapp_custom", "Мини-приложение с кастомной ссылкой"), - ("link", "Прямая ссылка"), - ("happ_cryptolink", "Happ CryptoLink"), - ], - "SERVER_STATUS_MODE": [ - ("disabled", "Отключено"), - ("external_link", "Внешняя ссылка"), - ("external_link_miniapp", "Мини-приложение со ссылкой"), - ("xray", "Интеграция XrayChecker"), - ], - } - _definitions: Dict[str, SettingDefinition] = {} _original_values: Dict[str, Any] = settings.model_dump() _overrides_raw: Dict[str, Optional[str]] = {} - _callback_tokens: Dict[str, str] = {} - _token_lookup: Dict[str, str] = {} @classmethod def initialize_definitions(cls) -> None: @@ -157,8 +106,6 @@ class BotConfigurationService: category_key.capitalize() if category_key else "Прочее", ) - choices = cls.CHOICES.get(key) - cls._definitions[key] = SettingDefinition( key=key, category_key=category_key or "other", @@ -166,7 +113,6 @@ class BotConfigurationService: python_type=python_type, type_label=type_label, is_optional=is_optional, - choices=choices, ) @classmethod @@ -271,7 +217,7 @@ class BotConfigurationService: @classmethod def format_value_for_list(cls, key: str) -> str: value = cls.get_current_value(key) - formatted = cls._format_value_with_choices(key, value) + formatted = cls.format_value(value) if formatted == "—": return formatted return _truncate(formatted) @@ -304,55 +250,6 @@ class BotConfigurationService: cls._overrides_raw.clear() await cls.initialize() - @classmethod - def get_callback_token(cls, key: str) -> str: - cls.initialize_definitions() - if key in cls._callback_tokens: - return cls._callback_tokens[key] - - token = format(len(cls._callback_tokens) + 1, "x") - while token in cls._token_lookup: - token = format(len(cls._callback_tokens) + len(cls._token_lookup) + 1, "x") - - cls._callback_tokens[key] = token - cls._token_lookup[token] = key - return token - - @classmethod - def resolve_key_from_token(cls, token: str) -> Optional[str]: - if not token: - return None - return cls._token_lookup.get(token) - - @classmethod - def get_choices(cls, key: str) -> List[Tuple[Any, str]]: - definition = cls.get_definition(key) - return definition.choices or [] - - @classmethod - def format_choice_label(cls, key: str, value: Any) -> Optional[str]: - definition = cls.get_definition(key) - if not definition.choices: - return None - - for stored_value, label in definition.choices: - if cls._values_equal(stored_value, value): - return f"{label} ({stored_value})" - return None - - @classmethod - def cast_choice_value(cls, key: str, raw_value: Any) -> Any: - definition = cls.get_definition(key) - python_type = definition.python_type - - if python_type is bool: - return bool(raw_value) - if python_type is int: - return int(raw_value) - if python_type is float: - return float(raw_value) - return str(raw_value) - @classmethod def deserialize_value(cls, key: str, raw_value: Optional[str]) -> Any: if raw_value is None: @@ -451,30 +348,14 @@ class BotConfigurationService: return { "key": key, "name": definition.display_name, - "current": cls._format_value_with_choices(key, current), - "original": cls._format_value_with_choices(key, original), + "current": cls.format_value(current), + "original": cls.format_value(original), "type": definition.type_label, "category_key": definition.category_key, "category_label": definition.category_label, "has_override": has_override, } - @classmethod - def _format_value_with_choices(cls, key: str, value: Any) -> str: - formatted = cls.format_value(value) - definition = cls.get_definition(key) - if not definition.choices: - return formatted - - choice_label = cls.format_choice_label(key, value) - return choice_label or formatted - - @staticmethod - def _values_equal(a: Any, b: Any) -> bool: - if isinstance(a, str) and isinstance(b, str): - return a.lower() == b.lower() - return a == b - bot_configuration_service = BotConfigurationService