feat(agent-office): Telegram bot — send messages, approval requests, webhook handler

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-11 08:49:51 +09:00
parent 8597a9efb7
commit 0754e4cab8

View File

@@ -0,0 +1,82 @@
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})