Files
Deal/archive/leadradar-legacy/backend/app/routers/tg_routes.py
T
Rustam Khalimov 9e07568ddd Инициализировать репозиторий «Дейл»
Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы
ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ,
инструкция пользователя, техдокументация, код-стайл), бэклог,
скрипты развёртывания и архив прототипа LeadRadar.
2026-09-11 02:50:17 +03:00

155 lines
4.9 KiB
Python

"""Telegram: статус, веб-авторизация, диалоги и мониторинг (п.4.2, 4.3 ТЗ)."""
from __future__ import annotations
import asyncio
from fastapi import APIRouter, Depends, HTTPException, Response
from pydantic import BaseModel
from ..auth import current_login
from ..services.telegram import tg
router = APIRouter(prefix="/api/tg", tags=["telegram"])
def _qr_svg(url: str) -> str:
"""Реальный QR-код (SVG, без внешних растровых зависимостей)."""
import io
import qrcode
import qrcode.image.svg
qr = qrcode.QRCode(version=None, box_size=12, border=1, error_correction=qrcode.constants.ERROR_CORRECT_M)
qr.add_data(url)
qr.make(fit=True)
buf = io.StringIO()
qr.make_image(image_factory=qrcode.image.svg.SvgPathImage).save(buf)
return buf.getvalue()
@router.get("/qr-image")
def qr_image(_: str = Depends(current_login)) -> Response:
"""SVG-картинка QR для сканирования (фаза входа 'qr')."""
if tg.phase != "qr" or not tg.qr_url:
raise HTTPException(404, "QR не активен — начните вход по QR")
return Response(
_qr_svg(tg.qr_url),
media_type="image/svg+xml",
headers={"Cache-Control": "no-store", "Content-Disposition": "inline"},
)
class PhoneBody(BaseModel):
phone: str
class CodeBody(BaseModel):
code: str
class PasswordBody(BaseModel):
password: str
class MonitorBody(BaseModel):
enabled: bool
class PreviewBody(BaseModel):
dialogId: str
limit: int = 24
@router.get("/status")
def status(_: str = Depends(current_login)) -> dict:
return tg.status()
@router.post("/start-phone")
async def start_phone(body: PhoneBody, _: str = Depends(current_login)) -> dict:
try:
await tg.start_phone(body.phone.strip())
except Exception as exc: # noqa: BLE001
raise HTTPException(400, tg.error or str(exc)) from exc
return {"phase": tg.phase}
@router.post("/start-qr")
async def start_qr(_: str = Depends(current_login)) -> dict:
try:
url = await tg.qr_start()
except Exception as exc: # noqa: BLE001
raise HTTPException(400, tg.error or str(exc)) from exc
return {"phase": tg.phase, "qrUrl": url}
@router.post("/send-code")
async def send_code(body: CodeBody, _: str = Depends(current_login)) -> dict:
try:
await tg.submit_code(body.code.strip())
except ValueError as exc:
raise HTTPException(400, str(exc)) from exc
except Exception as exc: # noqa: BLE001
raise HTTPException(400, tg.error or str(exc)) from exc
return {"phase": tg.phase}
@router.post("/send-password")
async def send_password(body: PasswordBody, _: str = Depends(current_login)) -> dict:
try:
await tg.submit_password(body.password)
except ValueError as exc:
raise HTTPException(400, str(exc)) from exc
return {"phase": tg.phase}
@router.post("/logout")
async def logout(_: str = Depends(current_login)) -> dict:
await tg.disconnect()
return {"ok": True}
@router.get("/dialogs")
def dialogs(_: str = Depends(current_login)) -> dict:
return {"items": tg.list_dialogs()}
@router.post("/dialogs/refresh")
async def dialogs_refresh(_: str = Depends(current_login)) -> dict:
if not tg.client or not tg.client.is_connected():
return {"ok": False, "reason": "not-connected", "count": 0}
count = await asyncio.wait_for(tg.refresh_dialogs(), timeout=60)
return {"ok": True, "count": count}
@router.post("/dialogs/monitor-all")
def monitor_all(body: MonitorBody, _: str = Depends(current_login)) -> dict:
"""Включить/выключить мониторинг сразу для всех каналов."""
count = tg.set_monitor_all(body.enabled)
return {"ok": True, "count": count, "enabled": body.enabled}
@router.post("/dialogs/backfill-all")
def backfill_all(_: str = Depends(current_login)) -> dict:
"""Перечитать последние 10 сообщений всех включённых каналов (кнопка)."""
count = tg.backfill_monitored()
return {"ok": True, "count": count}
@router.post("/dialogs/{dialog_id}/monitor")
def set_monitor(dialog_id: str, body: MonitorBody, _: str = Depends(current_login)) -> dict:
tg.set_monitor(dialog_id, body.enabled)
return {"ok": True, "enabled": body.enabled}
@router.post("/dialogs/{dialog_id}/backfill")
async def backfill(dialog_id: str, _: str = Depends(current_login)) -> dict:
processed = await tg.backfill_dialog(dialog_id)
return {"ok": True, "processed": processed}
@router.post("/dialogs/preview")
async def preview(body: PreviewBody, _: str = Depends(current_login)) -> dict:
items = await tg.dialog_messages(body.dialogId, min(max(body.limit, 1), 50))
return {"items": items}