Files
dickendock/frontend/src/tools/paywallkiller.jsx

215 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useRef, useEffect } from 'react';
import { S, api } from '../lib.js';
const C = {
accent: '#f59e0b',
success: '#4ade80',
error: '#f87171',
muted: '#6b7280',
};
export default function PaywallKiller() {
const [inputUrl, setInputUrl] = useState('');
const [status, setStatus] = useState('idle');
const [archiveUrl, setArchiveUrl] = useState('');
const [showCaptcha, setShowCaptcha] = useState(false);
const [errMsg, setErrMsg] = useState('');
const [pdfLoading, setPdfLoading] = useState(false);
const inputRef = useRef(null);
const iframeRef = useRef(null);
function getTargetUrl() {
const url = inputUrl.trim();
return url.startsWith('http') ? url : 'https://' + url;
}
function handleSearch() {
if (!inputUrl.trim()) return;
const aUrl = `https://archive.ph/newest/${getTargetUrl()}`;
setArchiveUrl(aUrl);
setStatus('captcha');
setShowCaptcha(true);
setErrMsg('');
}
function handleCaptchaDone() {
setShowCaptcha(false);
setStatus('ready');
}
async function handlePdf() {
setPdfLoading(true);
setErrMsg('');
try {
// Cookies für archive.ph aus dem Browser lesen
// document.cookie liefert nur same-site cookies archive.ph cookies
// sind third-party und nicht lesbar. Wir übergeben stattdessen
// die URL und lassen Puppeteer direkt die Session simulieren:
// Puppeteer öffnet zuerst archive.ph root um Cookies zu setzen,
// dann direkt die Snapshot-URL.
const token = localStorage.getItem('sk_token');
const res = await fetch('/api/tools/paywallkiller/pdf', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
body: JSON.stringify({ archiveUrl }),
});
if (!res.ok) {
const d = await res.json().catch(() => ({}));
throw new Error(d.error || 'PDF-Fehler');
}
const blob = await res.blob();
const objUrl = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = objUrl;
a.download = 'artikel_' + Date.now() + '.pdf';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(objUrl), 5000);
} catch (err) {
setErrMsg(err.message || 'PDF fehlgeschlagen');
} finally {
setPdfLoading(false);
}
}
function handleReset() {
setInputUrl('');
setStatus('idle');
setArchiveUrl('');
setShowCaptcha(false);
setErrMsg('');
setPdfLoading(false);
setTimeout(() => inputRef.current?.focus(), 50);
}
return (
<div style={{ padding: '20px 16px', maxWidth: 640, margin: '0 auto' }}>
{/* Header */}
<div style={{ marginBottom: 24 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4 }}>
<span style={{ fontSize: 20 }}>🔓</span>
<span style={{ color: C.accent, fontFamily: 'monospace', fontSize: 13, letterSpacing: 1 }}>PAYWALL-KILLER</span>
</div>
<div style={{ color: C.muted, fontSize: 11, fontFamily: 'monospace' }}>archive.ph · Als PDF speichern</div>
</div>
{/* URL-Eingabe */}
<div style={{ ...S.card, marginBottom: 16 }}>
<div style={{ ...S.head, marginBottom: 10 }}>ARTIKEL-URL</div>
<input
ref={inputRef}
value={inputUrl}
onChange={e => setInputUrl(e.target.value)}
onKeyDown={e => e.key === 'Enter' && status === 'idle' && handleSearch()}
placeholder="URL einfügen (Long-Press → Einfügen)"
style={{ ...S.inp, width: '100%', boxSizing: 'border-box', marginBottom: 10 }}
/>
<div style={{ display: 'flex', gap: 8 }}>
{status === 'idle' && (
<button onClick={handleSearch} disabled={!inputUrl.trim()}
style={{ ...S.btn(C.accent), flex: 1, opacity: !inputUrl.trim() ? 0.45 : 1, cursor: !inputUrl.trim() ? 'default' : 'pointer' }}>
🔍 Archiv öffnen
</button>
)}
{status !== 'idle' && (
<button onClick={handleReset}
style={{ background: 'rgba(255,255,255,0.06)', border: '1px solid rgba(255,255,255,0.14)', borderRadius: 6, padding: '8px 14px', color: 'rgba(255,255,255,0.6)', cursor: 'pointer', fontFamily: 'monospace', fontSize: 12 }}>
Reset
</button>
)}
</div>
</div>
{/* CAPTCHA-Schritt */}
{status === 'captcha' && (
<div style={{ ...S.card, borderColor: `${C.accent}55`, marginBottom: 16 }}>
<div style={{ ...S.head, marginBottom: 8 }}>SCHRITT 1 CAPTCHA BESTÄTIGEN</div>
<div style={{ color: C.muted, fontSize: 11, fontFamily: 'monospace', marginBottom: 12, lineHeight: 1.6 }}>
archive.ph wird unten geladen. Falls ein CAPTCHA erscheint: abhaken, warten bis der Artikel sichtbar ist, dann auf "Artikel bereit → PDF erstellen" klicken.
</div>
{/* archive.ph iframe */}
<div style={{ borderRadius: 8, overflow: 'hidden', border: '1px solid rgba(255,255,255,0.1)', marginBottom: 12, background: '#fff' }}>
<iframe
ref={iframeRef}
src={archiveUrl}
style={{ width: '100%', height: 400, border: 'none', display: 'block' }}
title="archive.ph"
/>
</div>
<div style={{ display: 'flex', gap: 8 }}>
<a href={archiveUrl} target="_blank" rel="noreferrer"
style={{ ...S.btn(C.muted), textDecoration: 'none', fontSize: 12 }}>
🌐 Im Browser öffnen
</a>
<button onClick={handleCaptchaDone}
style={{ ...S.btn(C.success), flex: 1, fontSize: 12 }}>
Artikel bereit PDF erstellen
</button>
</div>
</div>
)}
{/* PDF-Schritt */}
{status === 'ready' && (
<div style={{ ...S.card, borderColor: `${C.success}55`, marginBottom: 16 }}>
<div style={{ ...S.head, marginBottom: 8 }}>SCHRITT 2 PDF ERSTELLEN</div>
<div style={{ color: C.muted, fontSize: 11, fontFamily: 'monospace', marginBottom: 12, lineHeight: 1.6 }}>
Der Server lädt den Artikel jetzt über archive.ph und erstellt ein PDF. Das dauert 1530 Sekunden.
</div>
<div style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 6, padding: '7px 10px', marginBottom: 12, wordBreak: 'break-all' }}>
<span style={{ color: C.muted, fontSize: 10, fontFamily: 'monospace' }}>{archiveUrl}</span>
</div>
<div style={{ display: 'flex', gap: 8 }}>
<a href={archiveUrl} target="_blank" rel="noreferrer"
style={{ ...S.btn(C.success), textDecoration: 'none', fontSize: 12 }}>
🌐 Öffnen
</a>
<button onClick={handlePdf} disabled={pdfLoading}
style={{ ...S.btn(C.accent), flex: 1, opacity: pdfLoading ? 0.5 : 1, cursor: pdfLoading ? 'default' : 'pointer', fontSize: 12 }}>
{pdfLoading ? '⏳ Erstelle PDF...' : '⬇ Als PDF speichern'}
</button>
</div>
{pdfLoading && (
<div style={{ color: C.muted, fontSize: 11, fontFamily: 'monospace', marginTop: 8 }}>
Seite wird serverseitig gerendert (1530s)...
</div>
)}
</div>
)}
{errMsg && (
<div style={{ ...S.card, borderColor: `${C.error}44`, marginTop: 8 }}>
<div style={{ display: 'flex', gap: 8 }}>
<span></span>
<span style={{ color: C.error, fontFamily: 'monospace', fontSize: 12 }}>{errMsg}</span>
</div>
</div>
)}
{status === 'idle' && (
<div style={{ ...S.card }}>
<div style={{ ...S.head, marginBottom: 10 }}>SO FUNKTIONIERT ES</div>
{[
['1', 'URL des Artikels einfügen'],
['2', 'archive.ph wird geladen CAPTCHA einmalig bestätigen'],
['3', 'Wenn der Artikel sichtbar ist: "PDF erstellen" klicken'],
['4', 'PDF wird automatisch heruntergeladen'],
].map(([num, text]) => (
<div key={num} style={{ display: 'flex', gap: 10, alignItems: 'flex-start', marginBottom: 8 }}>
<span style={{ background: `${C.accent}20`, border: `1px solid ${C.accent}44`, borderRadius: '50%', width: 20, height: 20, flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 10, color: C.accent, fontFamily: 'monospace', marginTop: 2 }}>{num}</span>
<span style={{ color: C.muted, fontSize: 11, fontFamily: 'monospace', lineHeight: 1.6 }}>{text}</span>
</div>
))}
</div>
)}
</div>
);
}