#!/usr/bin/env python3 from __future__ import annotations import argparse from download_happyfappy_torrent import run as run_happyfappy_download from scrape_happyfappy_bookmarks import run as run_happyfappy_bookmarks SITE_ALIASES = { "happyfappy": "happyfappy", "hf": "happyfappy", } ACTION_ALIASES = { "get-bookmarks": "get-bookmarks", "gb": "get-bookmarks", "bookmarks": "get-bookmarks", "download-torrent-files": "download-torrent-files", "dtf": "download-torrent-files", "download": "download-torrent-files", } def normalize_site(value: str) -> str: key = value.strip().lower() if key not in SITE_ALIASES: supported = ", ".join(sorted(SITE_ALIASES)) raise ValueError(f"Unsupported site: {value!r}. Supported values: {supported}") return SITE_ALIASES[key] def normalize_action(value: str) -> str: key = value.strip().lower() if key not in ACTION_ALIASES: supported = ", ".join(sorted(ACTION_ALIASES)) raise ValueError(f"Unsupported action: {value!r}. Supported values: {supported}") return ACTION_ALIASES[key] def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( description="wscraper: multi-site scraping entrypoint", ) parser.add_argument("site", help="Site key, e.g. happyfappy or hf") parser.add_argument("-a", "--action", required=True, help="Action to run") parser.add_argument("--base-url", help="Override site base URL") parser.add_argument("--cookie", help='Raw cookie string, e.g. "a=1; b=2"') parser.add_argument("-c", "--cookie-file", help="Path to cookie file") parser.add_argument("-u", "--url", help="Detail page URL (required for download action)") parser.add_argument( "-o", "--output", help="Output target: file path for get-bookmarks, directory path for download-torrent-files", ) parser.add_argument("-r", "--retries", type=int, default=3) parser.add_argument("--backoff-base", type=float, default=5.0) parser.add_argument("--delay-min", type=float, default=1.8) parser.add_argument("--delay-max", type=float, default=3.2) parser.add_argument("--max-pages", type=int, default=200) return parser def run_happyfappy(args: argparse.Namespace, action: str) -> None: base_url = args.base_url or "https://www.happyfappy.net" if action == "get-bookmarks": bookmarks_args = argparse.Namespace( base_url=base_url, cookie=args.cookie, cookie_file=args.cookie_file, output=args.output or "bookmarks.json", delay_min=args.delay_min, delay_max=args.delay_max, retries=args.retries, backoff_base=args.backoff_base, max_pages=args.max_pages, ) run_happyfappy_bookmarks(bookmarks_args) return if action == "download-torrent-files": if not args.url: raise ValueError("--url is required for action=download-torrent-files.") download_args = argparse.Namespace( url=args.url, base_url=base_url, cookie=args.cookie, cookie_file=args.cookie_file, output_dir=args.output or "torrent", retries=args.retries, backoff_base=args.backoff_base, ) run_happyfappy_download(download_args) return raise ValueError(f"Unsupported action for happyfappy: {action}") def main() -> None: parser = build_parser() args = parser.parse_args() if args.retries < 1: raise ValueError("--retries must be at least 1.") if args.backoff_base < 0: raise ValueError("--backoff-base must be >= 0.") if args.delay_min < 0 or args.delay_max < 0: raise ValueError("Delay values must be non-negative.") if args.delay_min > args.delay_max: raise ValueError("--delay-min cannot be greater than --delay-max.") site = normalize_site(args.site) action = normalize_action(args.action) if not args.cookie and not args.cookie_file: raise ValueError("Cookie is required. Use --cookie or --cookie-file/-c.") if site == "happyfappy": run_happyfappy(args, action) return raise ValueError(f"Unsupported site: {site}") if __name__ == "__main__": main()