import os import uuid from aiogram import Bot, F, Router from aiogram.filters import Command from aiogram.types import CallbackQuery, Message from sqlalchemy import select from sqlalchemy.orm import Session from app.config import settings from app.messengers.telegram.keyboards import ( protocol_actions_keyboard, recording_list_keyboard, template_choice_keyboard, ) from app.models.prompt_template import PromptTemplate from app.models.protocol import Protocol, ProtocolStatus from app.models.recording import Recording, RecordingSource, RecordingStatus from app.models.user import User from app.services.auth import get_user_by_username, verify_password from app.services.recording import get_upload_path from app.tasks.transcription import transcribe_recording from app.tasks.transcription import SyncSession router = Router() _user_sessions: dict[int, uuid.UUID] = {} _pending_files: dict[int, str] = {} @router.message(Command("start")) async def cmd_start(message: Message): await message.answer( "Добро пожаловать в Meeting Protocol Bot!\n\n" "Для авторизации отправьте логин и пароль в формате:\n" "/login username password" ) @router.message(Command("login")) async def cmd_login(message: Message): parts = (message.text or "").split(maxsplit=2) if len(parts) < 3: await message.answer("Формат: /login username password") return username, password = parts[1], parts[2] with SyncSession() as db: from sqlalchemy import select as sync_select result = db.execute(sync_select(User).where(User.username == username)) user = result.scalar_one_or_none() if not user or not verify_password(password, user.password_hash): await message.answer("❌ Неверный логин или пароль") return _user_sessions[message.from_user.id] = user.id await message.answer(f"✅ Авторизация успешна! Привет, {user.username}.\n\nОтправьте аудио/видео файл для обработки.") @router.message(Command("list")) async def cmd_list(message: Message): user_id = _user_sessions.get(message.from_user.id) if not user_id: await message.answer("Сначала авторизуйтесь: /login username password") return with SyncSession() as db: result = db.execute( select(Recording) .where(Recording.user_id == user_id) .order_by(Recording.created_at.desc()) .limit(10) ) recordings = result.scalars().all() if not recordings: await message.answer("У вас пока нет записей.") return recs = [{"id": str(r.id), "title": r.title, "status": r.status.value} for r in recordings] await message.answer("📋 Ваши записи:", reply_markup=recording_list_keyboard(recs)) @router.message(Command("protocols")) async def cmd_protocols(message: Message): user_id = _user_sessions.get(message.from_user.id) if not user_id: await message.answer("Сначала авторизуйтесь: /login username password") return with SyncSession() as db: result = db.execute( select(Protocol) .join(Protocol.transcription) .join(Recording) .where(Recording.user_id == user_id) .order_by(Protocol.created_at.desc()) .limit(10) ) protocols = result.scalars().all() if not protocols: await message.answer("Протоколов пока нет.") return text_parts = [] for p in protocols: status_emoji = {"generating": "⚙️", "done": "✅", "error": "❌"} emoji = status_emoji.get(p.status.value, "") text_parts.append(f"{emoji} `{str(p.id)[:8]}` — {p.llm_model or 'N/A'}") await message.answer( "📋 Ваши протоколы:\n\n" + "\n".join(text_parts) + "\n\nДля просмотра: /view ", parse_mode="Markdown", ) @router.message(Command("templates")) async def cmd_templates(message: Message): with SyncSession() as db: result = db.execute(select(PromptTemplate)) templates = result.scalars().all() text_parts = [f"• {t.name} — {t.description}" for t in templates] await message.answer("📐 Доступные шаблоны:\n\n" + "\n".join(text_parts)) @router.message(Command("help")) async def cmd_help(message: Message): await message.answer( "📖 Команды:\n\n" "/start — приветствие\n" "/login username password — авторизация\n" "/list — список записей\n" "/protocols — список протоколов\n" "/templates — список шаблонов\n" "/view — просмотр протокола\n" "/help — эта справка\n" "/logout — выход\n\n" "Или просто отправьте аудио/видео файл!" ) @router.message(Command("logout")) async def cmd_logout(message: Message): _user_sessions.pop(message.from_user.id, None) await message.answer("Вы вышли из системы.") @router.message(F.document | F.audio | F.video | F.voice | F.video_note) async def handle_file(message: Message, bot: Bot): user_id = _user_sessions.get(message.from_user.id) if not user_id: await message.answer("Сначала авторизуйтесь: /login username password") return if message.document: file_obj = message.document filename = file_obj.file_name or "document" elif message.audio: file_obj = message.audio filename = file_obj.file_name or "audio.mp3" elif message.video: file_obj = message.video filename = f"video_{message.video.file_unique_id}.mp4" elif message.voice: file_obj = message.voice filename = f"voice_{message.voice.file_unique_id}.ogg" elif message.video_note: file_obj = message.video_note filename = f"videonote_{message.video_note.file_unique_id}.mp4" else: return if file_obj.file_size and file_obj.file_size > 50 * 1024 * 1024: await message.answer( f"⚠️ Файл слишком большой для Telegram (лимит 50 МБ).\n" f"Загрузите через веб-интерфейс: {settings.app_url}" ) return await message.answer("⏬ Загружаю файл...") file = await bot.get_file(file_obj.file_id) file_path = get_upload_path(filename) os.makedirs(os.path.dirname(file_path), exist_ok=True) await bot.download_file(file.file_path, file_path) file_size = os.path.getsize(file_path) _pending_files[message.from_user.id] = file_path with SyncSession() as db: result = db.execute(select(PromptTemplate)) templates = result.scalars().all() tpls = [{"id": str(t.id), "name": t.name} for t in templates] await message.answer( "📁 Файл получен! Выберите тип протокола:", reply_markup=template_choice_keyboard(tpls), ) _pending_files[message.from_user.id] = { "file_path": file_path, "filename": filename, "file_size": file_size, } @router.callback_query(F.data.startswith("template:")) async def on_template_selected(callback: CallbackQuery): user_id = _user_sessions.get(callback.from_user.id) if not user_id: await callback.answer("Сначала авторизуйтесь") return file_info = _pending_files.pop(callback.from_user.id, None) if not file_info or not isinstance(file_info, dict): await callback.answer("Файл не найден, отправьте заново") return template_id = callback.data.split(":")[1] from app.services.recording import get_format_from_filename fmt = get_format_from_filename(file_info["filename"]) with SyncSession() as db: recording = Recording( user_id=user_id, title=file_info["filename"], file_path=file_info["file_path"], file_size=file_info["file_size"], format=fmt, source=RecordingSource.telegram, ) db.add(recording) db.commit() db.refresh(recording) recording_id = recording.id await callback.message.edit_text("⏳ Принято! Обработка началась...") transcribe_recording.delay(str(recording_id)) await callback.answer() @router.callback_query(F.data.startswith("export:")) async def on_export_requested(callback: CallbackQuery, bot: Bot): parts = callback.data.split(":") protocol_id, fmt = parts[1], parts[2] from app.services.export import export_docx, export_markdown, export_pdf from app.models.export_file import ExportFormat with SyncSession() as db: result = db.execute(select(Protocol).where(Protocol.id == uuid.UUID(protocol_id))) protocol = result.scalar_one_or_none() if not protocol: await callback.answer("Протокол не найден") return exporters = {"docx": export_docx, "pdf": export_pdf, "md": export_markdown} exporter = exporters.get(fmt) if not exporter: await callback.answer("Неизвестный формат") return file_path = exporter(protocol.content, protocol.id) from aiogram.types import FSInputFile await bot.send_document( callback.message.chat.id, FSInputFile(file_path, filename=f"protocol.{fmt}"), ) await callback.answer()