- check-session.py: preflight in gdl-cron.sh. No unexpired sessionid in the Chrome profile gdl reads -> abort before any request, ping /76 (red check). - stories-streak.py: postflight. Every fetched stories source empty for 3 consecutive days -> exit 77, for a session Instagram stopped honouring while the cookie is still present. The 2026-10-07 session death went unnoticed for a week because every run still exited 0. - gdl-sync.py: [n/N] source headers, a 60s heartbeat with new-file counts, and per-source timing, so a healthy paced run no longer looks hung. - gdl-sync@.service now runs run-%i.sh instead of gdl-cron.sh: HC_URL is set in the wrappers, so scheduled runs had never pinged Healthchecks. - Tests for both guards (offline) and the progress helpers.
71 lines
2.7 KiB
Python
Executable File
71 lines
2.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Preflight for gdl-cron.sh: is there a usable Instagram login in the cookie jar gdl reads?
|
|
|
|
gdl-sync.py runs gallery-dl with `--cookies-from-browser chrome:<profile>`, so what matters
|
|
is not what the interactive Chrome window LOOKS like (an open tab keeps showing its logged-in
|
|
UI long after the session died -- found 2026-10-14, stories had been empty for a week) but
|
|
whether the profile's cookie DB holds a `sessionid`. This reads the same DB, the same way.
|
|
|
|
Never prints cookie values -- only the name, expiry, and a verdict.
|
|
|
|
Exit 0 logged in (a non-expired sessionid exists)
|
|
Exit 1 logged out / unreadable -- the caller must not scrape
|
|
"""
|
|
import datetime as dt
|
|
import os
|
|
import shutil
|
|
import sqlite3
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
from pathlib import Path
|
|
|
|
PROFILE = Path(os.environ.get("GDL_COOKIE_PROFILE",
|
|
str(Path.home() / ".config/google-chrome-devtools")))
|
|
WARN_DAYS = 7
|
|
|
|
|
|
def main() -> int:
|
|
src = PROFILE / "Default" / "Cookies"
|
|
if not src.exists():
|
|
print(f"no cookie DB at {src} -- Chrome profile missing?")
|
|
return 1
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
# Copy first: Chrome holds the live DB open, and a copy keeps this read-only.
|
|
for suffix in ("", "-journal", "-wal", "-shm"):
|
|
f = Path(str(src) + suffix)
|
|
if f.exists():
|
|
shutil.copy2(f, Path(tmp) / f.name)
|
|
try:
|
|
con = sqlite3.connect(str(Path(tmp) / "Cookies"))
|
|
rows = con.execute(
|
|
"SELECT expires_utc, has_expires FROM cookies "
|
|
"WHERE host_key LIKE '%instagram.com' AND name = 'sessionid'").fetchall()
|
|
except sqlite3.Error as exc:
|
|
print(f"cannot read cookie DB: {exc}")
|
|
return 1
|
|
if not rows:
|
|
print("NOT LOGGED IN: no Instagram sessionid in the Chrome profile "
|
|
f"{PROFILE}. Log in to Instagram in that Chrome window (the open tab can "
|
|
"look logged in while stale -- reload it), then re-run.")
|
|
return 1
|
|
expires, has_expires = rows[0]
|
|
if has_expires:
|
|
exp = dt.datetime.fromtimestamp(expires / 1_000_000 - 11644473600, dt.timezone.utc)
|
|
left = (exp.timestamp() - time.time()) / 86400
|
|
if left <= 0:
|
|
print(f"NOT LOGGED IN: the Instagram sessionid expired {exp:%Y-%m-%d}.")
|
|
return 1
|
|
note = f"expires {exp:%Y-%m-%d}"
|
|
if left < WARN_DAYS:
|
|
note += f" (WARNING: only {left:.1f} days left)"
|
|
print(f"session ok: sessionid present, {note}")
|
|
else:
|
|
print("session ok: sessionid present (session cookie, no expiry)")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|