feat(tg): forum-first adapter complete — handlers, bot.py, 46 tests pass

This commit is contained in:
Mikhail Putilovskij 2026-04-02 13:23:40 +03:00
parent 82dc840544
commit 24c61468d7
9 changed files with 675 additions and 0 deletions

View file

@ -0,0 +1,75 @@
from __future__ import annotations
import structlog
from aiogram import Router
from aiogram.exceptions import TelegramBadRequest
from aiogram.filters import CommandStart
from aiogram.types import Message
from adapter.telegram import db
logger = structlog.get_logger(__name__)
router = Router(name="start")
@router.message(CommandStart())
async def cmd_start(message: Message) -> None:
"""
Bootstrap the user's forum.
First visit: create Чат #1, hide General topic.
Returning visit: health-check all active topics, archive stale ones.
"""
user_id = message.from_user.id
chat_id = message.chat.id
await _check_and_prune_stale_topics(message, user_id, chat_id)
active = db.get_active_chats(user_id)
if not active:
try:
topic = await message.bot.create_forum_topic(chat_id=chat_id, name="Чат #1")
thread_id = topic.message_thread_id
db.create_chat(user_id=user_id, thread_id=thread_id, chat_name="Чат #1")
logger.info("start_created_first_topic", user_id=user_id, thread_id=thread_id)
except TelegramBadRequest as e:
logger.warning("start_create_topic_failed", error=str(e))
await message.answer(
"Не удалось создать топик. Убедись, что в @BotFather включён "
"Threaded Mode для этого бота."
)
return
try:
await message.bot.hide_general_forum_topic(chat_id=chat_id)
except TelegramBadRequest:
pass # Not critical
await message.answer(
"Привет! Это твоё личное пространство с AI-агентом Lambda. "
"Каждый топик — отдельный контекст. Напиши что-нибудь."
)
else:
await message.answer(
f"Снова привет! У тебя {len(active)} активных чатов. "
"Напиши /new чтобы создать новый."
)
async def _check_and_prune_stale_topics(
message: Message, user_id: int, chat_id: int
) -> None:
"""Send typing action to each active topic; archive any that no longer exist."""
for chat in db.get_active_chats(user_id):
thread_id = chat["thread_id"]
try:
await message.bot.send_chat_action(
chat_id=chat_id,
action="typing",
message_thread_id=thread_id,
)
except TelegramBadRequest:
db.archive_chat(user_id=user_id, thread_id=thread_id)
logger.info("pruned_stale_topic", user_id=user_id, thread_id=thread_id)