Add app auth, dashboard, scheduler, video management, and new scrapers
- JWT-based app authentication with user roles, folder/route access control - Dashboard with storage stats, health checks, and recent activity - Auto-download/scrape scheduler (12h interval) with per-user and per-job configs - Video upload, tagging, HLS transcoding, and detail pages - New scrapers: LeakGallery, Mega (megajs), yt-dlp - FlareSolverr integration for Cloudflare-protected sites - Gallery: advanced filtering (date, size, search), sort modes, equal-mix shuffle - Forum sites management with stored cookies/auth - GridWall/GridCell components for responsive media grid - Media API with folder-access permissions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Login helper using undetected_chromedriver to bypass Cloudflare Turnstile.
|
||||
Runs Chrome in headed mode with Xvfb (virtual display) so Turnstile sees a real browser.
|
||||
|
||||
Usage:
|
||||
xvfb-run python3 login_helper.py <login_url> <username> <password>
|
||||
|
||||
Outputs JSON to stdout:
|
||||
{"ok": true, "cookies": "name=val; name2=val2", "url": "<final_url>"}
|
||||
{"ok": false, "error": "reason"}
|
||||
"""
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
import shutil
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 4:
|
||||
print(json.dumps({"ok": False, "error": "Usage: login_helper.py <login_url> <username> <password>"}))
|
||||
sys.exit(1)
|
||||
|
||||
login_url = sys.argv[1]
|
||||
username = sys.argv[2]
|
||||
password = sys.argv[3]
|
||||
|
||||
try:
|
||||
import undetected_chromedriver as uc
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
except ImportError as e:
|
||||
print(json.dumps({"ok": False, "error": f"Missing dependency: {e}"}))
|
||||
sys.exit(1)
|
||||
|
||||
driver = None
|
||||
try:
|
||||
# Find chromium binary
|
||||
chromium_path = os.environ.get('CHROMIUM_PATH', '')
|
||||
if not chromium_path or not os.path.exists(chromium_path):
|
||||
for p in ['/usr/bin/chromium-browser', '/usr/bin/chromium', '/usr/lib/chromium/chromium']:
|
||||
if os.path.exists(p):
|
||||
chromium_path = p
|
||||
break
|
||||
|
||||
# Find system chromedriver (Alpine: chromium-chromedriver package)
|
||||
chromedriver_path = None
|
||||
for p in ['/usr/bin/chromedriver', '/usr/lib/chromium/chromedriver']:
|
||||
if os.path.exists(p):
|
||||
chromedriver_path = p
|
||||
break
|
||||
|
||||
# Get chromium version for undetected_chromedriver
|
||||
version_main = None
|
||||
try:
|
||||
import subprocess
|
||||
result = subprocess.run([chromium_path, '--version'], capture_output=True, text=True, timeout=5)
|
||||
# e.g. "Chromium 131.0.6778.139" or "Chromium 131.0.6778.139 Alpine Linux"
|
||||
parts = result.stdout.strip().split()
|
||||
ver_str = None
|
||||
for part in parts:
|
||||
if '.' in part and part[0].isdigit():
|
||||
ver_str = part
|
||||
break
|
||||
if ver_str:
|
||||
version_main = int(ver_str.split('.')[0])
|
||||
log(f"Chromium version: {ver_str} (major: {version_main})")
|
||||
except Exception as e:
|
||||
log(f"Could not detect chromium version: {e}")
|
||||
|
||||
options = uc.ChromeOptions()
|
||||
options.binary_location = chromium_path
|
||||
options.add_argument('--no-sandbox')
|
||||
options.add_argument('--disable-dev-shm-usage')
|
||||
options.add_argument('--disable-gpu')
|
||||
options.add_argument('--window-size=1920,1080')
|
||||
|
||||
log(f"Chromium: {chromium_path}")
|
||||
log(f"Chromedriver: {chromedriver_path}")
|
||||
|
||||
# Create the driver
|
||||
# Use system chromedriver to avoid downloading (fails on Alpine/musl)
|
||||
driver = uc.Chrome(
|
||||
options=options,
|
||||
driver_executable_path=chromedriver_path,
|
||||
headless=False,
|
||||
version_main=version_main,
|
||||
)
|
||||
driver.set_window_size(1920, 1080)
|
||||
|
||||
log(f"Navigating to {login_url}...")
|
||||
driver.get(login_url)
|
||||
|
||||
# Wait for DDoS-Guard to solve and login form to appear
|
||||
log("Waiting for login form (DDoS-Guard solving)...")
|
||||
WebDriverWait(driver, 60).until(
|
||||
EC.presence_of_element_located((By.CSS_SELECTOR, 'input[name="login"]'))
|
||||
)
|
||||
log("Login form found")
|
||||
|
||||
# Wait for Turnstile to auto-solve (should work in undetected headed mode)
|
||||
log("Waiting for Turnstile to solve...")
|
||||
turnstile_token = ""
|
||||
for i in range(45):
|
||||
try:
|
||||
el = driver.find_element(By.CSS_SELECTOR, 'input[name="cf-turnstile-response"]')
|
||||
val = el.get_attribute("value")
|
||||
if val:
|
||||
turnstile_token = val
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(1)
|
||||
if i % 10 == 9:
|
||||
log(f"Still waiting for Turnstile... ({i+1}s)")
|
||||
|
||||
if turnstile_token:
|
||||
log(f"Turnstile solved (token: {turnstile_token[:20]}...)")
|
||||
else:
|
||||
log("Warning: Turnstile token not found after 45s — attempting login anyway")
|
||||
|
||||
# Fill the login form
|
||||
log("Filling login form...")
|
||||
login_input = driver.find_element(By.CSS_SELECTOR, 'input[name="login"]')
|
||||
login_input.clear()
|
||||
# Type slowly to appear human
|
||||
for ch in username:
|
||||
login_input.send_keys(ch)
|
||||
time.sleep(0.03)
|
||||
|
||||
pass_input = driver.find_element(By.CSS_SELECTOR, 'input[name="password"]')
|
||||
pass_input.clear()
|
||||
for ch in password:
|
||||
pass_input.send_keys(ch)
|
||||
time.sleep(0.03)
|
||||
|
||||
# Check remember checkbox
|
||||
try:
|
||||
remember = driver.find_element(By.CSS_SELECTOR, 'input[name="remember"]')
|
||||
if not remember.is_selected():
|
||||
driver.execute_script("arguments[0].checked = true;", remember)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Submit form
|
||||
log("Submitting login form...")
|
||||
try:
|
||||
submit_btn = driver.find_element(By.CSS_SELECTOR,
|
||||
'button[type="submit"], input[type="submit"], .button--primary')
|
||||
submit_btn.click()
|
||||
except Exception:
|
||||
driver.execute_script("""
|
||||
var form = document.querySelector('form.block-body') ||
|
||||
document.querySelector('form[action*="login"]');
|
||||
if (form) form.submit();
|
||||
""")
|
||||
|
||||
# Wait for navigation after submit
|
||||
log("Waiting for redirect...")
|
||||
time.sleep(5)
|
||||
try:
|
||||
WebDriverWait(driver, 15).until(
|
||||
lambda d: d.execute_script("return document.readyState") == "complete"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
final_url = driver.current_url
|
||||
log(f"After submit: {final_url}")
|
||||
|
||||
# Extract cookies
|
||||
cookies = driver.get_cookies()
|
||||
cookie_str = "; ".join(f"{c['name']}={c['value']}" for c in cookies)
|
||||
|
||||
# Check for login success
|
||||
has_user_cookie = any(c['name'] in ('xf_user', 'ogaddgmetaprof_user') for c in cookies)
|
||||
|
||||
if not has_user_cookie:
|
||||
# Check for error message
|
||||
error_msg = "Login failed — no user cookie returned"
|
||||
try:
|
||||
error_el = driver.find_element(By.CSS_SELECTOR, '.blockMessage--error')
|
||||
error_msg = error_el.text.strip()
|
||||
except Exception:
|
||||
pass
|
||||
# Also dump all cookie names for debugging
|
||||
cookie_names = [c['name'] for c in cookies]
|
||||
log(f"Cookie names: {cookie_names}")
|
||||
log(f"Error: {error_msg}")
|
||||
print(json.dumps({"ok": False, "error": error_msg, "url": final_url}))
|
||||
sys.exit(1)
|
||||
|
||||
log(f"Login successful — {len(cookies)} cookies")
|
||||
print(json.dumps({"ok": True, "cookies": cookie_str, "url": final_url}))
|
||||
|
||||
except Exception as e:
|
||||
log(f"Fatal error: {e}")
|
||||
print(json.dumps({"ok": False, "error": str(e)}))
|
||||
sys.exit(1)
|
||||
finally:
|
||||
if driver:
|
||||
try:
|
||||
driver.quit()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def log(msg):
|
||||
"""Log to stderr so it doesn't interfere with JSON stdout."""
|
||||
print(f"[login_helper] {msg}", file=sys.stderr, flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user