Files
wiki/skills/diagram-table/scripts/send_table.py
2026-04-12 21:55:33 +03:00

148 lines
4.2 KiB
Python
Executable File

#!/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())