115 lines
4.0 KiB
Python
115 lines
4.0 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
SRC_ROOT = REPO_ROOT / "src"
|
|
OUTPUT_ROOT = REPO_ROOT / "outputs"
|
|
FRESHRSS_OUTPUT_ROOT = OUTPUT_ROOT / "freshrss"
|
|
|
|
if str(SRC_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(SRC_ROOT))
|
|
|
|
from summary_mcp.integrations.freshrss import READ_TAG, FreshRSSClient, map_entry_to_item
|
|
|
|
|
|
def _save_json(path: Path, payload: dict[str, Any] | list[Any]) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
|
|
|
|
def _load_required(name: str, value: str | None) -> str:
|
|
resolved = value or os.environ.get(name)
|
|
if not resolved:
|
|
cli_name = name.lower().replace("_", "-")
|
|
raise RuntimeError(f"Missing required value: pass --{cli_name} or set {name}.")
|
|
return resolved
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Pull FreshRSS entries and map them into normalized item objects.")
|
|
parser.add_argument("--api-base-url", type=str, default=None, help="FreshRSS greader API base URL")
|
|
parser.add_argument("--username", type=str, default=None, help="FreshRSS API username")
|
|
parser.add_argument("--api-password", type=str, default=None, help="FreshRSS API password")
|
|
parser.add_argument(
|
|
"--stream-id",
|
|
type=str,
|
|
default="user/-/state/com.google/reading-list",
|
|
help="Google Reader API stream id",
|
|
)
|
|
parser.add_argument("--limit", type=int, default=10, help="Maximum number of entries to request")
|
|
parser.add_argument("--continuation", type=str, default=None, help="Continuation token for paging")
|
|
parser.add_argument(
|
|
"--include-read",
|
|
action="store_true",
|
|
help="Do not exclude items already tagged as read.",
|
|
)
|
|
parser.add_argument(
|
|
"--mark-read",
|
|
action="store_true",
|
|
help="Mark fetched entries as read after this script finishes successfully.",
|
|
)
|
|
parser.add_argument(
|
|
"--raw-output",
|
|
type=Path,
|
|
default=FRESHRSS_OUTPUT_ROOT / "raw" / "freshrss.raw.json",
|
|
help="Where to save the raw FreshRSS response",
|
|
)
|
|
parser.add_argument(
|
|
"--items-output",
|
|
type=Path,
|
|
default=FRESHRSS_OUTPUT_ROOT / "items" / "freshrss.items.json",
|
|
help="Where to save the mapped item list",
|
|
)
|
|
parser.add_argument("--timeout", type=float, default=20.0, help="Request timeout in seconds")
|
|
args = parser.parse_args()
|
|
|
|
api_base_url = _load_required("FRESHRSS_API_BASE_URL", args.api_base_url)
|
|
username = _load_required("FRESHRSS_USERNAME", args.username)
|
|
api_password = _load_required("FRESHRSS_API_PASSWORD", args.api_password)
|
|
|
|
client = FreshRSSClient(
|
|
api_base_url=api_base_url,
|
|
username=username,
|
|
api_password=api_password,
|
|
timeout_seconds=args.timeout,
|
|
)
|
|
auth_token = client.client_login()
|
|
payload = client.fetch_stream_contents(
|
|
auth_token=auth_token,
|
|
stream_id=args.stream_id,
|
|
limit=args.limit,
|
|
continuation=args.continuation,
|
|
exclude_targets=[] if args.include_read else [READ_TAG],
|
|
)
|
|
|
|
entries = payload.get("items")
|
|
if not isinstance(entries, list):
|
|
raise RuntimeError("FreshRSS stream response does not contain an items array.")
|
|
|
|
mapped_items = [map_entry_to_item(entry).model_dump(mode="json") for entry in entries]
|
|
|
|
_save_json(args.raw_output, payload)
|
|
_save_json(args.items_output, mapped_items)
|
|
|
|
marked_count = 0
|
|
if args.mark_read:
|
|
item_ids = [entry.get("external_id") for entry in mapped_items if isinstance(entry.get("external_id"), str)]
|
|
if item_ids:
|
|
client.mark_items_as_read(auth_token=auth_token, item_ids=item_ids)
|
|
marked_count = len(item_ids)
|
|
|
|
print(f"Saved {len(mapped_items)} mapped items to {args.items_output}")
|
|
if args.mark_read:
|
|
print(f"Marked {marked_count} FreshRSS entries as read")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|