Add shotscraper script
This commit is contained in:
parent
eca8876d09
commit
3c1a347fa3
1 changed files with 98 additions and 0 deletions
98
shotscraper
Executable file
98
shotscraper
Executable file
|
|
@ -0,0 +1,98 @@
|
||||||
|
#!/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()
|
||||||
Loading…
Reference in a new issue