fix: Wayback komplett im Browser, Server nur noch für PDF

This commit is contained in:
2026-06-12 12:47:58 +02:00
parent 6eb9e1cc72
commit 4c909392e5
2 changed files with 124 additions and 233 deletions

View File

@@ -1,142 +1,14 @@
const express = require('express');
const router = express.Router();
const { authenticate } = require('../../middleware/auth');
const https = require('https');
const http = require('http');
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
// ── HTTP GET mit Redirect-Follow + Cookie-Jar ─────────────────────────────
function fetchPage(url, opts = {}) {
return new Promise((resolve, reject) => {
if ((opts.redirects || 0) > 8) return reject(new Error('Zu viele Redirects'));
const mod = url.startsWith('https') ? https : http;
const cookieHeader = opts.cookieJar && Object.keys(opts.cookieJar).length
? Object.entries(opts.cookieJar).map(([k,v]) => `${k}=${v}`).join('; ')
: undefined;
const headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'de-DE,de;q=0.9,en;q=0.8',
'Accept-Encoding': 'identity',
'Connection': 'keep-alive',
...(opts.referer ? { 'Referer': opts.referer } : {}),
...(cookieHeader ? { 'Cookie': cookieHeader } : {}),
};
const req = mod.get(url, { headers, timeout: 15000 }, (res) => {
const jar = opts.cookieJar || {};
for (const c of (res.headers['set-cookie'] || [])) {
const m = c.match(/^([^=]+)=([^;]*)/);
if (m) jar[m[1].trim()] = m[2].trim();
}
if ([301,302,303,307,308].includes(res.statusCode) && res.headers.location) {
let loc = res.headers.location;
if (!loc.startsWith('http')) {
try { const b = new URL(url); loc = loc.startsWith('/') ? `${b.protocol}//${b.host}${loc}` : `${b.protocol}//${b.host}/${loc}`; }
catch { return reject(new Error('Ungültige Redirect-URL')); }
}
res.resume();
return fetchPage(loc, { ...opts, redirects: (opts.redirects||0)+1, cookieJar: jar, referer: url }).then(resolve).catch(reject);
}
let body = '';
res.setEncoding('utf8');
res.on('data', d => { if (body.length < 150000) body += d; });
res.on('end', () => resolve({ statusCode: res.statusCode, finalUrl: url, body, cookieJar: jar }));
res.on('error', reject);
});
req.on('error', reject);
req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
});
}
// ── GET /api/tools/paywallkiller/wayback?url=... ──────────────────────────
// Sucht vorhandenen Wayback-Snapshot via CDX API
router.get('/wayback', authenticate, async (req, res) => {
const { url } = req.query;
if (!url || typeof url !== 'string') return res.status(400).json({ error: 'url Parameter fehlt' });
let targetUrl = url.trim();
if (!targetUrl.startsWith('http')) targetUrl = 'https://' + targetUrl;
try {
const cdxUrl = `https://web.archive.org/cdx/search/cdx?url=${encodeURIComponent(targetUrl)}&output=json&limit=1&fl=timestamp&filter=statuscode:200&fastLatest=true`;
const r = await fetchPage(cdxUrl);
if (r.statusCode === 200 && r.body) {
let data;
try { data = JSON.parse(r.body); } catch { data = null; }
if (Array.isArray(data) && data.length >= 2 && data[1]?.[0]) {
return res.json({ found: true, waybackUrl: `https://web.archive.org/web/${data[1][0]}/${targetUrl}` });
}
}
return res.json({ found: false });
} catch (err) {
return res.status(500).json({ error: 'Fehler: ' + err.message });
}
});
// ── POST /api/tools/paywallkiller/wayback-save ────────────────────────────
// Schießt Save-Request ab (fire-and-forget), pollt dann CDX auf Ergebnis
router.post('/wayback-save', authenticate, async (req, res) => {
const { url } = req.body;
if (!url || typeof url !== 'string') return res.status(400).json({ error: 'url fehlt' });
let targetUrl = url.trim();
if (!targetUrl.startsWith('http')) targetUrl = 'https://' + targetUrl;
async function checkCdx() {
const cdxUrl = `https://web.archive.org/cdx/search/cdx?url=${encodeURIComponent(targetUrl)}&output=json&limit=1&fl=timestamp&filter=statuscode:200&fastLatest=true`;
try {
const r = await fetchPage(cdxUrl);
if (r.statusCode === 200 && r.body) {
const data = JSON.parse(r.body);
if (Array.isArray(data) && data.length >= 2 && data[1]?.[0]) {
return `https://web.archive.org/web/${data[1][0]}/${targetUrl}`;
}
}
} catch {}
return null;
}
function fireSaveRequest() {
return new Promise((resolve) => {
const savePath = `/save/${targetUrl}`;
const req = https.get({
hostname: 'web.archive.org',
path: savePath,
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; WaybackSave/1.0)', 'Accept': 'text/html' },
timeout: 8000,
}, (res) => {
const location = res.headers.location || '';
res.resume();
resolve(location);
});
req.on('error', () => resolve(''));
req.on('timeout', () => { req.destroy(); resolve(''); });
});
}
try {
const location = await fireSaveRequest();
const directMatch = location.match(/\/web\/(\d{14})\//);
if (directMatch) {
const waybackUrl = location.startsWith('http') ? location : `https://web.archive.org${location}`;
return res.json({ success: true, waybackUrl });
}
// Polling: bis zu 6x alle 5s
for (let i = 0; i < 6; i++) {
await new Promise(r => setTimeout(r, 5000));
const found = await checkCdx();
if (found) return res.json({ success: true, waybackUrl: found });
}
return res.status(502).json({ error: 'Archivierung läuft noch oder wurde blockiert. Versuche es in einer Minute erneut.' });
} catch (err) {
return res.status(500).json({ error: 'Save fehlgeschlagen: ' + err.message });
}
});
// ── POST /api/tools/paywallkiller/pdf ─────────────────────────────────────
// Lädt Original-URL mit Puppeteer, blendet Paywall-Overlays aus, erstellt PDF
router.post('/pdf', authenticate, async (req, res) => {
const { archiveUrl, originalUrl } = req.body;
const { originalUrl, archiveUrl } = req.body;
const targetUrl = originalUrl || archiveUrl;
if (!targetUrl || typeof targetUrl !== 'string') return res.status(400).json({ error: 'URL fehlt' });
@@ -145,7 +17,12 @@ router.post('/pdf', authenticate, async (req, res) => {
try {
const puppeteer = require('puppeteer-core');
const candidates = [process.env.CHROMIUM_PATH, '/usr/bin/chromium-browser', '/usr/bin/chromium', '/usr/bin/google-chrome'].filter(Boolean);
const candidates = [
process.env.CHROMIUM_PATH,
'/usr/bin/chromium-browser',
'/usr/bin/chromium',
'/usr/bin/google-chrome',
].filter(Boolean);
const executablePath = candidates.find(p => { try { fs.accessSync(p); return true; } catch { return false; } });
if (!executablePath) return res.status(500).json({ error: 'Chromium nicht gefunden' });
@@ -162,6 +39,7 @@ router.post('/pdf', authenticate, async (req, res) => {
await page.goto(targetUrl, { waitUntil: 'networkidle0', timeout: 30000 });
await new Promise(r => setTimeout(r, 1500));
// Paywall-Overlays, Cookie-Banner, Blur-Filter entfernen
await page.addStyleTag({ content: `
[class*="paywall"],[class*="Paywall"],[id*="paywall"],
[class*="piano"],[class*="Piano"],[id*="piano"],