diff --git a/backend/src/tools/paywallkiller/routes.js b/backend/src/tools/paywallkiller/routes.js index e38b3ff..3d260fb 100644 --- a/backend/src/tools/paywallkiller/routes.js +++ b/backend/src/tools/paywallkiller/routes.js @@ -231,7 +231,8 @@ router.post('/pdf', authenticate, async (req, res) => { // ── GET /api/tools/paywallkiller/wayback?url=... ────────────────────────── -// Proxyt die Wayback CDX API – umgeht CORS/CSP Probleme im Browser +// 1. Sucht vorhandenen Snapshot via CDX API +// 2. Falls keiner → erstellt neuen Snapshot via Save 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' }); @@ -239,24 +240,62 @@ router.get('/wayback', authenticate, async (req, res) => { let targetUrl = url.trim(); 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`; - + // ── Schritt 1: Vorhandenen Snapshot suchen ────────────────────────────── try { - const result = await fetchPage(cdxUrl); - if (result.statusCode !== 200) { - return res.status(502).json({ error: `Wayback API Fehler: ${result.statusCode}` }); + const cdxUrl = `https://web.archive.org/cdx/search/cdx?url=${encodeURIComponent(targetUrl)}&output=json&limit=1&fl=timestamp&filter=statuscode:200&fastLatest=true`; + const cdxResult = await fetchPage(cdxUrl); + if (cdxResult.statusCode === 200 && cdxResult.body) { + let data; + try { data = JSON.parse(cdxResult.body); } catch { data = null; } + if (Array.isArray(data) && data.length >= 2 && data[1]?.[0]) { + const timestamp = data[1][0]; + return res.json({ found: true, waybackUrl: `https://web.archive.org/web/${timestamp}/${targetUrl}`, created: false }); + } } - let data; - try { data = JSON.parse(result.body); } catch { return res.status(502).json({ error: 'Ungültige Wayback-Antwort' }); } + } catch { /* weiter zu Save API */ } - // data = [["timestamp"],["20260608201434"]] oder [] wenn nichts gefunden - if (!Array.isArray(data) || data.length < 2 || !data[1]?.[0]) { - return res.json({ found: false }); + // ── Schritt 2: Keinen Snapshot gefunden → neu erstellen ───────────────── + return res.json({ found: false, canSave: true }); +}); + +// ── POST /api/tools/paywallkiller/wayback-save ──────────────────────────── +// Erstellt einen neuen Wayback-Snapshot und wartet auf das 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; + + // Save API aufrufen: GET https://web.archive.org/save/URL + // Antwort: 302 Redirect auf https://web.archive.org/web/TIMESTAMP/URL + try { + const saveUrl = `https://web.archive.org/save/${targetUrl}`; + const saveResult = await fetchPage(saveUrl); // fetchPage folgt Redirects + + // Nach Redirect landet man auf web.archive.org/web/TIMESTAMP/URL + const final = saveResult.finalUrl || ''; + const match = final.match(/web\.archive\.org\/web\/(\d{14})\//); + if (match) { + return res.json({ success: true, waybackUrl: final, timestamp: match[1] }); } - const timestamp = data[1][0]; - return res.json({ found: true, waybackUrl: `https://web.archive.org/web/${timestamp}/${targetUrl}` }); + + // Fallback: CDX nochmal prüfen (Save kann asynchron sein) + await new Promise(r => setTimeout(r, 3000)); + const cdxUrl = `https://web.archive.org/cdx/search/cdx?url=${encodeURIComponent(targetUrl)}&output=json&limit=1&fl=timestamp&filter=statuscode:200&fastLatest=true`; + const cdxResult = await fetchPage(cdxUrl); + if (cdxResult.statusCode === 200) { + let data; + try { data = JSON.parse(cdxResult.body); } catch { data = null; } + if (Array.isArray(data) && data.length >= 2 && data[1]?.[0]) { + const timestamp = data[1][0]; + return res.json({ success: true, waybackUrl: `https://web.archive.org/web/${timestamp}/${targetUrl}`, timestamp }); + } + } + + return res.status(502).json({ error: 'Snapshot konnte nicht erstellt werden. Die Seite blockiert möglicherweise die Wayback Machine.' }); } catch (err) { - return res.status(500).json({ error: 'Fehler: ' + err.message }); + return res.status(500).json({ error: 'Save fehlgeschlagen: ' + err.message }); } }); diff --git a/frontend/src/tools/paywallkiller.jsx b/frontend/src/tools/paywallkiller.jsx index cd1fac7..7aa87fb 100644 --- a/frontend/src/tools/paywallkiller.jsx +++ b/frontend/src/tools/paywallkiller.jsx @@ -6,6 +6,7 @@ const C = { success: '#4ade80', error: '#f87171', muted: '#6b7280', + info: '#60a5fa', }; function getDomain(url) { @@ -15,29 +16,59 @@ function getDomain(url) { export default function PaywallKiller() { const [inputUrl, setInputUrl] = useState(''); + // status: idle | checking | saving | done | error const [status, setStatus] = useState('idle'); const [waybackUrl, setWaybackUrl] = useState(''); + const [wasCreated, setWasCreated] = useState(false); const [errMsg, setErrMsg] = useState(''); const [pdfLoading, setPdfLoading] = useState(false); const inputRef = useRef(null); + function getTargetUrl() { + const url = inputUrl.trim(); + return url.startsWith('http') ? url : 'https://' + url; + } + async function handleCheck() { const url = inputUrl.trim(); if (!url) return; - const targetUrl = url.startsWith('http') ? url : 'https://' + url; setStatus('checking'); setErrMsg(''); setWaybackUrl(''); + setWasCreated(false); try { - const data = await api(`/tools/paywallkiller/wayback?url=${encodeURIComponent(targetUrl)}`, { method: 'GET' }); - setWaybackUrl(data.found ? data.waybackUrl : ''); - setStatus('done'); + const data = await api(`/tools/paywallkiller/wayback?url=${encodeURIComponent(getTargetUrl())}`, { method: 'GET' }); + if (data.found) { + setWaybackUrl(data.waybackUrl); + setStatus('done'); + } else { + // Kein Snapshot → direkt Save starten + await handleSave(); + } } catch (err) { setErrMsg(err.message || 'Fehler beim Suchen'); setStatus('error'); } } + async function handleSave() { + setStatus('saving'); + setErrMsg(''); + try { + const data = await api('/tools/paywallkiller/wayback-save', { body: { url: getTargetUrl() } }); + if (data.success) { + setWaybackUrl(data.waybackUrl); + setWasCreated(true); + setStatus('done'); + } else { + throw new Error(data.error || 'Snapshot konnte nicht erstellt werden'); + } + } catch (err) { + setErrMsg(err.message || 'Snapshot-Erstellung fehlgeschlagen'); + setStatus('error'); + } + } + async function handlePdf() { if (!waybackUrl) return; setPdfLoading(true); @@ -76,16 +107,18 @@ export default function PaywallKiller() { setInputUrl(''); setStatus('idle'); setWaybackUrl(''); + setWasCreated(false); setErrMsg(''); setPdfLoading(false); setTimeout(() => inputRef.current?.focus(), 50); } const isChecking = status === 'checking'; + const isSaving = status === 'saving'; + const isBusy = isChecking || isSaving; const isDone = status === 'done'; - const hasWayback = isDone && !!waybackUrl; - const archivePhUrl = isDone - ? `https://archive.ph/newest/${(inputUrl.startsWith('http') ? inputUrl : 'https://' + inputUrl).trim()}` + const archivePhUrl = inputUrl.trim() + ? `https://archive.ph/newest/${getTargetUrl()}` : ''; return ( @@ -109,14 +142,14 @@ export default function PaywallKiller() { ref={inputRef} value={inputUrl} onChange={e => setInputUrl(e.target.value)} - onKeyDown={e => e.key === 'Enter' && !isChecking && handleCheck()} - placeholder="URL hier einfügen (Long-Press → Einfügen)" + onKeyDown={e => e.key === 'Enter' && !isBusy && handleCheck()} + placeholder="URL einfügen (Long-Press → Einfügen)" style={{ ...S.inp, width: '100%', boxSizing: 'border-box', marginBottom: 10 }} />