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:
Trey T
2026-04-16 07:48:10 -05:00
parent 4903b84aef
commit 236f36aae6
54 changed files with 9986 additions and 420 deletions
+216
View File
@@ -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()