Files
web-page-backend/agent-office/app/telegram_bot.py
gahusb 6f8b199548 feat: Agent Office — AI 에이전트 가상 오피스 (#2)
## Summary
- 2D 픽셀아트 가상 오피스에서 AI 에이전트(Stock, Music)가 실제 작업 수행
- FastAPI + WebSocket 실시간 상태 동기화 + 텔레그램 봇 양방향 알림/승인
- BaseAgent FSM (idle/working/waiting/reporting/break), 서비스 프록시 패턴
- Docker Compose 서비스 (port 18900) + Nginx WebSocket 프록시

## Changes (13 commits)
- Backend scaffold: config, db, models, Dockerfile
- WebSocket manager + Service proxy
- BaseAgent FSM + StockAgent + MusicAgent
- Telegram bot + Scheduler
- FastAPI main (REST + WS endpoints)
- Infrastructure: docker-compose + nginx
- Code review fixes: HTTPException, async polling, input validation

Reviewed-on: #2
2026-04-11 13:35:24 +09:00

83 lines
2.7 KiB
Python

import json
import uuid
import httpx
from typing import Optional
from .config import TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID, TELEGRAM_WEBHOOK_URL
from .db import save_telegram_callback, get_telegram_callback, mark_telegram_responded
_BASE = "https://api.telegram.org/bot"
def _enabled() -> bool:
return bool(TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID)
async def _api(method: str, payload: dict) -> dict:
if not _enabled():
return {"ok": False, "description": "Telegram not configured"}
async with httpx.AsyncClient(timeout=10.0) as client:
resp = await client.post(f"{_BASE}{TELEGRAM_BOT_TOKEN}/{method}", json=payload)
return resp.json()
async def send_message(text: str, reply_markup: dict = None) -> dict:
payload = {
"chat_id": TELEGRAM_CHAT_ID,
"text": text,
"parse_mode": "HTML",
}
if reply_markup:
payload["reply_markup"] = reply_markup
return await _api("sendMessage", payload)
async def send_stock_summary(summary: str) -> dict:
return await send_message(summary)
async def send_approval_request(agent_id: str, task_id: str, title: str, detail: str) -> dict:
approve_id = f"approve_{uuid.uuid4().hex[:8]}"
reject_id = f"reject_{uuid.uuid4().hex[:8]}"
save_telegram_callback(approve_id, task_id, agent_id)
save_telegram_callback(reject_id, task_id, agent_id)
text = f"{title}\n{'' * 20}\n{detail}"
reply_markup = {
"inline_keyboard": [[
{"text": "✅ 승인", "callback_data": approve_id},
{"text": "❌ 거절", "callback_data": reject_id},
]]
}
return await send_message(text, reply_markup)
async def send_task_result(agent_id: str, title: str, result: str) -> dict:
text = f"{title}\n{'' * 20}\n{result}"
return await send_message(text)
async def handle_webhook(data: dict) -> Optional[dict]:
callback_query = data.get("callback_query")
if not callback_query:
return None
callback_id = callback_query.get("data", "")
cb = get_telegram_callback(callback_id)
if not cb:
return None
action = "approve" if callback_id.startswith("approve_") else "reject"
mark_telegram_responded(callback_id, action)
await _api("answerCallbackQuery", {
"callback_query_id": callback_query["id"],
"text": "승인됨 ✅" if action == "approve" else "거절됨 ❌",
})
return {
"task_id": cb["task_id"],
"agent_id": cb["agent_id"],
"action": action,
"approved": action == "approve",
}
async def setup_webhook() -> dict:
if not _enabled() or not TELEGRAM_WEBHOOK_URL:
return {"ok": False, "description": "Webhook URL not configured"}
return await _api("setWebhook", {"url": TELEGRAM_WEBHOOK_URL})