Initial backup 2026-02-17
This commit is contained in:
199
skills/notion-apikeys/scripts/query.py
Normal file
199
skills/notion-apikeys/scripts/query.py
Normal file
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
notion-apikeys - Query Notion 'API' database for API keys
|
||||
Usage:
|
||||
query.py <service_name> 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()
|
||||
Reference in New Issue
Block a user