📄 notify.py 4,620 bytes Apr 24, 2026 📋 Raw

"""Telegram notification service."""

import os
import logging
from typing import Optional
import httpx

logger = logging.getLogger(name)

TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
DEFAULT_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID") # Family group
MATT_CHAT_ID = os.getenv("TELEGRAM_MATTS_ID", "8386527252")

class TelegramNotifier:
"""Send Telegram notifications to family group or Matt directly."""

def __init__(self):
    self.token = TELEGRAM_BOT_TOKEN
    self.base_url = f"https://api.telegram.org/bot{self.token}" if self.token else None
    self.client = httpx.AsyncClient(timeout=30.0) if self.token else None

async def send(
    self,
    message: str,
    chat_id: Optional[str] = None,
    parse_mode: str = "HTML",
    disable_notification: bool = False
) -> dict:
    """Send a message to Telegram.

    Args:
        message: Text to send (HTML allowed)
        chat_id: Target chat (defaults to family group)
        parse_mode: "HTML" or "Markdown"
        disable_notification: Send silently if True

    Returns:
        Telegram API response dict
    """
    if not self.client:
        logger.warning("Telegram not configured, message not sent")
        return {"ok": False, "error": "not_configured"}

    chat_id = chat_id or DEFAULT_CHAT_ID
    if not chat_id:
        logger.error("No chat_id specified and no default configured")
        return {"ok": False, "error": "no_chat_id"}

    payload = {
        "chat_id": chat_id,
        "text": message,
        "parse_mode": parse_mode,
        "disable_notification": disable_notification
    }

    try:
        response = await self.client.post(
            f"{self.base_url}/sendMessage",
            json=payload
        )
        response.raise_for_status()
        result = response.json()

        if result.get("ok"):
            logger.info(f"Telegram sent to {chat_id}")
        else:
            logger.error(f"Telegram API error: {result}")

        return result
    except Exception as e:
        logger.error(f"Failed to send Telegram: {e}")
        return {"ok": False, "error": str(e)}

async def to_matt(self, message: str, **kwargs) -> dict:
    """Send directly to Matt's DM."""
    return await self.send(message, chat_id=MATT_CHAT_ID, **kwargs)

async def to_family(self, message: str, **kwargs) -> dict:
    """Send to family group."""
    return await self.send(message, chat_id=DEFAULT_CHAT_ID, **kwargs)

async def send_with_buttons(
    self,
    message: str,
    reply_markup: dict,
    chat_id: Optional[str] = None,
    parse_mode: str = "HTML"
) -> dict:
    """Send a message with inline keyboard buttons to Telegram.

    Args:
        message: Text to send (HTML allowed)
        reply_markup: Inline keyboard JSON (e.g., {"inline_keyboard": [[...]]})
        chat_id: Target chat (defaults to family group)
        parse_mode: "HTML" or "Markdown"

    Returns:
        Telegram API response dict
    """
    if not self.client:
        logger.warning("Telegram not configured, message not sent")
        return {"ok": False, "error": "not_configured"}

    chat_id = chat_id or DEFAULT_CHAT_ID
    if not chat_id:
        logger.error("No chat_id specified and no default configured")
        return {"ok": False, "error": "no_chat_id"}

    import json
    payload = {
        "chat_id": chat_id,
        "text": message,
        "parse_mode": parse_mode,
        "reply_markup": json.dumps(reply_markup)
    }

    try:
        response = await self.client.post(
            f"{self.base_url}/sendMessage",
            json=payload
        )
        response.raise_for_status()
        result = response.json()

        if result.get("ok"):
            logger.info(f"Telegram buttons sent to {chat_id}")
        else:
            logger.error(f"Telegram API error: {result}")

        return result
    except Exception as e:
        logger.error(f"Failed to send Telegram with buttons: {e}")
        return {"ok": False, "error": str(e)}

async def close(self):
    if self.client:
        await self.client.aclose()