Download fb_comments/cli.py from bep40/fb-comments-crawler-space: direct link, hf CLI and curl.
- Browser
- Download file 5.93 kB
-
https://huggingface.co/spaces/bep40/fb-comments-crawler-space/resolve/main/fb_comments/cli.py
- Command line
-
hf download hf://spaces/bep40/fb-comments-crawler-space/fb_comments/cli.py
-
curl -L -o cli.py https://huggingface.co/spaces/bep40/fb-comments-crawler-space/resolve/main/fb_comments/cli.py
5.93 kB
| """Command-line entry point for the Facebook comment crawler (used by the Space's | |
| fb_comments package import chain; the Space UI itself uses crawler/export only). | |
| """ | |
| import argparse | |
| import json | |
| import os | |
| import sys | |
| from .crawler import CommentCrawler, CommentCrawlerError, RateLimitError | |
| from .export import ExportError, export, parse_post_identifier, resolve_post_id | |
| from .oauth import ( | |
| DEFAULT_SCOPE, | |
| OAuthError, | |
| exchange_code, | |
| exchange_long_lived, | |
| list_pages, | |
| ) | |
| def _cmd_login(args): | |
| """Interactive login: print a URL, capture the code, exchange to a page token.""" | |
| app_id = args.app_id or os.environ.get("FB_APP_ID") | |
| app_secret = args.app_secret or os.environ.get("FB_APP_SECRET") | |
| if not app_id or not app_secret: | |
| print("--app-id / --app-secret (or FB_APP_ID / FB_APP_SECRET) required for login.") | |
| sys.exit(2) | |
| from .oauth import login_url | |
| from .oauth import start_callback_server | |
| url, state = login_url(app_id, app_secret, DEFAULT_SCOPE.split(","), port=args.port) | |
| print("Open this URL in a browser and log in as the Page's admin:") | |
| print(" " + url) | |
| print("\nWaiting for the redirect to localhost ...") | |
| holder = start_callback_server(port=args.port, timeout=args.timeout) | |
| if holder.get("error"): | |
| sys.exit(f"Facebook login failed: {holder['error']}") | |
| if not holder.get("code"): | |
| sys.exit("No login code received (timed out).") | |
| if holder.get("state") != state: | |
| sys.exit("state mismatch - possible CSRF, aborting.") | |
| short = None | |
| try: | |
| data = exchange_code(app_id, app_secret, holder["code"], | |
| redirect_uri=f"http://localhost:{args.port}/callback") | |
| short = data["access_token"] | |
| except OAuthError as e: | |
| sys.exit(f"Code exchange failed: {e}") | |
| try: | |
| long_data = exchange_long_lived(app_id, app_secret, short) | |
| long_token = long_data["access_token"] | |
| except OAuthError as e: | |
| sys.exit(f"Long-lived exchange failed: {e}") | |
| pages = list_pages(long_token) | |
| print(f"\nUser has {len(pages)} managed Page(s):") | |
| for p in pages: | |
| print(f" - {p.get('name')} (id={p.get('id')})") | |
| if not pages: | |
| sys.exit("User manages no Pages; comments can only be read for pages you manage.") | |
| if len(pages) > 1 and not args.page: | |
| print("Multiple Pages — pass --page <name-or-id> to pick one.") | |
| sys.exit(2) | |
| page = pages[0] if len(pages) == 1 else next(p for p in pages if p.get("id") == args.page or p.get("name") == args.page) | |
| token = page["access_token"] | |
| out = { | |
| "app_id": app_id, | |
| "page_name": page.get("name"), | |
| "page_id": page.get("id"), | |
| "user_long_lived_token": long_token, | |
| "page_access_token": token, | |
| "token_type": "page", | |
| } | |
| if args.token_cache: | |
| with open(args.token_cache, "w", encoding="utf-8") as f: | |
| json.dump(out, f, indent=2, ensure_ascii=False) | |
| print(f"\n[OK] Saved token config to {args.token_cache}") | |
| print("Use: fbcomments crawl --token <page_access_token> --url <post-url> --csv out.csv") | |
| def _cmd_crawl(args): | |
| token = args.token or os.environ.get("FB_TOKEN") | |
| if not token: | |
| sys.exit("--token required (Page access token) or FB_TOKEN env var. Run 'login' first.") | |
| post_id = parse_post_identifier(args.url) | |
| crawler = CommentCrawler(token) | |
| print(f"Resolving post identifier: {post_id}") | |
| try: | |
| post_id = resolve_post_id(post_id, crawler) | |
| print(f"Using object id: {post_id}") | |
| except Exception: | |
| pass | |
| print("Crawling comments ...\n") | |
| try: | |
| comments = list(crawler.comments(post_id, include_replies=not args.no_replies)) | |
| except CommentCrawlerError as e: | |
| sys.exit(f"Crawl failed: {e}") | |
| print(f"Collected {len(comments)} comments") | |
| export(comments, out_csv=args.csv, out_json=args.json) | |
| def _cmd_list_tokens(args): | |
| token = args.token or os.environ.get("FB_TOKEN") | |
| if not token: | |
| sys.exit("--token required.") | |
| try: | |
| for p in list_pages(token): | |
| print(f"{p.get('name')}\t{p.get('id')}") | |
| except OAuthError as e: | |
| sys.exit(f"Error listing pages: {e}") | |
| def _build_parser(): | |
| parser = argparse.ArgumentParser( | |
| prog="fbcomments", | |
| description="Fetch 100% of comments (with replies) from Facebook posts you can read, with commenter UID export.", | |
| ) | |
| parser.add_argument("--version", action="store_true") | |
| sub = parser.add_subparsers(dest="cmd") | |
| p_login = sub.add_parser("login", help="OAuth login -> page access token") | |
| p_login.add_argument("--app-id", default=None) | |
| p_login.add_argument("--app-secret", default=None) | |
| p_login.add_argument("--port", type=int, default=8080) | |
| p_login.add_argument("--timeout", type=float, default=300) | |
| p_login.add_argument("--page", default=None) | |
| p_login.add_argument("--token-cache", default=None) | |
| p_crawl = sub.add_parser("crawl", help="Fetch comments for a post URL/id") | |
| p_crawl.add_argument("--token", default=None) | |
| p_crawl.add_argument("--url", required=True, help="Facebook post URL or id") | |
| p_crawl.add_argument("--csv", default=None) | |
| p_crawl.add_argument("--json", default=None) | |
| p_crawl.add_argument("--no-replies", action="store_true") | |
| p_tokens = sub.add_parser("tokens", help="List pages of the current token") | |
| p_tokens.add_argument("--token", default=None) | |
| return parser | |
| def main(argv=None): | |
| parser = _build_parser() | |
| args = parser.parse_args(argv) | |
| if args.version: | |
| from . import __version__ | |
| print(__version__) | |
| return | |
| if args.cmd == "login": | |
| _cmd_login(args) | |
| elif args.cmd == "crawl": | |
| _cmd_crawl(args) | |
| elif args.cmd == "tokens": | |
| _cmd_list_tokens(args) | |
| else: | |
| parser.print_help() | |
| if __name__ == "__main__": | |
| main() |