Files
OFApp/server/flaresolverr.js
T
Trey T 236f36aae6 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>
2026-04-16 07:48:10 -05:00

129 lines
4.4 KiB
JavaScript

import { Router } from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import path from 'path';
import { fileURLToPath } from 'url';
import { getForumSiteById, updateForumSite } from './db.js';
const execAsync = promisify(exec);
const router = Router();
const FLARESOLVERR_URL = process.env.FLARESOLVERR_URL || 'http://localhost:8191';
const CHROMIUM_PATH = process.env.CHROMIUM_PATH || '/usr/bin/chromium-browser';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
/**
* Refresh forum cookies using undetected_chromedriver (Python).
* Runs login_helper.py via xvfb-run so Chrome runs in headed mode
* with a virtual display — this is what lets Turnstile auto-solve.
*/
export async function refreshForumCookies(siteId) {
const site = getForumSiteById(siteId);
if (!site) throw new Error(`Forum site ${siteId} not found`);
if (!site.username || !site.password) {
throw new Error('Forum site has no saved credentials — set username and password first');
}
const baseUrl = site.base_url || 'https://simpcity.su';
const loginUrl = `${baseUrl}/login/`;
const helperPath = path.join(__dirname, 'login_helper.py');
console.log(`[flaresolverr] Refreshing cookies for site ${siteId} (${site.name})`);
console.log(`[flaresolverr] Login URL: ${loginUrl}`);
// Run the Python helper with xvfb-run for virtual display
// Escape arguments for shell safety
const escapedUrl = loginUrl.replace(/'/g, "'\\''");
const escapedUser = site.username.replace(/'/g, "'\\''");
const escapedPass = site.password.replace(/'/g, "'\\''");
const cmd = `xvfb-run --auto-servernum --server-args='-screen 0 1920x1080x24' python3 '${helperPath}' '${escapedUrl}' '${escapedUser}' '${escapedPass}'`;
try {
const { stdout, stderr } = await execAsync(cmd, {
timeout: 120000, // 2 minutes
maxBuffer: 10 * 1024 * 1024,
env: { ...process.env, CHROMIUM_PATH },
});
// Log stderr (debug output from login_helper.py)
if (stderr) {
for (const line of stderr.split('\n').filter(Boolean)) {
console.log(`[flaresolverr] ${line}`);
}
}
// Parse JSON from stdout
const result = JSON.parse(stdout.trim());
if (!result.ok) {
throw new Error(result.error || 'Login failed');
}
// Update DB with new cookies
const expiresAt = new Date(Date.now() + 25 * 24 * 60 * 60 * 1000).toISOString();
updateForumSite(siteId, {
cookies: result.cookies,
cookie_expires_at: expiresAt,
});
console.log(`[flaresolverr] Cookie refresh successful for site ${siteId}`);
return result.cookies;
} catch (err) {
// If execAsync fails, the error might have stderr info
if (err.stderr) {
for (const line of err.stderr.split('\n').filter(Boolean)) {
console.error(`[flaresolverr] ${line}`);
}
}
// Try to parse stdout for a structured error
if (err.stdout) {
try {
const result = JSON.parse(err.stdout.trim());
if (result.error) throw new Error(result.error);
} catch (parseErr) {
// Not JSON, use original error
}
}
throw new Error(`Cookie refresh failed: ${err.message}`);
}
}
// --- API Endpoints ---
// Manual cookie refresh
router.post('/api/flaresolverr/refresh/:siteId', async (req, res) => {
const siteId = parseInt(req.params.siteId, 10);
try {
const cookieStr = await refreshForumCookies(siteId);
res.json({ ok: true, cookies: cookieStr });
} catch (err) {
console.error(`[flaresolverr] Refresh failed for site ${siteId}:`, err.message);
res.status(500).json({ error: err.message });
}
});
// Check if cookie refresh is available (Chromium + xvfb-run installed)
router.get('/api/flaresolverr/status', async (_req, res) => {
try {
// Check for xvfb-run and chromium
await execAsync('which xvfb-run && which chromium-browser || which chromium', { timeout: 5000 });
// Check for undetected_chromedriver python package
await execAsync('python3 -c "import undetected_chromedriver"', { timeout: 5000 });
res.json({ available: true });
} catch {
// Fallback: check FlareSolverr service
try {
const resp = await fetch(`${FLARESOLVERR_URL}/health`, {
signal: AbortSignal.timeout(5000),
});
res.json({ available: resp.ok });
} catch {
res.json({ available: false, error: 'Neither undetected_chromedriver nor FlareSolverr available' });
}
}
});
export default router;