diff --git a/playfox b/playfox new file mode 100755 index 0000000..8f68cb7 --- /dev/null +++ b/playfox @@ -0,0 +1,108 @@ +#!/usr/bin/env -S uv run --script +# /// script +# dependencies = ["playwright"] +# /// +""" +Headless Firefox with your cookies via cookiefire. + +Usage: + playfox https://twitter.com + playfox -P jtm https://private.site + playfox --interactive https://site.com +""" +import subprocess +import sys +from playwright.sync_api import sync_playwright + + +def parse_netscape_cookies(cookie_text): + """Convert cookiefire output to Playwright format.""" + cookies = [] + for line in cookie_text.strip().split("\n"): + if line.startswith("#") or not line.strip(): + continue + parts = line.split("\t") + if len(parts) >= 7: + domain, _, path, secure, expires, name, value = parts[:7] + exp = float(expires) + if exp <= 0: + exp = -1 + elif exp > 9999999999: + exp = exp / 1000 + cookies.append( + { + "name": name, + "value": value, + "domain": domain, + "path": path, + "expires": exp, + "httpOnly": False, + "secure": secure.upper() == "TRUE", + "sameSite": "Lax", + } + ) + return cookies + + +def main(): + args = sys.argv[1:] + profile = "jtm" + url = None + dump = False + interactive = False + + i = 0 + while i < len(args): + if args[i] == "-P" and i + 1 < len(args): + profile = args[i + 1] + i += 2 + elif args[i] == "--dump": + dump = True + i += 1 + elif args[i] == "--interactive" or args[i] == "-i": + interactive = True + i += 1 + elif args[i].startswith("http"): + url = args[i] + i += 1 + else: + i += 1 + + if not url: + print("Usage: playfox [-P profile] [--dump] [--interactive] URL", file=sys.stderr) + sys.exit(1) + + # get cookies + cookie_text = subprocess.check_output(["cookiefire", profile], text=True) + cookies = parse_netscape_cookies(cookie_text) + + with sync_playwright() as p: + browser = p.firefox.launch(headless=True) + context = browser.new_context() + context.add_cookies(cookies) + + page = context.new_page() + + # logging + page.on("console", lambda msg: print(f"[console] {msg.text}")) + page.on("pageerror", lambda err: print(f"[error] {err}")) + page.on("request", lambda req: print(f"[req] {req.method} {req.url}")) + + page.goto(url) + + if dump: + print(page.content()) + + if interactive: + print("\n--- interactive mode ---") + print("page object available as 'page'") + print("ctrl-d to exit\n") + import code + + code.interact(local={"page": page, "context": context, "browser": browser}) + + browser.close() + + +if __name__ == "__main__": + main()