File size: 5,391 Bytes
48ecd01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#!/usr/bin/env python3
"""
Standalone Telegram notification helper for FRANKENSTALLM 3B training.

Usage:
    python3 scripts/telegram_notify.py "Your message here"
    python3 scripts/telegram_notify.py "<b>Bold</b> message" --parse-mode HTML

Function API:
    from scripts.telegram_notify import send_telegram
    send_telegram("message text")
"""

import os
import sys
import json
import urllib.request
import urllib.parse
import urllib.error
import logging
from typing import Optional

# ─── Configuration ────────────────────────────────────────────────────────────
BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
CHAT_ID   = os.environ.get("TELEGRAM_CHAT_ID", "")
TIMEOUT   = 15  # seconds
MAX_MSG_LEN = 4096  # Telegram limit

logging.basicConfig(
    level=logging.WARNING,
    format="%(asctime)s [telegram_notify] %(levelname)s: %(message)s",
)
log = logging.getLogger("telegram_notify")


def send_telegram(
    message: str,
    parse_mode: str = "HTML",
    token: str = BOT_TOKEN,
    chat_id: str = CHAT_ID,
    disable_web_page_preview: bool = True,
) -> bool:
    """
    Send a Telegram message via Bot API using urllib (curl-free).

    Args:
        message:  Text to send (HTML or Markdown depending on parse_mode).
        parse_mode: "HTML" or "Markdown" or "" (plain).
        token:    Bot token (defaults to module-level BOT_TOKEN).
        chat_id:  Recipient chat/channel ID.
        disable_web_page_preview: Suppress link previews.

    Returns:
        True on success, False on any error.
    """
    if not message:
        log.warning("Empty message β€” skipping send.")
        return False

    # Truncate if over Telegram limit, with notice
    if len(message) > MAX_MSG_LEN:
        truncated_notice = "\n\n<i>[message truncated]</i>" if parse_mode == "HTML" else "\n\n[message truncated]"
        message = message[: MAX_MSG_LEN - len(truncated_notice)] + truncated_notice

    url = f"https://api.telegram.org/bot{token}/sendMessage"

    payload: dict = {
        "chat_id": chat_id,
        "text": message,
        "disable_web_page_preview": disable_web_page_preview,
    }
    if parse_mode:
        payload["parse_mode"] = parse_mode

    data = urllib.parse.urlencode(payload).encode("utf-8")

    try:
        req = urllib.request.Request(
            url,
            data=data,
            method="POST",
            headers={"Content-Type": "application/x-www-form-urlencoded"},
        )
        with urllib.request.urlopen(req, timeout=TIMEOUT) as resp:
            body = resp.read().decode("utf-8")
            result = json.loads(body)
            if result.get("ok"):
                return True
            else:
                log.error("Telegram API error: %s", result.get("description", result))
                return False

    except urllib.error.HTTPError as e:
        try:
            err_body = e.read().decode("utf-8")
        except Exception:
            err_body = str(e)
        log.error("HTTP %d from Telegram: %s", e.code, err_body)
        return False

    except urllib.error.URLError as e:
        log.error("Network error sending Telegram message: %s", e.reason)
        return False

    except json.JSONDecodeError as e:
        log.error("Failed to parse Telegram response: %s", e)
        return False

    except Exception as e:  # noqa: BLE001
        log.error("Unexpected error in send_telegram: %s", e)
        return False


def send_telegram_safe(message: str, **kwargs) -> bool:
    """
    Wrapper that catches ALL exceptions β€” guaranteed never to crash the caller.
    Suitable for embedding in training loops where stability is critical.
    """
    try:
        return send_telegram(message, **kwargs)
    except Exception as e:  # noqa: BLE001
        log.error("send_telegram_safe caught unhandled exception: %s", e)
        return False


# ─── CLI entry point ──────────────────────────────────────────────────────────
if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(
        description="Send a Telegram message from the command line."
    )
    parser.add_argument("message", nargs="?", help="Message text to send")
    parser.add_argument(
        "--parse-mode",
        default="HTML",
        choices=["HTML", "Markdown", "MarkdownV2", ""],
        help="Telegram parse_mode (default: HTML)",
    )
    parser.add_argument(
        "--token", default=BOT_TOKEN, help="Override bot token"
    )
    parser.add_argument(
        "--chat-id", default=CHAT_ID, help="Override chat ID"
    )
    args = parser.parse_args()

    # Allow piped stdin if no positional arg given
    if args.message is None:
        if not sys.stdin.isatty():
            args.message = sys.stdin.read().strip()
        else:
            parser.print_help()
            sys.exit(1)

    ok = send_telegram(
        args.message,
        parse_mode=args.parse_mode,
        token=args.token,
        chat_id=args.chat_id,
    )

    if ok:
        print("Telegram message sent successfully.")
        sys.exit(0)
    else:
        print("ERROR: Failed to send Telegram message.", file=sys.stderr)
        sys.exit(1)