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()