diff --git a/src/glitchup_bot/bot/handlers/commands.py b/src/glitchup_bot/bot/handlers/commands.py
index d56eb25..c44edfa 100644
--- a/src/glitchup_bot/bot/handlers/commands.py
+++ b/src/glitchup_bot/bot/handlers/commands.py
@@ -789,7 +789,7 @@ async def cb_admin_actions(callback: CallbackQuery) -> None:
if action == "menu:recipients":
await _show_callback_screen(
callback,
- _admin_recipients_text(),
+ await _admin_recipients_text(),
reply_markup=admin_recipients_keyboard(),
)
return
@@ -950,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:sync"),
+ reply_markup=admin_result_keyboard("admin:menu:settings"),
)
return
if action == "settings:bot_title":
@@ -1306,7 +1306,7 @@ async def cmd_pending_recipient_input(message: Message) -> None:
await reload_scheduler()
text = f"Timezone обновлён: {escape(raw_value)}."
- back_callback = "admin:menu:sync"
+ back_callback = "admin:menu:settings"
else:
await set_runtime_setting(setting_key, raw_value)
text = f"Настройка {escape(setting_key)} обновлена."
@@ -1479,6 +1479,120 @@ async def _admins_text() -> str:
return "\n".join(lines)
+async def _admin_recipients_text() -> str:
+ backend = await resolve_subscribers("backend")
+ frontend = await resolve_subscribers("frontend")
+
+ def _format_users(values: list[int]) -> str:
+ if not values:
+ return "никого нет"
+ return ", ".join(f"{user_id}" for user_id in values)
+
+ return "\n".join(
+ [
+ "Получатели уведомлений",
+ "",
+ "Здесь настраивается, кому бот отправляет уведомления в личные сообщения.",
+ "Для каждой группы ниже сразу показан текущий список пользователей.",
+ "",
+ f"Backend: {_format_users(backend)}",
+ f"Frontend: {_format_users(frontend)}",
+ ]
+ )
+
+
+def _admin_sync_text() -> str:
+ return "\n".join(
+ [
+ "Синхронизация",
+ "",
+ "Синхронизация подтягивает актуальные issues из GlitchTip в локальный кэш бота.",
+ "Здесь запускается ручной sync и настраивается расписание обновления и отчёта.",
+ ]
+ )
+
+
+def _admin_routing_text() -> str:
+ return "\n".join(
+ [
+ "Topic и routing",
+ "",
+ (
+ "Здесь настраивается доставка в Telegram-топики и привязка "
+ "проектов к backend/frontend."
+ ),
+ "Это нужно, когда уведомления должны уходить не в личку, а в темы группы.",
+ ]
+ )
+
+
+async def _runtime_settings_text() -> str:
+ runtime = await get_runtime_settings()
+ return "\n".join(
+ [
+ "Настройки бота",
+ "",
+ "Здесь собраны общие тексты бота и часовой пояс для расписания.",
+ f"• часовой пояс: {escape(runtime.digest_timezone)}",
+ f"• название: {escape(runtime.bot_title)}",
+ f"• описание: {escape(runtime.bot_purpose)}",
+ f"• подсказка админу: {escape(runtime.bot_admin_hint)}",
+ ]
+ )
+
+
+def _recipient_group_text(group_name: str) -> str:
+ title = group_name.capitalize()
+ return "\n".join(
+ [
+ f"{escape(title)} получатели",
+ "",
+ "Здесь вы управляете пользователями, которым бот пишет в личные сообщения.",
+ "Можно быстро добавить или удалить Telegram ID кнопками ниже.",
+ ]
+ )
+
+
+async def _admins_text() -> str:
+ admins = await list_effective_admins()
+ lines = [
+ "Администраторы",
+ "",
+ "Администраторы настраивают бота, получателей, топики и расписание.",
+ "Добавление и удаление можно делать прямо кнопками ниже по 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"• {user_id} — {source_label}")
+ return "\n".join(lines)
+
+
+async def _mute_rules_text() -> str:
+ rules = await list_rules()
+ lines = [
+ "Mute rules",
+ "",
+ (
+ "Mute rules скрывают шумные и неважные события, чтобы они не мешали "
+ "в сводках и уведомлениях."
+ ),
+ ]
+ if not rules:
+ lines.extend(["", "Правила пока не настроены."])
+ return "\n".join(lines)
+
+ lines.append("")
+ for rule in rules:
+ suffix = f" — {escape(rule.description)}" if rule.description else ""
+ lines.append(f"• #{rule.id} {escape(rule.pattern)}{suffix}")
+ return "\n".join(lines)
+
+
@router.message(Command("subscribe"))
async def cmd_subscribe(message: Message) -> None:
await message.answer("Самоподписка отключена. Получателей настраивает администратор.")
diff --git a/src/glitchup_bot/bot/keyboards.py b/src/glitchup_bot/bot/keyboards.py
index 9478b23..c0e8f8b 100644
--- a/src/glitchup_bot/bot/keyboards.py
+++ b/src/glitchup_bot/bot/keyboards.py
@@ -52,16 +52,10 @@ def help_result_keyboard(
total_pages=total_pages,
)
builder.button(text="Назад", callback_data=back_callback)
- if is_admin:
- if total_pages > 1:
- builder.adjust(3, 1)
- else:
- builder.adjust(1)
+ if total_pages > 1:
+ builder.adjust(3, 1)
else:
- if total_pages > 1:
- builder.adjust(3, 1)
- else:
- builder.adjust(1)
+ builder.adjust(1)
return builder.as_markup()
@@ -73,9 +67,8 @@ def admin_home_keyboard() -> InlineKeyboardMarkup:
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="admin:guide")
builder.button(text="Назад", callback_data="help:open")
- builder.adjust(2, 2, 2, 2)
+ builder.adjust(2, 2, 2, 1)
return builder.as_markup()
@@ -87,9 +80,8 @@ def admin_sync_keyboard() -> InlineKeyboardMarkup:
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:settings:digest_timezone")
builder.button(text="Назад", callback_data="admin:open")
- builder.adjust(2, 2, 2, 1, 1)
+ builder.adjust(2, 2, 2, 1)
return builder.as_markup()
@@ -106,11 +98,12 @@ def admin_routing_keyboard() -> InlineKeyboardMarkup:
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, 1, 1)
+ builder.adjust(2, 2, 1)
return builder.as_markup()
@@ -125,21 +118,19 @@ def admin_recipients_keyboard() -> InlineKeyboardMarkup:
def admin_recipient_group_keyboard(group_name: str) -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
- 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:del:{group_name}")
builder.button(text="Назад", callback_data="admin:menu:recipients")
- builder.adjust(2, 1, 1)
+ builder.adjust(2, 1)
return builder.as_markup()
def admin_admins_keyboard() -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
- builder.button(text="Список", callback_data="admin:admins")
builder.button(text="Добавить ID", callback_data="admin:admins:add")
builder.button(text="Удалить ID", callback_data="admin:admins:del")
builder.button(text="Назад", callback_data="admin:open")
- builder.adjust(2, 1, 1)
+ builder.adjust(2, 1)
return builder.as_markup()