#!/usr/bin/env python3 from __future__ import annotations import json import os import subprocess import sys from pathlib import Path from typing import Any SCRIPT_DIR = Path(__file__).resolve().parent GENERATE_SCRIPT = SCRIPT_DIR / "generate_table.py" ENV_PATH = Path.home() / ".openclaw" / ".env" class SendTableError(RuntimeError): pass def load_env_file(path: Path) -> dict[str, str]: values: dict[str, str] = {} if not path.exists(): return values for raw_line in path.read_text(encoding="utf-8").splitlines(): line = raw_line.strip() if not line or line.startswith("#") or "=" not in line: continue key, value = line.split("=", 1) values[key.strip()] = value.strip().strip('"').strip("'") return values def resolve_credentials(bot_token: str | None, chat_id: str | None) -> tuple[str, str]: env_values = load_env_file(ENV_PATH) token = bot_token or os.environ.get("BOT_TOKEN") or env_values.get("BOT_TOKEN") chat = chat_id or os.environ.get("CHAT_ID") or env_values.get("CHAT_ID") if not token: raise SendTableError("BOT_TOKEN is required (arg or ~/.openclaw/.env)") if not chat: raise SendTableError("CHAT_ID is required (arg or ~/.openclaw/.env)") return token, chat def parse_args(argv: list[str]) -> tuple[str, dict[str, Any], str | None, str | None]: if len(argv) < 2: raise SendTableError("Usage: ./send_table.py TITLE JSON [BOT_TOKEN] [CHAT_ID]") title = argv[0] raw_json = argv[1] bot_token = argv[2] if len(argv) >= 3 else None chat_id = argv[3] if len(argv) >= 4 else None try: payload = json.loads(raw_json) except json.JSONDecodeError as exc: raise SendTableError(f"Invalid JSON: {exc}") from exc if not isinstance(payload, dict): raise SendTableError("JSON payload must be an object") payload["title"] = title return title, payload, bot_token, chat_id def generate_png(payload: dict[str, Any]) -> Path: result = subprocess.run( [sys.executable, str(GENERATE_SCRIPT)], input=json.dumps(payload, ensure_ascii=False), text=True, capture_output=True, check=False, ) if result.returncode != 0: raise SendTableError(result.stderr.strip() or "generate_table.py failed") output = result.stdout.strip() if not output: raise SendTableError("generate_table.py did not return output path") path = Path(output) if not path.exists(): raise SendTableError(f"Generated file not found: {path}") return path def send_photo(bot_token: str, chat_id: str, title: str, image_path: Path) -> int: caption = title[:1024] url = f"https://api.telegram.org/bot{bot_token}/sendPhoto" command = [ "curl", "--silent", "--show-error", "--fail", "-X", "POST", url, "-F", f"chat_id={chat_id}", "-F", f"caption={caption}", "-F", f"photo=@{image_path}", ] result = subprocess.run(command, text=True, capture_output=True, check=False) if result.returncode != 0: raise SendTableError(result.stderr.strip() or "curl sendPhoto failed") try: response = json.loads(result.stdout) except json.JSONDecodeError as exc: raise SendTableError(f"Telegram API returned invalid JSON: {exc}") from exc if not response.get("ok"): raise SendTableError(f"Telegram API error: {response}") message_id = response.get("result", {}).get("message_id") if message_id is None: raise SendTableError("Telegram response missing message_id") return int(message_id) def main() -> int: try: title, payload, bot_token, chat_id = parse_args(sys.argv[1:]) token, chat = resolve_credentials(bot_token, chat_id) image_path = generate_png(payload) message_id = send_photo(token, chat, title, image_path) except SendTableError as exc: print(str(exc), file=sys.stderr) return 1 except Exception as exc: # pragma: no cover - defensive CLI handling print(f"Unexpected error: {exc}", file=sys.stderr) return 1 print(message_id) return 0 if __name__ == "__main__": raise SystemExit(main())