Убраны подсказки лишние и добавлено пояснение для димитриев
Some checks failed
CI / Lint (ruff + mypy) (push) Failing after 36s
CI / Run tests (push) Has been skipped
CI / Docker build test (push) Successful in 18s

This commit is contained in:
2026-03-31 15:11:46 +07:00
parent 8b835ad08d
commit c10f3bf862
2 changed files with 190 additions and 93 deletions

View File

@@ -11,6 +11,7 @@ from aiogram.filters import Command
from aiogram.types import CallbackQuery, Message
from glitchup_bot.bot.keyboards import (
admin_admins_keyboard,
admin_home_keyboard,
admin_recipient_group_keyboard,
admin_recipients_keyboard,
@@ -58,6 +59,7 @@ logger = logging.getLogger(__name__)
MAX_PAGE_CHARS = 3000
MAX_PAGE_LINES = 18
MAX_PAGINATION_SESSIONS = 200
PENDING_ADMIN_ACTIONS: dict[int, str] = {}
PENDING_RECIPIENT_ACTIONS: dict[int, tuple[str, str]] = {}
PENDING_SETTING_ACTIONS: dict[int, tuple[str, str]] = {}
@@ -798,68 +800,11 @@ async def cb_admin_actions(callback: CallbackQuery) -> None:
reply_markup=admin_routing_keyboard(),
)
return
if action == "hint:sync":
await _deliver_result(
if action == "menu:admins":
await _show_callback_screen(
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,
await _admins_text(),
reply_markup=admin_admins_keyboard(),
)
return
if action == "menu:settings":
@@ -925,11 +870,37 @@ async def cb_admin_actions(callback: CallbackQuery) -> None:
await _deliver_result(
callback,
await _admins_text(),
back_callback="admin:open",
back_callback="admin:menu:admins",
is_admin=True,
admin_mode=True,
)
return
if action == "admins:add":
admin_id = _callback_sender_id(callback)
if admin_id is not None:
PENDING_ADMIN_ACTIONS[admin_id] = "add"
await _show_callback_screen(
callback,
(
"<b>Добавление администратора</b>\n\n"
"Отправьте следующим сообщением Telegram ID, который нужно добавить."
),
reply_markup=admin_result_keyboard("admin:menu:admins"),
)
return
if action == "admins:del":
admin_id = _callback_sender_id(callback)
if admin_id is not None:
PENDING_ADMIN_ACTIONS[admin_id] = "del"
await _show_callback_screen(
callback,
(
"<b>Удаление администратора</b>\n\n"
"Отправьте следующим сообщением Telegram ID, который нужно удалить."
),
reply_markup=admin_result_keyboard("admin:menu:admins"),
)
return
if action == "settings:sync_enabled":
runtime = await get_runtime_settings()
await set_runtime_setting("sync_enabled", "false" if runtime.sync_enabled else "true")
@@ -938,8 +909,8 @@ async def cb_admin_actions(callback: CallbackQuery) -> None:
await reload_scheduler()
await _show_callback_screen(
callback,
await _runtime_settings_text(),
reply_markup=admin_settings_keyboard(),
_admin_sync_text(),
reply_markup=admin_sync_keyboard(),
)
return
if action == "settings:sync_interval":
@@ -949,7 +920,7 @@ async def cb_admin_actions(callback: CallbackQuery) -> None:
await _show_callback_screen(
callback,
_setting_prompt_text("sync_interval_minutes", ""),
reply_markup=admin_result_keyboard("admin:menu:settings"),
reply_markup=admin_result_keyboard("admin:menu:sync"),
)
return
if action == "settings:digest_day":
@@ -959,7 +930,7 @@ async def cb_admin_actions(callback: CallbackQuery) -> None:
await _show_callback_screen(
callback,
_setting_prompt_text("digest_cron_day", ""),
reply_markup=admin_result_keyboard("admin:menu:settings"),
reply_markup=admin_result_keyboard("admin:menu:sync"),
)
return
if action == "settings:digest_time":
@@ -969,7 +940,7 @@ async def cb_admin_actions(callback: CallbackQuery) -> None:
await _show_callback_screen(
callback,
_setting_prompt_text("digest_time", ""),
reply_markup=admin_result_keyboard("admin:menu:settings"),
reply_markup=admin_result_keyboard("admin:menu:sync"),
)
return
if action == "settings:digest_timezone":
@@ -979,7 +950,7 @@ async def cb_admin_actions(callback: CallbackQuery) -> None:
await _show_callback_screen(
callback,
_setting_prompt_text("digest_timezone", ""),
reply_markup=admin_result_keyboard("admin:menu:settings"),
reply_markup=admin_result_keyboard("admin:menu:sync"),
)
return
if action == "settings:bot_title":
@@ -1217,7 +1188,7 @@ async def cmd_admins(message: Message) -> None:
await _deliver_result(
message,
await _admins_text(),
back_callback="admin:open",
back_callback="admin:menu:admins",
is_admin=True,
admin_mode=True,
)
@@ -1244,7 +1215,7 @@ async def cmd_admin_add(message: Message) -> None:
await _deliver_result(
message,
text,
back_callback="admin:open",
back_callback="admin:menu:admins",
is_admin=True,
admin_mode=True,
)
@@ -1269,7 +1240,7 @@ async def cmd_admin_del(message: Message) -> None:
await _deliver_result(
message,
text,
back_callback="admin:open",
back_callback="admin:menu:admins",
is_admin=True,
admin_mode=True,
)
@@ -1306,14 +1277,14 @@ async def cmd_pending_recipient_input(message: Message) -> None:
await reload_scheduler()
text = f"Интервал sync обновлён: <code>{escape(raw_value)}</code> мин."
back_callback = "admin:menu:settings"
back_callback = "admin:menu:sync"
elif setting_key == "digest_cron_day":
await set_runtime_setting("digest_cron_day", raw_value)
from glitchup_bot.tasks.scheduler import reload_scheduler
await reload_scheduler()
text = f"День weekly digest обновлён: <code>{escape(raw_value)}</code>."
back_callback = "admin:menu:settings"
back_callback = "admin:menu:sync"
elif setting_key == "digest_time":
if ":" not in raw_value:
await message.answer("Нужно указать время в формате HH:MM.")
@@ -1328,14 +1299,14 @@ async def cmd_pending_recipient_input(message: Message) -> None:
await reload_scheduler()
text = f"Время weekly digest обновлено: <code>{escape(raw_value)}</code>."
back_callback = "admin:menu:settings"
back_callback = "admin:menu:sync"
elif setting_key == "digest_timezone":
await set_runtime_setting("digest_timezone", raw_value)
from glitchup_bot.tasks.scheduler import reload_scheduler
await reload_scheduler()
text = f"Timezone обновлён: <code>{escape(raw_value)}</code>."
back_callback = "admin:menu:settings"
back_callback = "admin:menu:sync"
else:
await set_runtime_setting(setting_key, raw_value)
text = f"Настройка <code>{escape(setting_key)}</code> обновлена."
@@ -1350,6 +1321,40 @@ async def cmd_pending_recipient_input(message: Message) -> None:
)
return
if user_id in PENDING_ADMIN_ACTIONS:
if not raw_value.lstrip("-").isdigit():
await message.answer("Нужен числовой Telegram ID.")
return
action = PENDING_ADMIN_ACTIONS.pop(user_id)
target_id = int(raw_value)
if action == "add":
added = await add_admin(target_id)
text = (
f"Администратор <code>{target_id}</code> добавлен."
if added
else (
f"Пользователь <code>{target_id}</code> уже есть среди "
"runtime-администраторов."
)
)
else:
removed = await remove_admin(target_id)
text = (
f"Runtime-администратор <code>{target_id}</code> удалён."
if removed
else f"Runtime-администратор <code>{target_id}</code> не найден."
)
await _deliver_result(
message,
text,
back_callback="admin:menu:admins",
is_admin=True,
admin_mode=True,
)
return
if user_id not in PENDING_RECIPIENT_ACTIONS:
return
@@ -1385,6 +1390,95 @@ async def cmd_pending_recipient_input(message: Message) -> None:
)
def _admin_routing_text() -> str:
return "\n".join(
[
"<b>Topic и routing</b>",
"",
"Здесь можно смотреть текущую схему доставки и менять topic override без env.",
(
"Routing определяет, к какой группе относится проект, а topic override "
"определяет тему Telegram для backend, frontend и digest."
),
]
)
def _admin_sync_text() -> str:
return "\n".join(
[
"<b>Синхронизация</b>",
"",
"Синхронизация подтягивает актуальные issues из GlitchTip в локальный кэш бота.",
(
"Здесь удобно запускать ручной sync, смотреть статус и менять "
"расписание, не трогая .env."
),
]
)
def _admin_recipients_text() -> str:
return "\n".join(
[
"<b>Получатели уведомлений</b>",
"",
"Выберите группу и назначайте Telegram ID через кнопки.",
(
"Получатели — это пользователи, которым бот отправляет уведомления "
"по backend или frontend."
),
]
)
async def _runtime_settings_text() -> str:
runtime = await get_runtime_settings()
return "\n".join(
[
"<b>Настройки бота</b>",
"",
"Здесь собраны тексты интерфейса, которые можно менять без правки .env.",
f"• название: {escape(runtime.bot_title)}",
f"• описание: {escape(runtime.bot_purpose)}",
f"• подсказка админу: {escape(runtime.bot_admin_hint)}",
]
)
def _recipient_group_text(group_name: str) -> str:
return "\n".join(
[
f"<b>{escape(group_name.capitalize())} получатели</b>",
"",
"Здесь можно посмотреть список, добавить новый Telegram ID или удалить существующий.",
(
"Удаление отсюда прекращает доставку уведомлений этому пользователю "
"по выбранной группе."
),
]
)
async def _admins_text() -> str:
admins = await list_effective_admins()
lines = [
"<b>Администраторы</b>",
"",
"Администраторы могут менять настройки бота, получателей и расписание.",
"Добавление и удаление можно делать прямо кнопками ниже по Telegram ID.",
]
if not admins:
lines.extend(["", "Список пока пуст."])
return "\n".join(lines)
lines.append("")
for user_id, source in admins:
source_label = source.replace("env", "из .env")
lines.append(f"• <code>{user_id}</code> — {source_label}")
return "\n".join(lines)
@router.message(Command("subscribe"))
async def cmd_subscribe(message: Message) -> None:
await message.answer("Самоподписка отключена. Получателей настраивает администратор.")