refactor: Paywall-Killer nur Browser-Flow, kein Server mehr
This commit is contained in:
@@ -9,13 +9,10 @@ const C = {
|
||||
};
|
||||
|
||||
export default function PaywallKiller() {
|
||||
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);
|
||||
const [inputUrl, setInputUrl] = useState('');
|
||||
const [archiveUrl, setArchiveUrl] = useState('');
|
||||
const [done, setDone] = useState(false);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
function getTargetUrl() {
|
||||
const url = inputUrl.trim();
|
||||
@@ -25,37 +22,16 @@ export default function PaywallKiller() {
|
||||
function handleSearch() {
|
||||
if (!inputUrl.trim()) return;
|
||||
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();
|
||||
if (snap && snap.startsWith('https://archive.')) {
|
||||
const token = localStorage.getItem('sk_token') || '';
|
||||
return `/api/tools/paywallkiller/render?url=${encodeURIComponent(snap)}&token=${encodeURIComponent(token)}`;
|
||||
}
|
||||
return null;
|
||||
setDone(false);
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
setInputUrl('');
|
||||
setSnapshotUrl('');
|
||||
setStatus('idle');
|
||||
setArchiveUrl('');
|
||||
setCaptchaDone(false);
|
||||
setDone(false);
|
||||
setTimeout(() => inputRef.current?.focus(), 50);
|
||||
}
|
||||
|
||||
const renderUrl = getRenderUrl();
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px 16px', maxWidth: 640, margin: '0 auto' }}>
|
||||
|
||||
@@ -64,114 +40,91 @@ export default function PaywallKiller() {
|
||||
<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 · Bereinigt · Als PDF drucken</div>
|
||||
<div style={{ color: C.muted, fontSize: 11, fontFamily: 'monospace' }}>archive.ph · Als PDF speichern</div>
|
||||
</div>
|
||||
|
||||
{/* URL-Eingabe */}
|
||||
{/* 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()}
|
||||
onKeyDown={e => e.key === 'Enter' && 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 suchen
|
||||
</button>
|
||||
) : (
|
||||
<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 suchen
|
||||
</button>
|
||||
{archiveUrl && (
|
||||
<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>
|
||||
|
||||
{/* Schritt 1: archive.ph öffnen */}
|
||||
{(status === 'step1' || status === 'step2') && (
|
||||
<div style={{ ...S.card, borderColor: status === 'step2' ? `${C.success}44` : `${C.accent}55`, marginBottom: 12 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
|
||||
<span style={{ background: `${C.accent}20`, border: `1px solid ${C.accent}44`, borderRadius: '50%', width: 22, height: 22, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 11, color: C.accent, fontFamily: 'monospace', flexShrink: 0 }}>1</span>
|
||||
<span style={{ color: C.accent, fontFamily: 'monospace', fontSize: 12 }}>ARCHIVE.PH ÖFFNEN</span>
|
||||
{status === 'step2' && <span style={{ color: C.success, fontFamily: 'monospace', fontSize: 10 }}>✓ erledigt</span>}
|
||||
</div>
|
||||
{/* Ergebnis */}
|
||||
{archiveUrl && (
|
||||
<div style={{ ...S.card, borderColor: `${C.accent}55`, marginBottom: 12 }}>
|
||||
<div style={{ ...S.head, marginBottom: 12 }}>ARTIKEL ÖFFNEN & ALS PDF SPEICHERN</div>
|
||||
|
||||
{status === 'step1' && (
|
||||
<>
|
||||
<div style={{ color: C.muted, fontSize: 11, fontFamily: 'monospace', lineHeight: 1.7, marginBottom: 12 }}>
|
||||
Öffne archive.ph. Falls ein CAPTCHA erscheint: lösen und warten bis der Artikel vollständig sichtbar ist. Dann die <strong style={{ color: 'rgba(255,255,255,0.6)' }}>URL aus der Adressleiste</strong> kopieren – sie sieht dann so aus:<br/>
|
||||
<span style={{ color: C.accent }}>archive.ph/AbCdE</span> oder <span style={{ color: C.accent }}>archive.ph/20260608.../https://...</span>
|
||||
{/* Schritt 1 */}
|
||||
<div style={{ display: 'flex', gap: 10, marginBottom: 16 }}>
|
||||
<span style={{ background: `${C.accent}20`, border: `1px solid ${C.accent}44`, borderRadius: '50%', width: 22, height: 22, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 11, color: C.accent, fontFamily: 'monospace', flexShrink: 0, marginTop: 1 }}>1</span>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ color: 'rgba(255,255,255,0.7)', fontFamily: 'monospace', fontSize: 11, marginBottom: 8 }}>
|
||||
Öffne archive.ph – falls CAPTCHA erscheint einmalig lösen:
|
||||
</div>
|
||||
<a href={archiveUrl} target="_blank" rel="noreferrer"
|
||||
style={{ ...S.btn(C.accent), display: 'block', textAlign: 'center', textDecoration: 'none', boxSizing: 'border-box', fontSize: 13, marginBottom: 12 }}>
|
||||
🌐 archive.ph öffnen
|
||||
style={{ ...S.btn(C.accent), display: 'block', textAlign: 'center', textDecoration: 'none', boxSizing: 'border-box', fontSize: 13 }}>
|
||||
🌐 Artikel auf archive.ph öffnen
|
||||
</a>
|
||||
<button onClick={handleCaptchaDone}
|
||||
style={{ ...S.btn(C.success), width: '100%', fontSize: 12 }}>
|
||||
✅ Artikel ist sichtbar → weiter
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Schritt 2: Snapshot-URL einfügen + bereinigt öffnen */}
|
||||
{status === 'step2' && (
|
||||
<div style={{ ...S.card, borderColor: `${C.success}55`, marginBottom: 12 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
|
||||
<span style={{ background: `${C.success}20`, border: `1px solid ${C.success}44`, borderRadius: '50%', width: 22, height: 22, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 11, color: C.success, fontFamily: 'monospace', flexShrink: 0 }}>2</span>
|
||||
<span style={{ color: C.success, fontFamily: 'monospace', fontSize: 12 }}>SNAPSHOT-URL EINFÜGEN</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ color: C.muted, fontSize: 11, fontFamily: 'monospace', lineHeight: 1.6, marginBottom: 10 }}>
|
||||
Kopiere die URL aus der Adressleiste von archive.ph und füge sie hier ein:
|
||||
</div>
|
||||
|
||||
<input
|
||||
ref={snapshotRef}
|
||||
value={snapshotUrl}
|
||||
onChange={e => setSnapshotUrl(e.target.value)}
|
||||
placeholder="https://archive.ph/AbCdE"
|
||||
style={{ ...S.inp, width: '100%', boxSizing: 'border-box', marginBottom: 12, fontSize: 12 }}
|
||||
/>
|
||||
|
||||
{renderUrl ? (
|
||||
<>
|
||||
<a href={renderUrl} target="_blank" rel="noreferrer"
|
||||
style={{ ...S.btn(C.success), display: 'block', textAlign: 'center', textDecoration: 'none', boxSizing: 'border-box', fontSize: 13, marginBottom: 10 }}>
|
||||
🧹 Bereinigt öffnen (ohne Banner & Archiv-Links)
|
||||
</a>
|
||||
<div style={{ padding: '8px 12px', background: 'rgba(255,255,255,0.02)', borderRadius: 6, border: '1px solid rgba(255,255,255,0.06)' }}>
|
||||
<div style={{ color: 'rgba(255,255,255,0.4)', fontFamily: 'monospace', fontSize: 10, marginBottom: 4 }}>DANN ALS PDF DRUCKEN</div>
|
||||
<div style={{ color: C.muted, fontFamily: 'monospace', fontSize: 10, lineHeight: 1.6 }}>
|
||||
📱 Android: Menü → Teilen → Drucken → Als PDF · Kopf-/Fußzeilen aus{'\n'}
|
||||
🍎 iOS: Teilen → Als PDF sichern{'\n'}
|
||||
💻 Desktop: Strg+P → Als PDF · Kopf-/Fußzeilen aus
|
||||
{/* Schritt 2 */}
|
||||
<div style={{ display: 'flex', gap: 10 }}>
|
||||
<span style={{ background: `${C.success}20`, border: `1px solid ${C.success}44`, borderRadius: '50%', width: 22, height: 22, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 11, color: C.success, fontFamily: 'monospace', flexShrink: 0, marginTop: 1 }}>2</span>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ color: 'rgba(255,255,255,0.7)', fontFamily: 'monospace', fontSize: 11, marginBottom: 8 }}>
|
||||
Wenn der Artikel sichtbar ist, als PDF speichern:
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
{[
|
||||
['📱', 'Android Chrome', 'Menü (⋮) → Teilen → Drucken → Als PDF · Kopf-/Fußzeilen: aus'],
|
||||
['🍎', 'iOS Safari', 'Teilen-Button → Als PDF sichern'],
|
||||
['💻', 'Desktop', 'Strg+P / Cmd+P → Als PDF · Kopf-/Fußzeilen: aus'],
|
||||
].map(([icon, label, desc]) => (
|
||||
<div key={label} style={{ padding: '8px 10px', background: 'rgba(255,255,255,0.03)', borderRadius: 6, border: '1px solid rgba(255,255,255,0.07)' }}>
|
||||
<div style={{ color: 'rgba(255,255,255,0.6)', fontFamily: 'monospace', fontSize: 11, marginBottom: 2 }}>{icon} {label}</div>
|
||||
<div style={{ color: C.muted, fontFamily: 'monospace', fontSize: 10, lineHeight: 1.5 }}>{desc}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ marginTop: 10, padding: '8px 10px', background: 'rgba(255,255,255,0.02)', borderRadius: 6, border: '1px solid rgba(255,255,255,0.05)' }}>
|
||||
<div style={{ color: 'rgba(255,255,255,0.3)', fontFamily: 'monospace', fontSize: 10, lineHeight: 1.5 }}>
|
||||
ℹ️ Der archive.ph-Balken oben ist Teil der archivierten Seite und kann nicht automatisch entfernt werden. Im Druckdialog "Kopf-/Fußzeilen" deaktivieren hält das PDF sauber.
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div style={{ color: C.muted, fontSize: 11, fontFamily: 'monospace', opacity: 0.5 }}>
|
||||
Snapshot-URL oben einfügen um fortzufahren...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status === 'idle' && (
|
||||
{/* Idle-Info */}
|
||||
{!archiveUrl && (
|
||||
<div style={{ ...S.card }}>
|
||||
<div style={{ ...S.head, marginBottom: 10 }}>SO FUNKTIONIERT ES</div>
|
||||
{[
|
||||
['1', 'URL des gesperrten Artikels einfügen'],
|
||||
['2', 'archive.ph öffnen, CAPTCHA einmalig lösen'],
|
||||
['3', 'URL aus Adressleiste kopieren und hier einfügen'],
|
||||
['4', 'Bereinigt öffnen (kein Banner, normale Links) → als PDF drucken'],
|
||||
['2', 'Artikel auf archive.ph öffnen'],
|
||||
['3', 'CAPTCHA einmalig lösen (danach dauerhaft gespeichert)'],
|
||||
['4', 'Über Drucken-Funktion als PDF speichern'],
|
||||
].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>
|
||||
|
||||
Reference in New Issue
Block a user