Правки проблем
Some checks failed
CI / Lint (ruff + mypy) (push) Failing after 35s
CI / Run tests (push) Has been skipped
CI / Docker build test (push) Successful in 18s

This commit is contained in:
2026-03-31 14:47:21 +07:00
parent e811b259fc
commit 8b835ad08d
2 changed files with 157 additions and 8 deletions

View File

@@ -6,7 +6,7 @@ from html import escape
from uuid import uuid4 from uuid import uuid4
from aiogram import F, Router from aiogram import F, Router
from aiogram.exceptions import TelegramBadRequest from aiogram.exceptions import TelegramBadRequest, TelegramRetryAfter
from aiogram.filters import Command from aiogram.filters import Command
from aiogram.types import CallbackQuery, Message from aiogram.types import CallbackQuery, Message
@@ -281,6 +281,72 @@ def _admin_guide_text() -> str:
) )
def _admin_hint_text(section: str, target: str | None = None) -> str:
hints = {
"sync": "\n".join(
[
"<b>Что такое синхронизация</b>",
"",
"Синхронизация подтягивает актуальные issues из GlitchTip в локальный кэш бота.",
"Ручной sync нужен, когда вы хотите обновить данные "
"прямо сейчас, не дожидаясь расписания.",
]
),
"recipients": "\n".join(
[
"<b>Что такое получатели</b>",
"",
"Получатели — это Telegram ID пользователей, которым бот отправляет уведомления.",
"Backend и frontend настраиваются отдельно.",
]
),
"routing": "\n".join(
[
"<b>Что такое Topic и routing</b>",
"",
"Routing определяет, в какую группу попадает проект.",
"Topic override определяет, в какую тему Telegram будут "
"приходить сообщения для backend, frontend и digest.",
]
),
"settings": "\n".join(
[
"<b>Что такое настройки бота</b>",
"",
"Здесь собраны runtime-параметры, которые можно менять "
"без правки `.env` и без пересборки контейнера.",
"Сюда входят автосинк, расписание digest и тексты интерфейса.",
]
),
"mute": "\n".join(
[
"<b>Что такое mute rules</b>",
"",
"Mute rule — это шаблон, по которому бот скрывает шумные или неважные события.",
"Если событие совпало с правилом, оно не будет мешать в уведомлениях.",
]
),
"admins": "\n".join(
[
"<b>Что такое администраторы</b>",
"",
"Администраторы могут менять настройки бота, получателей и расписание.",
"Часть админов может приходить из `.env`, а часть хранится в runtime-базе.",
]
),
"recipient_group": "\n".join(
[
f"<b>{escape((target or '').capitalize())} получатели</b>",
"",
"Добавляйте сюда Telegram ID тех, кто должен получать "
"уведомления по выбранной группе.",
"Удаление отсюда прекращает доставку уведомлений этому пользователю.",
]
),
}
return hints[section]
def _sync_summary_text(summary) -> str: def _sync_summary_text(summary) -> str:
return "\n".join( return "\n".join(
[ [
@@ -405,6 +471,18 @@ async def _show_callback_screen(
reply_markup=reply_markup, reply_markup=reply_markup,
disable_web_page_preview=disable_web_page_preview, disable_web_page_preview=disable_web_page_preview,
) )
except TelegramRetryAfter as exc:
logger.warning(
"Telegram edit flood control for chat %s; "
"falling back to sending a new message after %s seconds",
callback.message.chat.id,
exc.retry_after,
)
await callback.message.answer(
text,
reply_markup=reply_markup,
disable_web_page_preview=disable_web_page_preview,
)
except TelegramBadRequest as exc: except TelegramBadRequest as exc:
if "message is not modified" in str(exc).lower(): if "message is not modified" in str(exc).lower():
return return
@@ -720,6 +798,70 @@ async def cb_admin_actions(callback: CallbackQuery) -> None:
reply_markup=admin_routing_keyboard(), reply_markup=admin_routing_keyboard(),
) )
return return
if action == "hint:sync":
await _deliver_result(
callback,
_admin_hint_text("sync"),
back_callback="admin:menu:sync",
is_admin=True,
admin_mode=True,
)
return
if action == "hint:recipients":
await _deliver_result(
callback,
_admin_hint_text("recipients"),
back_callback="admin:menu:recipients",
is_admin=True,
admin_mode=True,
)
return
if action == "hint:routing":
await _deliver_result(
callback,
_admin_hint_text("routing"),
back_callback="admin:menu:routing",
is_admin=True,
admin_mode=True,
)
return
if action == "hint:settings":
await _deliver_result(
callback,
_admin_hint_text("settings"),
back_callback="admin:menu:settings",
is_admin=True,
admin_mode=True,
)
return
if action == "hint:mute":
await _deliver_result(
callback,
_admin_hint_text("mute"),
back_callback="admin:open",
is_admin=True,
admin_mode=True,
)
return
if action == "hint:admins":
await _deliver_result(
callback,
_admin_hint_text("admins"),
back_callback="admin:open",
is_admin=True,
admin_mode=True,
)
return
if action.startswith("hint:recipient_group:"):
group_name = action.rsplit(":", 1)[1]
await _deliver_result(
callback,
_admin_hint_text("recipient_group", group_name),
back_callback=f"admin:recipients:{group_name}",
is_admin=True,
admin_mode=True,
)
return
if action == "menu:settings": if action == "menu:settings":
await _show_callback_screen( await _show_callback_screen(
callback, callback,

View File

@@ -72,9 +72,12 @@ def admin_home_keyboard() -> InlineKeyboardMarkup:
builder.button(text="Topic и routing", callback_data="admin:menu:routing") 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:settings")
builder.button(text="Администраторы", callback_data="admin:admins") builder.button(text="Администраторы", callback_data="admin:admins")
builder.button(text="Что такое админы?", callback_data="admin:hint:admins")
builder.button(text="Mute rules", callback_data="admin:mute_list") builder.button(text="Mute rules", callback_data="admin:mute_list")
builder.button(text="Что такое mute?", callback_data="admin:hint:mute")
builder.button(text="Инструкция", callback_data="admin:guide") builder.button(text="Инструкция", callback_data="admin:guide")
builder.adjust(2, 2, 2, 1) builder.button(text="Назад", callback_data="help:open")
builder.adjust(2, 2, 2, 2, 1)
return builder.as_markup() return builder.as_markup()
@@ -82,9 +85,9 @@ def admin_sync_keyboard() -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
builder.button(text="Запустить sync", callback_data="admin:sync") builder.button(text="Запустить sync", callback_data="admin:sync")
builder.button(text="Статус sync", callback_data="admin:sync_status") builder.button(text="Статус sync", callback_data="admin:sync_status")
builder.button(text="Что это?", callback_data="admin:hint:sync")
builder.button(text="Назад", callback_data="admin:open") builder.button(text="Назад", callback_data="admin:open")
builder.button(text="Пользовательское меню", callback_data="help:open") builder.adjust(2, 1, 1)
builder.adjust(2, 2)
return builder.as_markup() return builder.as_markup()
@@ -94,8 +97,9 @@ def admin_routing_keyboard() -> InlineKeyboardMarkup:
builder.button(text="Topic backend", callback_data="admin:settings:topic:backend") builder.button(text="Topic backend", callback_data="admin:settings:topic:backend")
builder.button(text="Topic frontend", callback_data="admin:settings:topic:frontend") builder.button(text="Topic frontend", callback_data="admin:settings:topic:frontend")
builder.button(text="Topic digest", callback_data="admin:settings:topic:digest") builder.button(text="Topic digest", callback_data="admin:settings:topic:digest")
builder.button(text="Что это?", callback_data="admin:hint:routing")
builder.button(text="Назад", callback_data="admin:open") builder.button(text="Назад", callback_data="admin:open")
builder.adjust(2, 2, 1) builder.adjust(2, 2, 1, 1)
return builder.as_markup() return builder.as_markup()
@@ -109,8 +113,9 @@ def admin_settings_keyboard() -> InlineKeyboardMarkup:
builder.button(text="Название бота", callback_data="admin:settings:bot_title") 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_purpose")
builder.button(text="Подсказка админу", callback_data="admin:settings:bot_admin_hint") builder.button(text="Подсказка админу", callback_data="admin:settings:bot_admin_hint")
builder.button(text="Что это?", callback_data="admin:hint:settings")
builder.button(text="Назад", callback_data="admin:open") builder.button(text="Назад", callback_data="admin:open")
builder.adjust(2, 2, 2, 2, 1) builder.adjust(2, 2, 2, 2, 1, 1)
return builder.as_markup() return builder.as_markup()
@@ -118,8 +123,9 @@ def admin_recipients_keyboard() -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
builder.button(text="Backend", callback_data="admin:recipients:backend") builder.button(text="Backend", callback_data="admin:recipients:backend")
builder.button(text="Frontend", callback_data="admin:recipients:frontend") builder.button(text="Frontend", callback_data="admin:recipients:frontend")
builder.button(text="Что это?", callback_data="admin:hint:recipients")
builder.button(text="Назад", callback_data="admin:open") builder.button(text="Назад", callback_data="admin:open")
builder.adjust(2, 1) builder.adjust(2, 1, 1)
return builder.as_markup() return builder.as_markup()
@@ -128,8 +134,9 @@ def admin_recipient_group_keyboard(group_name: str) -> InlineKeyboardMarkup:
builder.button(text="Список", callback_data=f"admin:recipients:list:{group_name}") builder.button(text="Список", callback_data=f"admin:recipients:list:{group_name}")
builder.button(text="Добавить ID", callback_data=f"admin:recipients:add:{group_name}") builder.button(text="Добавить ID", callback_data=f"admin:recipients:add:{group_name}")
builder.button(text="Удалить ID", callback_data=f"admin:recipients:del:{group_name}") builder.button(text="Удалить ID", callback_data=f"admin:recipients:del:{group_name}")
builder.button(text="Что это?", callback_data=f"admin:hint:recipient_group:{group_name}")
builder.button(text="Назад", callback_data="admin:menu:recipients") builder.button(text="Назад", callback_data="admin:menu:recipients")
builder.adjust(2, 1, 1) builder.adjust(2, 1, 1, 1)
return builder.as_markup() return builder.as_markup()