surfaces/adapter/telegram/handlers/forum.py

212 lines
7.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import structlog
from aiogram import F, Router
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.types import Chat, Message, ReplyKeyboardRemove
from adapter.telegram import db
from adapter.telegram.keyboards.forum import forum_group_request_keyboard
from adapter.telegram.states import ChatState, ForumSetupState
logger = structlog.get_logger(__name__)
router = Router(name="forum")
def _thread_id_from_topic(topic: object) -> int | None:
thread_id = getattr(topic, "message_thread_id", None)
if thread_id is not None:
return thread_id
return getattr(topic, "thread_id", None)
def _resolve_forwarded_chat(message: Message) -> Chat | None:
forwarded_chat = getattr(message, "forward_from_chat", None)
if forwarded_chat is not None:
return forwarded_chat
forward_origin = getattr(message, "forward_origin", None)
if forward_origin is None:
return None
sender_chat = getattr(forward_origin, "sender_chat", None)
if sender_chat is not None:
return sender_chat
return getattr(forward_origin, "chat", None)
def _forward_debug_payload(message: Message) -> dict[str, object]:
forward_origin = getattr(message, "forward_origin", None)
forwarded_chat = _resolve_forwarded_chat(message)
return {
"has_forward_from_chat": getattr(message, "forward_from_chat", None) is not None,
"has_forward_origin": forward_origin is not None,
"forward_origin_type": getattr(forward_origin, "type", None),
"forwarded_chat_id": getattr(forwarded_chat, "id", None),
"forwarded_chat_type": getattr(forwarded_chat, "type", None),
"forwarded_chat_is_forum": getattr(forwarded_chat, "is_forum", None),
}
async def _send_message(
message: Message,
text: str,
*,
reply_markup=None,
thread_id: int | None = None,
) -> None:
if thread_id is None:
await message.answer(text, reply_markup=reply_markup)
return
await message.bot.send_message(
message.chat.id,
text,
reply_markup=reply_markup,
message_thread_id=thread_id,
)
async def _complete_group_link(message: Message, state: FSMContext, forwarded_chat: Chat) -> None:
bot_user = await message.bot.get_me()
member = await message.bot.get_chat_member(forwarded_chat.id, bot_user.id)
can_manage_topics = getattr(member, "can_manage_topics", False)
is_admin = member.status in ("administrator", "creator")
if not is_admin or (member.status == "administrator" and not can_manage_topics):
logger.warning(
"Forum onboarding failed: bot lacks forum admin rights",
tg_user_id=message.from_user.id,
forum_group_id=forwarded_chat.id,
member_status=member.status,
can_manage_topics=can_manage_topics,
)
await message.answer(
"Я не вижу прав на управление темами. "
"Добавь меня администратором с правом `can_manage_topics` и попробуй снова.",
reply_markup=ReplyKeyboardRemove(),
)
return
tg_user_id = message.from_user.id
db.set_forum_group(tg_user_id, forwarded_chat.id)
logger.info(
"Forum group linked",
tg_user_id=tg_user_id,
forum_group_id=forwarded_chat.id,
forum_group_title=getattr(forwarded_chat, "title", None),
)
created_topics = 0
for chat in db.get_user_chats(tg_user_id):
if chat.get("forum_thread_id") is not None:
continue
topic = await message.bot.create_forum_topic(
chat_id=forwarded_chat.id,
name=chat["name"],
)
thread_id = _thread_id_from_topic(topic)
if thread_id is None:
logger.warning("Forum topic created without thread id", chat_id=chat["chat_id"])
continue
db.set_forum_thread(chat["chat_id"], thread_id)
created_topics += 1
logger.info(
"Forum topic linked to chat",
tg_user_id=tg_user_id,
chat_id=chat["chat_id"],
forum_group_id=forwarded_chat.id,
forum_thread_id=thread_id,
)
await state.set_state(ChatState.idle)
logger.info(
"Forum onboarding completed",
tg_user_id=tg_user_id,
forum_group_id=forwarded_chat.id,
created_topics=created_topics,
)
await message.answer(
f"✅ Группа подключена. Создал {created_topics} тем(ы) для существующих чатов.",
reply_markup=ReplyKeyboardRemove(),
)
@router.message(Command("forum"))
async def cmd_forum(message: Message, state: FSMContext) -> None:
await state.set_state(ForumSetupState.waiting_for_group)
logger.info("Forum onboarding started", tg_user_id=message.from_user.id)
await message.answer(
"Выбери forum-группу кнопкой ниже. Бот должен уже быть добавлен туда "
"администратором с правом управления темами.\n\n"
"Если кнопка не сработает, можно переслать сообщение из группы как fallback.",
reply_markup=forum_group_request_keyboard(),
)
@router.message(ForumSetupState.waiting_for_group)
async def handle_group_forward(message: Message, state: FSMContext) -> None:
chat_shared = getattr(message, "chat_shared", None)
if chat_shared is not None:
logger.info(
"Forum onboarding chat selected via request_chat",
tg_user_id=message.from_user.id,
forum_group_id=chat_shared.chat_id,
forum_group_title=getattr(chat_shared, "title", None),
request_id=getattr(chat_shared, "request_id", None),
)
forwarded_chat = Chat(
id=chat_shared.chat_id,
type="supergroup",
title=getattr(chat_shared, "title", None),
is_forum=True,
)
await _complete_group_link(message, state, forwarded_chat)
return
debug_payload = _forward_debug_payload(message)
logger.info(
"Forum onboarding message received",
tg_user_id=message.from_user.id,
**debug_payload,
)
forwarded_chat = _resolve_forwarded_chat(message)
if forwarded_chat is None:
logger.warning(
"Forum onboarding failed: missing forwarded chat metadata",
tg_user_id=message.from_user.id,
**debug_payload,
)
await message.answer(
"Не вижу в сообщении данных о группе. "
"Нажми кнопку `Выбрать forum-группу` или перешли сообщение именно из нужной супергруппы, не копируй текст вручную."
)
return
if forwarded_chat.type != "supergroup":
logger.warning(
"Forum onboarding failed: forwarded chat is not supergroup",
tg_user_id=message.from_user.id,
**debug_payload,
)
await message.answer(
"Пересылка пришла не из супергруппы. Нужна именно supergroup с включёнными Topics."
)
return
if getattr(forwarded_chat, "is_forum", None) is False:
logger.warning(
"Forum onboarding failed: supergroup is not forum-enabled",
tg_user_id=message.from_user.id,
**debug_payload,
)
await message.answer(
"Это супергруппа, но в ней выключены Topics. Включи Topics и попробуй снова."
)
return
await _complete_group_link(message, state, forwarded_chat)