УРА ДИМА ПОРЕЗАЛ ФУНКЦИОНАЛ
Some checks failed
CI / Lint (ruff + mypy) (push) Failing after 34s
CI / Run tests (push) Has been skipped
CI / Docker build test (push) Successful in 19s

This commit is contained in:
2026-03-31 16:17:20 +07:00
parent c270d66487
commit fd2fb25e45
2 changed files with 122 additions and 30 deletions

View File

@@ -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,
(
"<b>Добавление mute rule</b>\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,
(
"<b>Удаление mute rule</b>\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} добавлено: <code>{escape(rule.pattern)}</code>"
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(
[
"<b>Админ-панель</b>",
"",
"Здесь оставлены только рабочие разделы для ежедневной настройки.",
"Личные уведомления настраиваются в получателях, сообщения в топики — в 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(
[
"<b>Routing и доставка</b>",
"",
"Здесь видно, куда бот отправляет сообщения: в личку пользователям или в темы группы.",
"",
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(
[
"<b>Синхронизация</b>",
f"Авто-синхронизация: {auto_sync}",
f"Статус: {status}",
f"Последняя синхронизация: {escape(last_sync)}",
f"Время отчёта: {escape(digest_time)}",
"",
"Синхронизация подтягивает актуальные issues из GlitchTip в локальный кэш бота.",
"Здесь можно только включить автосинк, поменять время и запустить sync вручную.",
]
)

View File

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