Source code for dmf.alerts.telegram_backend

import logging
from datetime import datetime
from pathlib import Path
from typing import Optional, Union

from ..utils.typing import Literal
from ..env import env

try:
    import requests
except ImportError:
    raise ImportError("The 'requests' package is required to use the Telegram backend."
                        "You can install it with 'pip install dmf-utils[alerts]'.")

from .backend import AlertBackend, AlertException

__all__ = ["TelegramBackend"]

DEFAULT_CHANNEL_ENV = "DMF_DEFAULT_CHANNEL"
TELEGRAM_API_URL = "https://api.telegram.org/bot"

TELEGRAM_EMOJI_MAPPING = {
    ":white_check_mark:": "✅",
    ":books:": "📚",
    ":warning:": "⚠️",
    ":red_circle:": "🔴",
    ":calendar: ": "📅",
    ":bell:": "🔔",
    ":stopwatch: Duration": "⏱️",
    ":inbox_tray:": "📥",
    ":outbox_tray:": "📤",
}


[docs] class TelegramBackend(AlertBackend): """ A backend for sending alerts through Telegram. This class allows you to send alerts to a specified Telegram channel using a bot token. It supports sending plain text messages, as well as messages with attachments. To use this backend, you'll need to provide a Telegram bot token and specify a channel (chat ID) where the alerts should be sent. **Getting a Telegram Bot Token**: 1. Open the Telegram app and start a conversation with the BotFather (@BotFather). 2. Use the `/newbot` command to create a new bot and follow the prompts to name your bot. 3. After creating the bot, BotFather will provide you with a token. This token is required to authenticate your bot and should be passed as the `token` parameter when initializing the `TelegramBackend`. **Channel Parameter**: - The `channel` parameter should be the chat ID where messages should be sent. - The chat ID can be obtained by adding the bot to the desired channel or group and retrieving the chat ID using the Telegram API or a bot command. - If you're sending messages to a private group or channel, ensure that your Telegram bot has been added to that group or channel. **Example Usage**: To send a simple message using the Telegram backend:: backend = TelegramBackend(token="your-telegram-bot-token", channel="@your_channel_id") backend("Hello, Telegram!") :param token: The Telegram bot token used for authentication. This token is necessary to send messages to Telegram. :param channel: Optional; The Telegram channel (chat ID) where messages should be sent. If not provided, the channel will be determined by the `DMF_DEFAULT_CHANNEL` environment variable. :param fail_silently: Optional; If True, errors will be logged instead of raising an exception. Default is True. """
[docs] def __init__( self, token: str, channel: Optional[str] = None, fail_silently: bool = True ): """ Initialize the Telegram backend with the token and default channel (chat ID). :param token: The Telegram bot token used for authentication. This token is necessary to send messages to Telegram. :param channel: Optional; The Telegram channel (chat ID) where messages should be sent. If not provided, the channel will be determined by the `DMF_DEFAULT_CHANNEL` environment variable. :param fail_silently: Optional; If True, errors will be logged instead of raising an exception. Default is True. :raises AlertException: If the channel (chat ID) is not provided and is not set in the environment variables. """ super().__init__(fail_silently=fail_silently) self.token = token self.channel = channel or env.getenv(DEFAULT_CHANNEL_ENV) if not self.channel: raise AlertException( "Channel (chat ID) must be provided or set in the DMF_DEFAULT_CHANNEL environment variable." )
[docs] def send_message( self, text: str = "", attachment: Optional[Union[str, Path]] = None, scheduled_time: Optional[Union[int, datetime]] = None, ) -> None: """ Send a message to a Telegram channel, optionally with a file attachment. If both attachment and scheduled_time are provided, scheduling is ignored and the message is sent immediately. Raises AlertException if an error occurs. """ if scheduled_time is not None: if not self.fail_silently: raise AlertException( "Scheduled time is provided but is not supported by the Telegram backend." ) logging.warning( "Scheduled time is provided but is not supported by the Telegram backend. " ) try: self._send_message(text=text, attachment=attachment) except Exception as error: raise AlertException(f"Error sending message: {str(error)}") from error
def __repr__(self) -> str: return f"<{self.__class__.__name__} channel={self.channel}>" def _send_message(self, text: str, attachment: Union[str, Path]) -> None: """Send a simple message to Telegram.""" url = f"{TELEGRAM_API_URL}{self.token}/sendMessage" data = { "chat_id": self.channel, "text": text, "parse_mode": "Markdown", } response = requests.post(url, data=data) response.raise_for_status() if attachment: self._send_attachment(attachment) def _send_attachment(self, attachment: Union[str, Path]) -> None: """Send a message with an attachment to Telegram.""" file_path = str(attachment) # Ensure the path is a string url = f"{TELEGRAM_API_URL}{self.token}/sendDocument" with open(file_path, "rb") as file: data = { "chat_id": self.channel, } files = {"document": file} response = requests.post(url, data=data, files=files) response.raise_for_status()
[docs] def get_alert_text( self, text: Optional[str] = None, level: Literal["success", "info", "warning", "error"] = "info", params: Optional[dict] = None, separator: str = "\n • ", ) -> str: text = super().get_alert_text(text, level, params, separator) for emoji, symbol in TELEGRAM_EMOJI_MAPPING.items(): text = text.replace(emoji, symbol) return text