"""Telegram notification module for Content Brief v2 approval flow.
Sends DMs to Matt when a brief is submitted for approval.
Uses the same Telegram bot token as the family assistant (Hermes).
"""
import os
import json
from typing import Optional
import requests
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "")
MATT_CHAT_ID = os.getenv("TELEGRAM_MATT_CHAT_ID", "8386527252")
API_BASE = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}"
def _send_message(chat_id: str, text: str, reply_markup: Optional[dict] = None) -> bool:
"""Send a Telegram message. Returns True on success."""
if not TELEGRAM_BOT_TOKEN:
print("[telegram] No bot token configured, skipping notification")
return False
payload = {
"chat_id": chat_id,
"text": text,
"parse_mode": "HTML",
}
if reply_markup:
payload["reply_markup"] = json.dumps(reply_markup)
try:
resp = requests.post(f"{API_BASE}/sendMessage", json=payload, timeout=10)
resp.raise_for_status()
result = resp.json()
if result.get("ok"):
print(f"[telegram] Sent notification to {chat_id}")
return True
else:
print(f"[telegram] Failed: {result}")
return False
except Exception as e:
print(f"[telegram] Error sending message: {e}")
return False
def notify_brief_submitted(brief_id: str, title: str, created_by: str, base_url: str = "https://notes.hoffdesk.com") -> bool:
"""Notify Matt that a new brief is pending approval.
Sends an inline keyboard with Approve / Reject / Edit buttons.
"""
text = (
f"📝 New Content Brief Pending Approval\n\n"
f"Title: {title}\n"
f"Author: {created_by}\n"
f"ID: {brief_id}\n\n"
f"Review and approve to trigger content generation."
)
reply_markup = {
"inline_keyboard": [
[
{"text": "✅ Approve", "callback_data": f"brief_approve:{brief_id}"},
{"text": "❌ Reject", "callback_data": f"brief_reject:{brief_id}"},
],
[
{"text": "✏️ Edit in Admin", "url": f"{base_url}/admin/content/briefs/{brief_id}"},
],
]
}
return _send_message(MATT_CHAT_ID, text, reply_markup)
def notify_brief_approved(brief_id: str, title: str, generation_job_id: str) -> bool:
"""Notify that a brief has been approved and generation started."""
text = (
f"✅ Brief Approved — Generation Started\n\n"
f"Title: {title}\n"
f"Job ID: {generation_job_id}\n\n"
f"Content is being generated on the local GPU. You'll be notified when it's ready."
)
return _send_message(MATT_CHAT_ID, text)
def notify_generation_complete(brief_id: str, title: str, struggle_score: float, output_url: str) -> bool:
"""Notify that content generation is complete."""
score_emoji = "🟢" if struggle_score >= 75 else "🟡" if struggle_score >= 50 else "🔴"
text = (
f"✨ Content Ready for Review\n\n"
f"Title: {title}\n"
f"Struggle Score: {score_emoji} {struggle_score:.1f}/100\n\n"
f"Preview Content"
)
reply_markup = {
"inline_keyboard": [
[
{"text": "📄 Preview", "url": output_url},
{"text": "🚀 Publish", "callback_data": f"brief_publish:{brief_id}"},
],
[
{"text": "🔄 Regenerate", "callback_data": f"brief_regenerate:{brief_id}"},
],
]
}
return _send_message(MATT_CHAT_ID, text, reply_markup)
def notify_brief_rejected(brief_id: str, title: str, feedback: str) -> bool:
"""Notify that a brief was rejected."""
text = (
f"❌ Brief Rejected\n\n"
f"Title: {title}\n"
f"Feedback: {feedback or 'No feedback provided'}\n\n"
f"The brief has been returned to draft status. Edit and resubmit when ready."
)
return _send_message(MATT_CHAT_ID, text)
# ─── Hook into brief lifecycle ────────────────────────────────────────────
def on_brief_submitted(brief_id: str, title: str, created_by: str) -> bool:
"""Called when a brief is submitted for approval."""
return notify_brief_submitted(brief_id, title, created_by)
def on_brief_approved(brief_id: str, title: str, generation_job_id: str) -> bool:
"""Called when a brief is approved."""
return notify_brief_approved(brief_id, title, generation_job_id)
def on_generation_complete(brief_id: str, title: str, struggle_score: float, output_url: str) -> bool:
"""Called when generation completes."""
return notify_generation_complete(brief_id, title, struggle_score, output_url)