#!/usr/bin/env python3 import argparse, json, socket, ssl, sys, time from pathlib import Path from urllib import request as ureq, parse as uparse HOST = "api.telegram.org" def read_secret(v: str, base: Path) -> str: if isinstance(v, str) and v.startswith("file:"): p = (base / v[5:]).resolve() return p.read_text(encoding="utf-8").strip() return str(v) def load_tg(cfg_path: Path): cfg = json.loads(cfg_path.read_text(encoding="utf-8")) tg = (((cfg.get("sweep") or {}).get("notify") or {}).get("telegram") or {}) if not tg or not tg.get("enabled"): sys.exit("[no-op] Telegram disabled in grid.json") token = read_secret(tg["token"], cfg_path.parent) chat = read_secret(tg["chat_id"], cfg_path.parent) quiet = bool(tg.get("quiet", True)) if not token or not chat: sys.exit("[err] token or chat_id missing/empty") return token, chat, quiet def dns_lookup(host): try: infos = socket.getaddrinfo(host, 443, 0, socket.SOCK_STREAM) A, AAAA = [], [] for af, _, _, _, sa in infos: ip = sa[0] (A if af == socket.AF_INET else AAAA).append(ip) # de-dup preserve order A2 = [] for ip in A: if ip not in A2: A2.append(ip) AAAA2 = [] for ip in AAAA: if ip not in AAAA2: AAAA2.append(ip) return A2, AAAA2, None except Exception as e: return [], [], str(e) def tcp_probe(ip, timeout=3): af = socket.AF_INET6 if ":" in ip else socket.AF_INET s = socket.socket(af, socket.SOCK_STREAM) s.settimeout(timeout) try: t0 = time.time() s.connect((ip, 443)) return True, f"TCP OK {ip} in {1000*(time.time()-t0):.0f} ms" except Exception as e: return False, f"TCP FAIL {ip}: {e}" finally: try: s.close() except: pass def tls_probe(ip, server_name=HOST, timeout=5): af = socket.AF_INET6 if ":" in ip else socket.AF_INET s = socket.socket(af, socket.SOCK_STREAM) s.settimeout(timeout) ctx = ssl.create_default_context() try: s.connect((ip, 443)) t0 = time.time() tls = ctx.wrap_socket(s, server_hostname=server_name) # send a minimal HEAD to see HTTP response req = f"HEAD / HTTP/1.1\r\nHost: {server_name}\r\nConnection: close\r\n\r\n".encode() tls.sendall(req) _ = tls.recv(1) # any byte indicates success return True, f"TLS OK {ip} in {1000*(time.time()-t0):.0f} ms" except Exception as e: return False, f"TLS FAIL {ip}: {e}" finally: try: tls.close() except: pass try: s.close() except: pass def send_text_normal(token, chat, text, quiet=True, timeout=6): url = f"https://{HOST}/bot{token}/sendMessage" data = uparse.urlencode({ "chat_id": chat, "text": text, "parse_mode": "HTML", "disable_web_page_preview": "true", "disable_notification": "true" if quiet else "false", }).encode() try: with ureq.urlopen(ureq.Request(url, data=data, method="POST"), timeout=timeout) as r: return True, r.read().decode("utf-8","ignore")[:200] except Exception as e: return False, str(e) def send_text_via_ip(token, chat, text, ip, quiet=True, timeout=6): # HTTPS POST to specific IP with SNI+Host=api.telegram.org af = socket.AF_INET6 if ":" in ip else socket.AF_INET s = socket.socket(af, socket.SOCK_STREAM) s.settimeout(timeout) ctx = ssl.create_default_context() body = uparse.urlencode({ "chat_id": chat, "text": text, "parse_mode": "HTML", "disable_web_page_preview": "true", "disable_notification": "true" if quiet else "false", }).encode() try: s.connect((ip, 443)) tls = ctx.wrap_socket(s, server_hostname=HOST) req = ( f"POST /bot{token}/sendMessage HTTP/1.1\r\n" f"Host: {HOST}\r\n" f"Content-Type: application/x-www-form-urlencoded\r\n" f"Content-Length: {len(body)}\r\n" f"Connection: close\r\n\r\n" ).encode() + body tls.sendall(req) buf = b"" while True: chunk = tls.recv(4096) if not chunk: break buf += chunk # crude success check return (b'"ok":true' in buf), buf.decode("utf-8","ignore")[:300] except Exception as e: return False, str(e) finally: try: tls.close() except: pass try: s.close() except: pass def main(): ap = argparse.ArgumentParser() ap.add_argument("--grid", default="grid.json") ap.add_argument("--force-ipv4-send", action="store_true", help="send via first A record instead of normal resolver") args = ap.parse_args() token, chat, quiet = load_tg(Path(args.grid)) print(f"[info] loading secrets from {args.grid}") A, AAAA, err = dns_lookup(HOST) if err: print(f"[dns] lookup error: {err}") print(f"[dns] A: {A or '—'}") print(f"[dns] AAAA: {AAAA or '—'}") # quick probes for ip in (A + AAAA): ok, msg = tcp_probe(ip) print(f"[probe] {msg}") ok2, msg2 = tls_probe(ip) print(f"[probe] {msg2}") text = "🔔 Telegram diagnostic ping\nmode: " + ("IPv4-forced" if args.force_ipv4_send else "normal") if args.force_ipv4_send: if not A: print("[send] no A record found to force IPv4") sys.exit(2) ok, msg = send_text_via_ip(token, chat, text, A[0], quiet=quiet) else: ok, msg = send_text_normal(token, chat, text, quiet=quiet) print(f"[send] {'OK' if ok else 'FAIL'}: {msg}") if __name__ == "__main__": main()