#!/usr/bin/env python3 """Call Bookmarks API with a Bearer token from FAB_TOKEN or a file.""" from __future__ import annotations import argparse import json import os import sys import urllib.error import urllib.request from pathlib import Path from typing import Any DEFAULT_BASE = os.environ.get("FAB_URL", "http://127.0.0.1:17634") def token_value(explicit: str | None = None) -> str: if explicit: return explicit.strip() env = os.environ.get("FAB_TOKEN") if env: return env.strip() path = os.environ.get("FAB_TOKEN_FILE") if path: return Path(path).expanduser().read_text(encoding="utf-8").strip() raise SystemExit( "no token: set FAB_TOKEN, or FAB_TOKEN_FILE, or pass --token. " "Generate one from the add-on toolbar → Manage clients." ) def call( method: str, args: list[Any] | None = None, base: str = DEFAULT_BASE, token: str | None = None, ) -> Any: payload = json.dumps({"method": method, "args": args or []}).encode("utf-8") req = urllib.request.Request( base.rstrip("/") + "/v1/call", data=payload, method="POST", headers={ "Authorization": f"Bearer {token_value(token)}", "Content-Type": "application/json", }, ) try: with urllib.request.urlopen(req, timeout=20) as resp: body = json.loads(resp.read().decode("utf-8")) except urllib.error.HTTPError as exc: detail = exc.read().decode("utf-8", errors="replace") raise SystemExit(f"bridge HTTP {exc.code}: {detail}") from exc except urllib.error.URLError as exc: raise SystemExit( f"bridge unreachable at {base} ({exc}). " "Firefox must be open with the extension loaded." ) from exc if not body.get("ok"): raise SystemExit(body.get("error") or body) return body.get("result") def main() -> int: parser = argparse.ArgumentParser(description="Call Bookmarks API") parser.add_argument("method", help="e.g. bookmarks.search or meta.methods") parser.add_argument("args_json", nargs="?", default="[]", help="JSON array of arguments") parser.add_argument("--url", default=DEFAULT_BASE) parser.add_argument("--token", help="override FAB_TOKEN") ns = parser.parse_args() args = json.loads(ns.args_json) if not isinstance(args, list): raise SystemExit("args_json must be a JSON array") print(json.dumps(call(ns.method, args, base=ns.url, token=ns.token), indent=2)) return 0 if __name__ == "__main__": raise SystemExit(main())