diff --git a/src/glitchup_bot/bot/handlers/commands.py b/src/glitchup_bot/bot/handlers/commands.py index 3556ed1..34de0fa 100644 --- a/src/glitchup_bot/bot/handlers/commands.py +++ b/src/glitchup_bot/bot/handlers/commands.py @@ -11,13 +11,12 @@ from aiogram.filters import Command from aiogram.types import CallbackQuery, Message from glitchup_bot.bot.keyboards import ( - admin_admins_keyboard, admin_home_keyboard, + admin_mute_keyboard, admin_recipient_group_keyboard, admin_recipients_keyboard, admin_result_keyboard, admin_routing_keyboard, - admin_settings_keyboard, admin_sync_keyboard, help_home_keyboard, help_result_keyboard, @@ -60,6 +59,7 @@ MAX_PAGE_CHARS = 3000 MAX_PAGE_LINES = 18 MAX_PAGINATION_SESSIONS = 200 PENDING_ADMIN_ACTIONS: dict[int, str] = {} +PENDING_MUTE_ACTIONS: dict[int, str] = {} PENDING_RECIPIENT_ACTIONS: dict[int, tuple[str, str]] = {} PENDING_SETTING_ACTIONS: dict[int, tuple[str, str]] = {} @@ -803,15 +803,15 @@ async def cb_admin_actions(callback: CallbackQuery) -> None: if action == "menu:admins": await _show_callback_screen( callback, - await _admins_text(), - reply_markup=admin_admins_keyboard(), + _admin_text(), + reply_markup=admin_home_keyboard(), ) return if action == "menu:settings": await _show_callback_screen( callback, - await _runtime_settings_text(), - reply_markup=admin_settings_keyboard(), + _admin_text(), + reply_markup=admin_home_keyboard(), ) return if action in {"recipients:backend", "recipients:frontend"}: @@ -1023,12 +1023,36 @@ async def cb_admin_actions(callback: CallbackQuery) -> None: ) return if action == "mute_list": - await _deliver_result( + await _show_callback_screen( callback, await _mute_rules_text(), - back_callback="admin:open", - is_admin=True, - admin_mode=True, + reply_markup=admin_mute_keyboard(), + ) + return + if action == "mute:add": + admin_id = _callback_sender_id(callback) + if admin_id is not None: + PENDING_MUTE_ACTIONS[admin_id] = "add" + await _show_callback_screen( + callback, + ( + "Добавление mute rule\n\n" + "Отправьте следующим сообщением regex или текст шаблона, который нужно скрывать." + ), + reply_markup=admin_result_keyboard("admin:mute_list"), + ) + return + if action == "mute:del": + admin_id = _callback_sender_id(callback) + if admin_id is not None: + PENDING_MUTE_ACTIONS[admin_id] = "del" + await _show_callback_screen( + callback, + ( + "Удаление mute rule\n\n" + "Отправьте следующим сообщением ID правила, которое нужно удалить." + ), + reply_markup=admin_result_keyboard("admin:mute_list"), ) return if action == "guide": @@ -1355,6 +1379,31 @@ async def cmd_pending_recipient_input(message: Message) -> None: ) return + if user_id in PENDING_MUTE_ACTIONS: + action = PENDING_MUTE_ACTIONS.pop(user_id) + if action == "add": + rule = await add_rule(raw_value) + text = f"Mute rule #{rule.id} добавлено: {escape(rule.pattern)}" + else: + if not raw_value.isdigit(): + await message.answer("Нужен числовой ID правила.") + return + removed = await remove_rule(int(raw_value)) + text = ( + f"Mute rule #{raw_value} удалено." + if removed + else f"Mute rule #{raw_value} не найдено." + ) + + await _deliver_result( + message, + text, + back_callback="admin:mute_list", + is_admin=True, + admin_mode=True, + ) + return + if user_id not in PENDING_RECIPIENT_ACTIONS: return @@ -1760,3 +1809,61 @@ async def _admin_sync_text() -> str: "Здесь запускается ручной sync и настраивается расписание обновления и отчёта.", ] ) +def _admin_text() -> str: + return "\n".join( + [ + "Админ-панель", + "", + "Здесь оставлены только рабочие разделы для ежедневной настройки.", + "Личные уведомления настраиваются в получателях, сообщения в топики — в routing.", + ] + ) + + +async def _ownership_text() -> str: + topic_backend = await resolve_topic_id("backend") + topic_frontend = await resolve_topic_id("frontend") + topic_digest = await resolve_topic_id("digest") + backend_subscribers = await resolve_subscribers("backend") + frontend_subscribers = await resolve_subscribers("frontend") + project_overrides = await list_project_overrides() + + return "\n".join( + [ + "Routing и доставка", + "", + "Здесь видно, куда бот отправляет сообщения: в личку пользователям или в темы группы.", + "", + f"• topic backend: {topic_backend}", + f"• topic frontend: {topic_frontend}", + f"• topic digest: {topic_digest}", + f"• получатели backend: {len(backend_subscribers)}", + f"• получатели frontend: {len(frontend_subscribers)}", + f"• project overrides: {len(project_overrides)}", + ] + ) + + +async def _admin_sync_text() -> str: + runtime = await get_runtime_settings() + state = await get_last_sync_state("api_sync") + last_sync = ( + state.last_successful_at.astimezone().strftime("%Y-%m-%d %H:%M") + if state and state.last_successful_at + else "ещё не было" + ) + status = "данные загружены" if state and state.last_successful_at else "данных пока нет" + auto_sync = "ВКЛ" if runtime.sync_enabled else "ВЫКЛ" + digest_time = f"{runtime.digest_cron_hour:02d}:{runtime.digest_cron_minute:02d}" + return "\n".join( + [ + "Синхронизация", + f"Авто-синхронизация: {auto_sync}", + f"Статус: {status}", + f"Последняя синхронизация: {escape(last_sync)}", + f"Время отчёта: {escape(digest_time)}", + "", + "Синхронизация подтягивает актуальные issues из GlitchTip в локальный кэш бота.", + "Здесь можно только включить автосинк, поменять время и запустить sync вручную.", + ] + ) diff --git a/src/glitchup_bot/bot/keyboards.py b/src/glitchup_bot/bot/keyboards.py index cd34f63..d078563 100644 --- a/src/glitchup_bot/bot/keyboards.py +++ b/src/glitchup_bot/bot/keyboards.py @@ -64,11 +64,9 @@ def admin_home_keyboard() -> InlineKeyboardMarkup: builder.button(text="Синхронизация", callback_data="admin:menu:sync") builder.button(text="Получатели", callback_data="admin:menu:recipients") builder.button(text="Topic и routing", callback_data="admin:menu:routing") - builder.button(text="Настройки бота", callback_data="admin:menu:settings") - builder.button(text="Администраторы", callback_data="admin:menu:admins") builder.button(text="Mute rules", callback_data="admin:mute_list") builder.button(text="Назад", callback_data="help:open") - builder.adjust(2, 2, 2, 1) + builder.adjust(2, 2, 1) return builder.as_markup() @@ -76,11 +74,9 @@ def admin_sync_keyboard() -> InlineKeyboardMarkup: builder = InlineKeyboardBuilder() builder.button(text="Запустить sync", callback_data="admin:sync") builder.button(text="Автосинк: вкл/выкл", callback_data="admin:settings:sync_enabled") - builder.button(text="Интервал sync", callback_data="admin:settings:sync_interval") - builder.button(text="День отчёта", callback_data="admin:settings:digest_day") builder.button(text="Время отчёта", callback_data="admin:settings:digest_time") builder.button(text="Назад", callback_data="admin:open") - builder.adjust(2, 2, 1, 1) + builder.adjust(2, 1, 1) return builder.as_markup() @@ -95,17 +91,6 @@ def admin_routing_keyboard() -> InlineKeyboardMarkup: return builder.as_markup() -def admin_settings_keyboard() -> InlineKeyboardMarkup: - builder = InlineKeyboardBuilder() - builder.button(text="Часовой пояс", callback_data="admin:settings:digest_timezone") - builder.button(text="Название бота", callback_data="admin:settings:bot_title") - builder.button(text="Описание бота", callback_data="admin:settings:bot_purpose") - builder.button(text="Подсказка админу", callback_data="admin:settings:bot_admin_hint") - builder.button(text="Назад", callback_data="admin:open") - builder.adjust(2, 2, 1) - return builder.as_markup() - - def admin_recipients_keyboard() -> InlineKeyboardMarkup: builder = InlineKeyboardBuilder() builder.button(text="Backend", callback_data="admin:recipients:backend") @@ -124,10 +109,10 @@ def admin_recipient_group_keyboard(group_name: str) -> InlineKeyboardMarkup: return builder.as_markup() -def admin_admins_keyboard() -> InlineKeyboardMarkup: +def admin_mute_keyboard() -> InlineKeyboardMarkup: builder = InlineKeyboardBuilder() - builder.button(text="Добавить ID", callback_data="admin:admins:add") - builder.button(text="Удалить ID", callback_data="admin:admins:del") + builder.button(text="Добавить правило", callback_data="admin:mute:add") + builder.button(text="Удалить правило", callback_data="admin:mute:del") builder.button(text="Назад", callback_data="admin:open") builder.adjust(2, 1) return builder.as_markup()