#!/usr/bin/env python3 """ notion-apikeys - Query Notion 'API' database for API keys Usage: query.py Look up a service by name query.py --list List all entries query.py --find-db Discover database structure/properties query.py --setup Interactive setup (save token + db-id) """ import sys import json import os import argparse CONFIG_FILE = os.path.expanduser("~/.config/notion-apikeys.json") NOTION_VERSION = "2022-06-28" NOTION_BASE = "https://api.notion.com/v1" def get_headers(token): return { "Authorization": f"Bearer {token}", "Notion-Version": NOTION_VERSION, "Content-Type": "application/json", } def notion_request(method, path, token, payload=None): try: import urllib.request import urllib.error url = f"{NOTION_BASE}{path}" data = json.dumps(payload).encode() if payload is not None else None req = urllib.request.Request(url, data=data, headers=get_headers(token), method=method) with urllib.request.urlopen(req) as resp: return json.loads(resp.read()) except Exception as e: if hasattr(e, "read"): err = e.read().decode() try: err = json.loads(err) except Exception: pass print(f"Notion API error: {err}", file=sys.stderr) else: print(f"Error: {e}", file=sys.stderr) sys.exit(1) def find_database(token, name="API"): result = notion_request("POST", "/search", token, { "query": name, "filter": {"value": "database", "property": "object"}, "page_size": 20, }) dbs = result.get("results", []) for db in dbs: title_parts = db.get("title", []) db_name = "".join(t.get("plain_text", "") for t in title_parts).strip() if db_name.lower() == name.lower(): return db["id"], db_name, db.get("properties", {}) # If no exact match, return all found return None, None, dbs def query_database(token, db_id, filter_text=None): payload = {"page_size": 100} if filter_text: payload["filter"] = { "or": [ {"property": "Name", "title": {"contains": filter_text}}, ] } return notion_request("POST", f"/databases/{db_id}/query", token, payload) def extract_value(prop): ptype = prop.get("type", "") if ptype == "title": return "".join(t.get("plain_text", "") for t in prop.get("title", [])) elif ptype == "rich_text": return "".join(t.get("plain_text", "") for t in prop.get("rich_text", [])) elif ptype in ("url", "email", "phone_number"): return prop.get(ptype) or "" elif ptype == "select": s = prop.get("select") return s.get("name", "") if s else "" elif ptype == "multi_select": return ", ".join(s.get("name", "") for s in prop.get("multi_select", [])) elif ptype == "number": v = prop.get("number") return str(v) if v is not None else "" elif ptype == "checkbox": return "✓" if prop.get("checkbox") else "✗" elif ptype == "date": d = prop.get("date") return d.get("start", "") if d else "" return "" def load_config(): if os.path.exists(CONFIG_FILE): with open(CONFIG_FILE) as f: return json.load(f) return {} def save_config(config): os.makedirs(os.path.dirname(CONFIG_FILE), exist_ok=True) with open(CONFIG_FILE, "w") as f: json.dump(config, f, indent=2) def main(): parser = argparse.ArgumentParser(description="Notion API key lookup") parser.add_argument("service", nargs="?", help="Service name to look up") parser.add_argument("--list", action="store_true", help="List all entries") parser.add_argument("--find-db", action="store_true", help="Discover DB structure") parser.add_argument("--setup", action="store_true", help="Save token + db-id to config") parser.add_argument("--token", help="Notion token (overrides config/env)") parser.add_argument("--db-id", help="Notion database ID (overrides config)") args = parser.parse_args() config = load_config() token = args.token or os.environ.get("NOTION_TOKEN") or config.get("token") db_id = args.db_id or config.get("database_id") # Setup mode if args.setup: if not token: token = input("Notion Integration Token (secret_...): ").strip() if not db_id: print("Finding 'API' database automatically...") found_id, found_name, _ = find_database(token, "API") if found_id: print(f"Found: '{found_name}' (ID: {found_id})") db_id = found_id else: db_id = input("Database ID (or leave blank to auto-find each time): ").strip() config["token"] = token if db_id: config["database_id"] = db_id save_config(config) print(f"✓ Config saved to {CONFIG_FILE}") return if not token: print("ERROR: Notion token required.", file=sys.stderr) print(" Run: query.py --setup", file=sys.stderr) print(" Or set: NOTION_TOKEN env var", file=sys.stderr) sys.exit(1) # Auto-discover database if needed if not db_id: found_id, found_name, found_dbs = find_database(token, "API") if found_id: db_id = found_id else: print("ERROR: Could not find 'API' database.", file=sys.stderr) print(f"Found databases: {[d.get('id') for d in found_dbs]}", file=sys.stderr) sys.exit(1) # Find database structure if args.find_db: found_id, found_name, props = find_database(token, "API") if found_id: print(f"Database: '{found_name}' (ID: {found_id})") print("Properties:") for name, prop in props.items(): print(f" • {name} ({prop.get('type', '?')})") return # Query filter_text = args.service if not args.list else None result = query_database(token, db_id, filter_text) pages = result.get("results", []) if not pages: label = f" for '{args.service}'" if args.service else "" print(f"No entries found{label}.") return for page in pages: props = page.get("properties", {}) values = {k: extract_value(v) for k, v in props.items()} name = next((values[k] for k, v in props.items() if v.get("type") == "title"), "Unknown") print(f"\n── {name} ──") for k, val in values.items(): if val and props[k].get("type") != "title": # Mask long keys for security in list mode if args.list and len(val) > 20 and k.lower() in ("key", "api key", "token", "secret", "value"): val = val[:8] + "..." + val[-4:] print(f" {k}: {val}") if __name__ == "__main__": main()