Virtual DM chats, FSM (idle/waiting_response/settings states), SQLite local DB for tg_users+chats, converter, keyboards, and handlers for /start, /new, /chats, /settings, confirm callbacks.
122 lines
4.4 KiB
Python
122 lines
4.4 KiB
Python
# 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()
|