fix: kein iframe, neuer Tab + Checkbox-Bestätigung vor PDF

This commit is contained in:
2026-06-12 14:44:31 +02:00
parent 25b906bee6
commit 88cd754fb5

View File

@@ -1,5 +1,5 @@
import { useState, useRef, useEffect } from 'react'; import { useState, useRef } from 'react';
import { S, api } from '../lib.js'; import { S } from '../lib.js';
const C = { const C = {
accent: '#f59e0b', accent: '#f59e0b',
@@ -9,14 +9,13 @@ const C = {
}; };
export default function PaywallKiller() { export default function PaywallKiller() {
const [inputUrl, setInputUrl] = useState(''); const [inputUrl, setInputUrl] = useState('');
const [status, setStatus] = useState('idle'); const [status, setStatus] = useState('idle');
const [archiveUrl, setArchiveUrl] = useState(''); const [archiveUrl, setArchiveUrl] = useState('');
const [showCaptcha, setShowCaptcha] = useState(false); const [captchaDone, setCaptchaDone] = useState(false);
const [errMsg, setErrMsg] = useState(''); const [errMsg, setErrMsg] = useState('');
const [pdfLoading, setPdfLoading] = useState(false); const [pdfLoading, setPdfLoading] = useState(false);
const inputRef = useRef(null); const inputRef = useRef(null);
const iframeRef = useRef(null);
function getTargetUrl() { function getTargetUrl() {
const url = inputUrl.trim(); const url = inputUrl.trim();
@@ -27,26 +26,19 @@ export default function PaywallKiller() {
if (!inputUrl.trim()) return; if (!inputUrl.trim()) return;
const aUrl = `https://archive.ph/newest/${getTargetUrl()}`; const aUrl = `https://archive.ph/newest/${getTargetUrl()}`;
setArchiveUrl(aUrl); setArchiveUrl(aUrl);
setStatus('captcha'); setStatus('open_archive');
setShowCaptcha(true); setCaptchaDone(false);
setErrMsg(''); setErrMsg('');
} }
function handleCaptchaDone() { function handleOpenArchive() {
setShowCaptcha(false); window.open(archiveUrl, '_blank');
setStatus('ready');
} }
async function handlePdf() { async function handlePdf() {
setPdfLoading(true); setPdfLoading(true);
setErrMsg(''); setErrMsg('');
try { 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 token = localStorage.getItem('sk_token');
const res = await fetch('/api/tools/paywallkiller/pdf', { const res = await fetch('/api/tools/paywallkiller/pdf', {
method: 'POST', method: 'POST',
@@ -80,7 +72,7 @@ export default function PaywallKiller() {
setInputUrl(''); setInputUrl('');
setStatus('idle'); setStatus('idle');
setArchiveUrl(''); setArchiveUrl('');
setShowCaptcha(false); setCaptchaDone(false);
setErrMsg(''); setErrMsg('');
setPdfLoading(false); setPdfLoading(false);
setTimeout(() => inputRef.current?.focus(), 50); setTimeout(() => inputRef.current?.focus(), 50);
@@ -110,13 +102,12 @@ export default function PaywallKiller() {
style={{ ...S.inp, width: '100%', boxSizing: 'border-box', marginBottom: 10 }} style={{ ...S.inp, width: '100%', boxSizing: 'border-box', marginBottom: 10 }}
/> />
<div style={{ display: 'flex', gap: 8 }}> <div style={{ display: 'flex', gap: 8 }}>
{status === 'idle' && ( {status === 'idle' ? (
<button onClick={handleSearch} disabled={!inputUrl.trim()} <button onClick={handleSearch} disabled={!inputUrl.trim()}
style={{ ...S.btn(C.accent), flex: 1, opacity: !inputUrl.trim() ? 0.45 : 1, cursor: !inputUrl.trim() ? 'default' : 'pointer' }}> style={{ ...S.btn(C.accent), flex: 1, opacity: !inputUrl.trim() ? 0.45 : 1, cursor: !inputUrl.trim() ? 'default' : 'pointer' }}>
🔍 Archiv öffnen 🔍 Archiv suchen
</button> </button>
)} ) : (
{status !== 'idle' && (
<button onClick={handleReset} <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 }}> 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 Reset
@@ -125,82 +116,84 @@ export default function PaywallKiller() {
</div> </div>
</div> </div>
{/* CAPTCHA-Schritt */} {/* Schritt 1: archive.ph öffnen + CAPTCHA */}
{status === 'captcha' && ( {status === 'open_archive' && (
<div style={{ ...S.card, borderColor: `${C.accent}55`, marginBottom: 16 }}> <div style={{ ...S.card, borderColor: `${C.accent}55`, marginBottom: 16 }}>
<div style={{ ...S.head, marginBottom: 8 }}>SCHRITT 1 CAPTCHA BESTÄTIGEN</div> <div style={{ ...S.head, marginBottom: 8 }}>
<div style={{ color: C.muted, fontSize: 11, fontFamily: 'monospace', marginBottom: 12, lineHeight: 1.6 }}> <span style={{ background: `${C.accent}20`, border: `1px solid ${C.accent}44`, borderRadius: '50%', width: 18, height: 18, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 10, marginRight: 8 }}>1</span>
archive.ph wird unten geladen. Falls ein CAPTCHA erscheint: abhaken, warten bis der Artikel sichtbar ist, dann auf "Artikel bereit → PDF erstellen" klicken. ARCHIVE.PH ÖFFNEN
</div> </div>
{/* archive.ph iframe */} <div style={{ color: C.muted, fontSize: 11, fontFamily: 'monospace', marginBottom: 14, lineHeight: 1.7 }}>
<div style={{ borderRadius: 8, overflow: 'hidden', border: '1px solid rgba(255,255,255,0.1)', marginBottom: 12, background: '#fff' }}> Öffne archive.ph im Browser. Falls ein CAPTCHA erscheint: abhaken und warten bis der Artikel vollständig sichtbar ist. Dann zurückkommen und Schritt 2 ausführen.
<iframe
ref={iframeRef}
src={archiveUrl}
style={{ width: '100%', height: 400, border: 'none', display: 'block' }}
title="archive.ph"
/>
</div> </div>
<div style={{ display: 'flex', gap: 8 }}> <button onClick={handleOpenArchive}
<a href={archiveUrl} target="_blank" rel="noreferrer" style={{ ...S.btn(C.accent), width: '100%', marginBottom: 14, fontSize: 13 }}>
style={{ ...S.btn(C.muted), textDecoration: 'none', fontSize: 12 }}> 🌐 archive.ph öffnen
🌐 Im Browser öffnen </button>
</a>
<button onClick={handleCaptchaDone} {/* Checkbox-Bestätigung */}
style={{ ...S.btn(C.success), flex: 1, fontSize: 12 }}> <div
Artikel bereit PDF erstellen onClick={() => setCaptchaDone(v => !v)}
</button> style={{ display: 'flex', alignItems: 'center', gap: 12, cursor: 'pointer', padding: '10px 12px', background: captchaDone ? `${C.success}15` : 'rgba(255,255,255,0.03)', border: `1px solid ${captchaDone ? C.success + '55' : 'rgba(255,255,255,0.1)'}`, borderRadius: 8 }}>
<div style={{
width: 22, height: 22, borderRadius: 5, flexShrink: 0,
background: captchaDone ? C.success : 'transparent',
border: `2px solid ${captchaDone ? C.success : 'rgba(255,255,255,0.3)'}`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
transition: 'all 0.15s',
}}>
{captchaDone && <span style={{ color: '#000', fontSize: 14, fontWeight: 'bold', lineHeight: 1 }}></span>}
</div>
<span style={{ color: captchaDone ? C.success : 'rgba(255,255,255,0.6)', fontFamily: 'monospace', fontSize: 12 }}>
Artikel ist sichtbar CAPTCHA gelöst
</span>
</div> </div>
</div> </div>
)} )}
{/* PDF-Schritt */} {/* Schritt 2: PDF erstellen */}
{status === 'ready' && ( {status === 'open_archive' && captchaDone && (
<div style={{ ...S.card, borderColor: `${C.success}55`, marginBottom: 16 }}> <div style={{ ...S.card, borderColor: `${C.success}55`, marginBottom: 16 }}>
<div style={{ ...S.head, marginBottom: 8 }}>SCHRITT 2 PDF ERSTELLEN</div> <div style={{ ...S.head, marginBottom: 8 }}>
<span style={{ background: `${C.success}20`, border: `1px solid ${C.success}44`, borderRadius: '50%', width: 18, height: 18, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 10, marginRight: 8 }}>2</span>
PDF ERSTELLEN
</div>
<div style={{ color: C.muted, fontSize: 11, fontFamily: 'monospace', marginBottom: 12, lineHeight: 1.6 }}> <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. Der Server lädt den Artikel über archive.ph und erstellt ein PDF (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> </div>
<button onClick={handlePdf} disabled={pdfLoading}
style={{ ...S.btn(C.success), width: '100%', fontSize: 13, opacity: pdfLoading ? 0.6 : 1, cursor: pdfLoading ? 'default' : 'pointer' }}>
{pdfLoading ? '⏳ PDF wird erstellt...' : '⬇ Als PDF speichern'}
</button>
{pdfLoading && ( {pdfLoading && (
<div style={{ color: C.muted, fontSize: 11, fontFamily: 'monospace', marginTop: 8 }}> <div style={{ color: C.muted, fontSize: 11, fontFamily: 'monospace', marginTop: 8, textAlign: 'center' }}>
Seite wird serverseitig gerendert (1530s)... Seite wird gerendert, bitte warten...
</div> </div>
)} )}
</div> </div>
)} )}
{/* Fehler */}
{errMsg && ( {errMsg && (
<div style={{ ...S.card, borderColor: `${C.error}44`, marginTop: 8 }}> <div style={{ ...S.card, borderColor: `${C.error}44`, marginBottom: 12 }}>
<div style={{ display: 'flex', gap: 8 }}> <div style={{ display: 'flex', gap: 8, alignItems: 'flex-start' }}>
<span></span> <span></span>
<span style={{ color: C.error, fontFamily: 'monospace', fontSize: 12 }}>{errMsg}</span> <span style={{ color: C.error, fontFamily: 'monospace', fontSize: 12 }}>{errMsg}</span>
</div> </div>
</div> </div>
)} )}
{/* Idle-Info */}
{status === 'idle' && ( {status === 'idle' && (
<div style={{ ...S.card }}> <div style={{ ...S.card }}>
<div style={{ ...S.head, marginBottom: 10 }}>SO FUNKTIONIERT ES</div> <div style={{ ...S.head, marginBottom: 10 }}>SO FUNKTIONIERT ES</div>
{[ {[
['1', 'URL des Artikels einfügen'], ['1', 'URL des gesperrten Artikels einfügen'],
['2', 'archive.ph wird geladen CAPTCHA einmalig bestätigen'], ['2', 'archive.ph im Browser öffnen, CAPTCHA einmalig lösen'],
['3', 'Wenn der Artikel sichtbar ist: "PDF erstellen" klicken'], ['3', 'Bestätigen dass der Artikel sichtbar ist'],
['4', 'PDF wird automatisch heruntergeladen'], ['4', 'PDF automatisch herunterladen'],
].map(([num, text]) => ( ].map(([num, text]) => (
<div key={num} style={{ display: 'flex', gap: 10, alignItems: 'flex-start', marginBottom: 8 }}> <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={{ 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>