# adapter/telegram/handlers/chat.py from __future__ import annotations import asyncio from aiogram import F, Router from aiogram.filters import Command from aiogram.fsm.context import FSMContext from aiogram.types import CallbackQuery, Message from adapter.telegram import db from adapter.telegram.converter import format_outgoing, from_message from adapter.telegram.keyboards.chat import chats_list_keyboard from adapter.telegram.states import ChatState from core.handler import EventDispatcher from core.protocol import OutgoingMessage, OutgoingUI from adapter.telegram.keyboards.confirm import confirm_keyboard router = Router(name="chat") async def _send_outgoing(message: Message, chat_name: str, events: list) -> None: for event in events: if isinstance(event, OutgoingUI): from adapter.telegram.keyboards.confirm import confirm_keyboard action_id = event.buttons[0].payload.get("action_id", "unknown") if event.buttons else "unknown" kb = confirm_keyboard(action_id) await message.answer(format_outgoing(chat_name, event), reply_markup=kb) elif isinstance(event, OutgoingMessage): await message.answer(format_outgoing(chat_name, event)) @router.message(ChatState.idle, F.text | F.photo | F.document | F.voice) async def handle_message( message: Message, state: FSMContext, dispatcher: EventDispatcher, ) -> None: data = await state.get_data() chat_id = data.get("active_chat_id") chat_name = data.get("active_chat_name", "Чат") if not chat_id: await message.answer("Нет активного чата. Введите /start") return await state.set_state(ChatState.waiting_response) # Typing indicator loop async def _typing_loop(): while True: await message.bot.send_chat_action(message.chat.id, "typing") await asyncio.sleep(4) task = asyncio.create_task(_typing_loop()) try: tg_id = message.from_user.id tg_user = db.get_or_create_tg_user(tg_id, str(tg_id), message.from_user.full_name) platform_user_id = tg_user.get("platform_user_id", str(tg_id)) incoming = from_message(message, chat_id) incoming.user_id = platform_user_id events = await dispatcher.dispatch(incoming) finally: task.cancel() try: await task except asyncio.CancelledError: pass await state.set_state(ChatState.idle) await _send_outgoing(message, chat_name, events) @router.message(Command("new")) async def cmd_new_chat(message: Message, state: FSMContext) -> None: tg_id = message.from_user.id args = message.text.split(maxsplit=1) name = args[1].strip() if len(args) > 1 else None count = db.count_chats(tg_id) chat_name = name or f"Чат #{count + 1}" chat_id = db.create_chat(tg_id, chat_name) await state.update_data(active_chat_id=chat_id, active_chat_name=chat_name) await state.set_state(ChatState.idle) await message.answer(f"✅ [{chat_name}] создан. Пиши!") @router.message(Command("chats")) async def cmd_list_chats(message: Message, state: FSMContext) -> None: tg_id = message.from_user.id chats = db.get_user_chats(tg_id) if not chats: await message.answer("Нет активных чатов. Введи /new чтобы создать.") return data = await state.get_data() active_id = data.get("active_chat_id") kb = chats_list_keyboard(chats, active_id) await message.answer("Твои чаты:", reply_markup=kb) @router.callback_query(F.data.startswith("switch:")) async def switch_chat(callback: CallbackQuery, state: FSMContext) -> None: _, chat_id, chat_name = callback.data.split(":", 2) await state.update_data(active_chat_id=chat_id, active_chat_name=chat_name) await state.set_state(ChatState.idle) await callback.message.edit_text(f"✅ Переключился на [{chat_name}]") await callback.answer() @router.callback_query(F.data == "new_chat") async def cb_new_chat(callback: CallbackQuery, state: FSMContext) -> None: tg_id = callback.from_user.id count = db.count_chats(tg_id) chat_name = f"Чат #{count + 1}" chat_id = db.create_chat(tg_id, chat_name) await state.update_data(active_chat_id=chat_id, active_chat_name=chat_name) await state.set_state(ChatState.idle) await callback.message.edit_text(f"✅ [{chat_name}] создан. Пиши!") await callback.answer()