98 lines
2.7 KiB
Text
Executable file
98 lines
2.7 KiB
Text
Executable file
#!/usr/bin/env -S uv run --script
|
|
# /// script
|
|
# dependencies = []
|
|
# ///
|
|
"""
|
|
shot-scraper wrapper that uses Firefox cookies via cookiefire.
|
|
|
|
Usage:
|
|
shotscraper https://example.com -o out.png
|
|
shotscraper -P jtm https://private.com -o private.png
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
|
|
def parse_netscape_cookies(cookie_text):
|
|
"""Convert Netscape cookie format to Playwright storage_state 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]
|
|
# playwright requires -1 (session) or positive unix timestamp in seconds
|
|
# firefox stores milliseconds, so divide by 1000
|
|
exp = float(expires)
|
|
if exp <= 0:
|
|
exp = -1
|
|
elif exp > 9999999999: # looks like milliseconds
|
|
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": cookies, "origins": []}
|
|
|
|
|
|
def main():
|
|
args = sys.argv[1:]
|
|
profile = "jtm"
|
|
passthrough = []
|
|
|
|
# extract -P profile from args
|
|
i = 0
|
|
while i < len(args):
|
|
if args[i] == "-P" and i + 1 < len(args):
|
|
next_arg = args[i + 1]
|
|
# don't treat URLs as profile names
|
|
if next_arg.startswith("http://") or next_arg.startswith("https://"):
|
|
passthrough.append(next_arg)
|
|
i += 2
|
|
else:
|
|
profile = next_arg
|
|
i += 2
|
|
else:
|
|
passthrough.append(args[i])
|
|
i += 1
|
|
|
|
# get cookies from firefox
|
|
try:
|
|
cookie_text = subprocess.check_output(
|
|
["cookiefire", profile],
|
|
text=True
|
|
)
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"cookiefire failed: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
storage_state = parse_netscape_cookies(cookie_text)
|
|
|
|
# write to temp file
|
|
with tempfile.NamedTemporaryFile(
|
|
mode='w', suffix='.json', delete=False
|
|
) as f:
|
|
json.dump(storage_state, f)
|
|
auth_file = f.name
|
|
|
|
try:
|
|
cmd = ["shot-scraper", "--auth", auth_file] + passthrough
|
|
result = subprocess.run(cmd)
|
|
sys.exit(result.returncode)
|
|
finally:
|
|
os.unlink(auth_file)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|