Ctrl K
ComunidadGuías…

scripts/seed_bookmarks.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env python3
"""Seed bookmarks via the Fusion HTTP API for manual testing.

Collects item IDs by paginating GET /api/items, then creates a bookmark
for each via POST /api/bookmarks with {"item_id": <id>}. Re-running is
safe: items already bookmarked are skipped (the server rejects duplicate
links with HTTP 500 due to the UNIQUE constraint on bookmarks.link).

Examples:
    # 100 bookmarks, anonymous (no password configured on server)
    python3 scripts/seed_bookmarks.py

    # 50 bookmarks against a password-protected server
    python3 scripts/seed_bookmarks.py --password secret --count 50

Flags may also be set via env vars: FUSION_URL, FUSION_PASSWORD.
"""

import argparse
import http.cookiejar
import json
import os
import sys
import time
import urllib.error
import urllib.parse
import urllib.request

DEFAULT_URL = "http://localhost:8080"
PAGE_SIZE = 100  # mirrors the server's maxListLimit


class FusionClient:
    """Thin HTTP client that persists the session cookie automatically."""

    def __init__(self, base_url):
        self.base_url = base_url.rstrip("/")
        self.cookiejar = http.cookiejar.CookieJar()
        self.opener = urllib.request.build_opener(
            urllib.request.HTTPCookieProcessor(self.cookiejar)
        )

    def request(self, method, path, body=None):
        url = self.base_url + path
        data = None
        headers = {"Accept": "application/json"}
        if body is not None:
            data = json.dumps(body).encode("utf-8")
            headers["Content-Type"] = "application/json"
        req = urllib.request.Request(url, data=data, headers=headers, method=method)
        try:
            with self.opener.open(req) as resp:
                raw = resp.read()
                return resp.status, (json.loads(raw) if raw else None)
        except urllib.error.HTTPError as e:
            raw = e.read().decode("utf-8", "replace")
            try:
                return e.code, json.loads(raw)
            except ValueError:
                return e.code, {"error": raw}
        except urllib.error.URLError as e:
            sys.exit(f"error: cannot reach {url}: {e.reason}")

    def login(self, password):
        status, body = self.request("POST", "/api/sessions", {"password": password})
        if status != 200:
            sys.exit(f"error: login failed (HTTP {status}): {body}")
        print("login: ok", file=sys.stderr)

    def list_items_page(self, limit, before=None):
        params = [("limit", str(limit))]
        if before:
            params.append(("before", before))
        status, body = self.request(
            "GET", "/api/items?" + urllib.parse.urlencode(params)
        )
        if status != 200:
            sys.exit(f"error: list items failed (HTTP {status}): {body}")
        return body

    def create_bookmark(self, item_id):
        return self.request("POST", "/api/bookmarks", {"item_id": item_id})


def collect_item_ids(client, count):
    """Page through GET /api/items until we have `count` IDs (or run out)."""
    ids = []
    cursor = None
    while len(ids) < count:
        page = client.list_items_page(PAGE_SIZE, before=cursor)
        data = page.get("data") or []
        if not data:
            break
        ids.extend(item["id"] for item in data)
        cursor = page.get("next_cursor")
        if not cursor:
            break
    return ids[:count]


def main():
    parser = argparse.ArgumentParser(description="Seed bookmarks for manual testing.")
    parser.add_argument(
        "--url",
        default=os.environ.get("FUSION_URL", DEFAULT_URL),
        help=f"Fusion base URL (default: {DEFAULT_URL} or $FUSION_URL)",
    )
    parser.add_argument(
        "--password",
        default=os.environ.get("FUSION_PASSWORD", ""),
        help="login password; omit for anonymous access (env: FUSION_PASSWORD)",
    )
    parser.add_argument(
        "--count",
        type=int,
        default=100,
        help="number of bookmarks to create (default: 100)",
    )
    parser.add_argument(
        "--sleep",
        type=float,
        default=0.0,
        help="seconds to wait between bookmark requests (default: 0)",
    )
    args = parser.parse_args()

    client = FusionClient(args.url)
    if args.password:
        client.login(args.password)

    ids = collect_item_ids(client, args.count)
    if not ids:
        print(
            "no items found; add some feeds and fetch items first.",
            file=sys.stderr,
        )
        return
    if len(ids) < args.count:
        print(
            f"warning: only {len(ids)} items available (requested {args.count}).",
            file=sys.stderr,
        )

    created = skipped = failed = 0
    for i, item_id in enumerate(ids, 1):
        status, _ = client.create_bookmark(item_id)
        if status == 200:
            created += 1
        elif status == 500:
            # UNIQUE(link) conflict => item is already bookmarked
            skipped += 1
        else:
            failed += 1
            print(f"  item {item_id}: unexpected HTTP {status}", file=sys.stderr)
        if i % 10 == 0 or i == len(ids):
            print(f"  progress: {i}/{len(ids)}", file=sys.stderr)
        if args.sleep:
            time.sleep(args.sleep)

    print(
        f"done: created={created}, skipped(already)={skipped}, failed={failed}",
        file=sys.stderr,
    )


if __name__ == "__main__":
    main()