]*class=["'][^"']*THUMBS-BLOCK[^"']*["'][^>]*>[\s\S]*?<\/div>/gi, '')
+ .replace(/
]*id=["']tools["'][^>]*>[\s\S]*?<\/div>/gi, '');
+
+ // ── Links zurück auf Originale umschreiben ────────────────────────────
+ // archive.ph schreibt Links um zu: https://archive.ph/TIMESTAMP/https://original.url
+ // Wir extrahieren die Original-URL
+ html = html.replace(
+ /https?:\/\/archive\.(?:ph|is|today|fo|li|vn|md|gg)\/[a-zA-Z0-9]+\/(https?:\/\/[^"'\s>]+)/g,
+ '$1'
+ );
+ // Auch relative Archive-Links
+ html = html.replace(
+ /href="\/[a-zA-Z0-9]{4,10}\/(https?:\/\/[^"]+)"/g,
+ 'href="$1"'
+ );
+
+ // ── CSS für sauberes Drucken injizieren ───────────────────────────────
+ const printCss = `
+
+`;
+
+ // CSS vor einfügen
+ if (html.includes('')) {
+ html = html.replace('', printCss + '');
+ } else {
+ html = printCss + html;
+ }
+
+ // Base-Tag setzen damit relative URLs funktionieren
+ const baseTag = `
`;
+ if (html.includes('')) {
+ html = html.replace('', '' + baseTag);
+ }
+
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
+ // X-Frame-Options entfernen damit wir es anzeigen können
+ res.removeHeader('X-Frame-Options');
+ res.send(html);
+ } catch (err) {
+ res.status(500).send(`Fehler: ${err.message}`);
+ }
+});
// ── GET /api/tools/paywallkiller/cdx?url=... ──────────────────────────────
-// Wayback CDX Proxy (für spätere Verwendung)
router.get('/cdx', authenticate, async (req, res) => {
const { url } = req.query;
if (!url) return res.status(400).json({ error: 'url fehlt' });
@@ -15,19 +147,9 @@ router.get('/cdx', authenticate, async (req, res) => {
if (!targetUrl.startsWith('http')) targetUrl = 'https://' + targetUrl;
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 result = await new Promise((resolve, reject) => {
- const req2 = https.get(cdxUrl, { headers: { 'User-Agent': 'Mozilla/5.0' }, timeout: 20000 }, (res2) => {
- let body = '';
- res2.setEncoding('utf8');
- res2.on('data', d => body += d);
- res2.on('end', () => resolve({ status: res2.statusCode, body }));
- res2.on('error', reject);
- });
- req2.on('error', reject);
- req2.on('timeout', () => { req2.destroy(); reject(new Error('Timeout')); });
- });
- if (result.status !== 200) return res.status(502).json({ error: `CDX ${result.status}` });
- const data = JSON.parse(result.body);
+ const r = await fetchHtml(cdxUrl);
+ if (r.status !== 200) return res.status(502).json({ error: `CDX ${r.status}` });
+ const data = JSON.parse(r.body);
if (!Array.isArray(data) || data.length < 2 || !data[1]?.[0]) return res.json({ found: false });
return res.json({ found: true, waybackUrl: `https://web.archive.org/web/${data[1][0]}/${targetUrl}` });
} catch (err) {
@@ -35,84 +157,4 @@ router.get('/cdx', authenticate, async (req, res) => {
}
});
-// ── POST /api/tools/paywallkiller/pdf ─────────────────────────────────────
-// Lädt archive.ph Snapshot-URL mit Puppeteer → PDF
-// archive.ph Snapshot-URLs (hash-basiert) benötigen kein CAPTCHA
-router.post('/pdf', authenticate, async (req, res) => {
- const { archiveUrl } = req.body;
- if (!archiveUrl || typeof archiveUrl !== 'string') return res.status(400).json({ error: 'archiveUrl fehlt' });
-
- const tmpFile = path.join('/tmp', `pwk_${crypto.randomBytes(8).toString('hex')}.pdf`);
- let browser = null;
-
- 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 executablePath = candidates.find(p => { try { fs.accessSync(p); return true; } catch { return false; } });
- if (!executablePath) return res.status(500).json({ error: 'Chromium nicht gefunden' });
-
- browser = await puppeteer.launch({
- executablePath,
- headless: true,
- args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage',
- '--disable-gpu', '--single-process', '--no-zygote'],
- timeout: 30000,
- });
-
- const page = await browser.newPage();
- await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36');
- await page.setViewport({ width: 1280, height: 900 });
-
- // archive.ph laden – Snapshot-URL direkt (kein CAPTCHA bei direkten Snapshot-URLs)
- await page.goto(archiveUrl, { waitUntil: 'networkidle0', timeout: 30000 });
-
- // Prüfen ob CAPTCHA erschienen ist
- const hasCaptcha = await page.evaluate(() => {
- return document.title.includes('One more step') ||
- document.body.innerText.includes('complete a CAPTCHA') ||
- document.body.innerText.includes('security check');
- });
-
- if (hasCaptcha) {
- await browser.close(); browser = null;
- return res.status(403).json({ error: 'CAPTCHA – bitte im Browser das Archiv öffnen und CAPTCHA lösen, dann erneut versuchen.' });
- }
-
- // UI-Störelemente entfernen
- await page.evaluate(() => {
- ['#wm-ipp-base','#wm-ipp','#donato',
- '#usercentrics-root','.uc-banner',
- '#onetrust-consent-sdk','.onetrust-pc-dark-filter',
- '[id*="cookie"]','[class*="cookie-banner"]'].forEach(sel => {
- try { document.querySelectorAll(sel).forEach(el => el.remove()); } catch {}
- });
- document.body.style.overflow = 'visible';
- });
-
- await new Promise(r => setTimeout(r, 500));
-
- await page.pdf({
- path: tmpFile, format: 'A4', printBackground: true,
- margin: { top: '10mm', bottom: '10mm', left: '10mm', right: '10mm' },
- });
- await browser.close(); browser = null;
-
- res.setHeader('Content-Type', 'application/pdf');
- res.setHeader('Content-Disposition', `attachment; filename="artikel_${Date.now()}.pdf"`);
- const stream = fs.createReadStream(tmpFile);
- stream.pipe(res);
- stream.on('end', () => fs.unlink(tmpFile, () => {}));
- stream.on('error', () => fs.unlink(tmpFile, () => {}));
- } catch (err) {
- if (browser) try { await browser.close(); } catch {}
- fs.unlink(tmpFile, () => {});
- return res.status(500).json({ error: 'PDF fehlgeschlagen: ' + err.message });
- }
-});
-
module.exports = router;
diff --git a/frontend/src/tools/paywallkiller.jsx b/frontend/src/tools/paywallkiller.jsx
index a7bded6..516c1fe 100644
--- a/frontend/src/tools/paywallkiller.jsx
+++ b/frontend/src/tools/paywallkiller.jsx
@@ -9,11 +9,13 @@ const C = {
};
export default function PaywallKiller() {
- const [inputUrl, setInputUrl] = useState('');
- const [status, setStatus] = useState('idle');
- const [archiveUrl, setArchiveUrl] = useState('');
- const [captchaDone, setCaptchaDone] = useState(false);
- const inputRef = useRef(null);
+ const [inputUrl, setInputUrl] = useState('');
+ const [snapshotUrl, setSnapshotUrl] = useState('');
+ const [status, setStatus] = useState('idle'); // idle | step1 | step2
+ const [archiveUrl, setArchiveUrl] = useState('');
+ const [captchaDone, setCaptchaDone] = useState(false);
+ const inputRef = useRef(null);
+ const snapshotRef = useRef(null);
function getTargetUrl() {
const url = inputUrl.trim();
@@ -22,20 +24,38 @@ export default function PaywallKiller() {
function handleSearch() {
if (!inputUrl.trim()) return;
- const aUrl = `https://archive.ph/newest/${getTargetUrl()}`;
- setArchiveUrl(aUrl);
- setStatus('open_archive');
+ setArchiveUrl(`https://archive.ph/newest/${getTargetUrl()}`);
+ setStatus('step1');
setCaptchaDone(false);
+ setSnapshotUrl('');
+ }
+
+ function handleCaptchaDone() {
+ setCaptchaDone(true);
+ setStatus('step2');
+ setTimeout(() => snapshotRef.current?.focus(), 100);
+ }
+
+ function getRenderUrl() {
+ const snap = snapshotUrl.trim();
+ // Snapshot-URL die der User eingefügt hat → über /render bereinigen
+ if (snap && snap.startsWith('https://archive.')) {
+ return `/api/tools/paywallkiller/render?url=${encodeURIComponent(snap)}`;
+ }
+ return null;
}
function handleReset() {
setInputUrl('');
+ setSnapshotUrl('');
setStatus('idle');
setArchiveUrl('');
setCaptchaDone(false);
setTimeout(() => inputRef.current?.focus(), 50);
}
+ const renderUrl = getRenderUrl();
+
return (
@@ -44,7 +64,7 @@ export default function PaywallKiller() {
🔓
PAYWALL-KILLER
-
archive.ph · Als PDF speichern
+
archive.ph · Bereinigt · Als PDF drucken
{/* URL-Eingabe */}
@@ -73,69 +93,75 @@ export default function PaywallKiller() {