192 lines
9.5 KiB
JavaScript
192 lines
9.5 KiB
JavaScript
import { useState, useRef } from 'react';
|
||
import { S } from '../lib.js';
|
||
|
||
const C = {
|
||
accent: '#f59e0b',
|
||
success: '#4ade80',
|
||
error: '#f87171',
|
||
muted: '#6b7280',
|
||
};
|
||
|
||
export default function PaywallKiller() {
|
||
const [inputUrl, setInputUrl] = useState('');
|
||
const [archiveUrl, setArchiveUrl] = useState('');
|
||
const [status, setStatus] = useState('idle');
|
||
const inputRef = useRef(null);
|
||
|
||
function getTargetUrl() {
|
||
let url = inputUrl.trim();
|
||
if (!url.startsWith('http')) url = 'https://' + url;
|
||
// Mobile Subdomains auf Desktop normalisieren (m.bild.de → www.bild.de)
|
||
// damit archive.ph den richtigen Snapshot findet
|
||
try {
|
||
const u = new URL(url);
|
||
if (u.hostname.startsWith('m.')) {
|
||
u.hostname = 'www.' + u.hostname.slice(2);
|
||
url = u.toString();
|
||
} else if (u.hostname.startsWith('mobile.')) {
|
||
u.hostname = 'www.' + u.hostname.slice(7);
|
||
url = u.toString();
|
||
}
|
||
} catch {}
|
||
return url;
|
||
}
|
||
|
||
function handleSearch() {
|
||
if (!inputUrl.trim()) return;
|
||
const targetUrl = getTargetUrl();
|
||
|
||
// archive.ph Submit (no-cors, fire-and-forget) – triggert Archivierung
|
||
try {
|
||
const formData = new FormData();
|
||
formData.append('url', targetUrl);
|
||
formData.append('anyway', '1');
|
||
fetch('https://archive.ph/submit/', {
|
||
method: 'POST',
|
||
mode: 'no-cors',
|
||
body: formData,
|
||
}).catch(() => {});
|
||
} catch {}
|
||
|
||
setArchiveUrl(`https://archive.ph/newest/${targetUrl}`);
|
||
setStatus('ready');
|
||
}
|
||
|
||
function handleReset() {
|
||
setInputUrl('');
|
||
setArchiveUrl('');
|
||
setStatus('idle');
|
||
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', marginBottom: 10 }}>archive.ph · Als PDF speichern</div>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
|
||
<span style={{ color: 'rgba(255,255,255,0.25)', fontFamily: 'monospace', fontSize: 9, letterSpacing: 0.5 }}>GETESTET MIT</span>
|
||
{[
|
||
{ name: 'waz.de', domain: 'waz.de' },
|
||
{ name: 'bild.de', domain: 'bild.de' },
|
||
{ name: 'heise.de', domain: 'heise.de' },
|
||
].map(({ name, domain }) => (
|
||
<div key={name} style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '2px 7px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 4 }}>
|
||
<img src={`https://www.google.com/s2/favicons?domain=${domain}&sz=16`} width={12} height={12} style={{ borderRadius: 2, opacity: 0.7 }} alt="" />
|
||
<span style={{ color: 'rgba(255,255,255,0.35)', fontFamily: 'monospace', fontSize: 9 }}>{name}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 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 suchen
|
||
</button>
|
||
) : (
|
||
<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>
|
||
|
||
{/* Ergebnis */}
|
||
{status === 'ready' && (
|
||
<div style={{ ...S.card, borderColor: `${C.accent}55`, marginBottom: 12 }}>
|
||
<div style={{ ...S.head, marginBottom: 12 }}>ARTIKEL ÖFFNEN & ALS PDF SPEICHERN</div>
|
||
|
||
{/* Schritt 1 */}
|
||
<div style={{ display: 'flex', gap: 10, marginBottom: 4 }}>
|
||
<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 den Artikel auf archive.ph. Falls CAPTCHA erscheint: einmalig lösen. Falls "No results": auf <strong style={{ color: 'rgba(255,255,255,0.5)' }}>"archive this url"</strong> klicken, kurz warten, dann nochmal öffnen.
|
||
</div>
|
||
<a href={archiveUrl} target="_blank" rel="noreferrer"
|
||
style={{ ...S.btn(C.accent), display: 'block', textAlign: 'center', textDecoration: 'none', boxSizing: 'border-box', fontSize: 13 }}>
|
||
🌐 Artikel auf archive.ph öffnen
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Nochmal öffnen nach Archivierung */}
|
||
<div style={{ margin: '10px 0 16px 32px', padding: '8px 12px', background: 'rgba(255,255,255,0.03)', borderRadius: 6, border: '1px solid rgba(255,255,255,0.07)' }}>
|
||
<div style={{ color: C.muted, fontSize: 10, fontFamily: 'monospace', marginBottom: 6 }}>
|
||
Nach "archive this url" ~15–30s warten, dann:
|
||
</div>
|
||
<a href={archiveUrl} target="_blank" rel="noreferrer"
|
||
style={{ ...S.btn(C.muted), display: 'inline-block', textDecoration: 'none', fontSize: 11 }}>
|
||
🔄 Nochmal öffnen
|
||
</a>
|
||
</div>
|
||
|
||
{/* 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.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Idle-Info */}
|
||
{status === 'idle' && (
|
||
<div style={{ ...S.card }}>
|
||
<div style={{ ...S.head, marginBottom: 10 }}>SO FUNKTIONIERT ES</div>
|
||
{[
|
||
['1', 'URL des gesperrten Artikels einfügen'],
|
||
['2', 'Artikel auf archive.ph öffnen'],
|
||
['3', 'Falls nicht archiviert: "archive this url" klicken, kurz warten, nochmal öffnen'],
|
||
['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>
|
||
<span style={{ color: C.muted, fontSize: 11, fontFamily: 'monospace', lineHeight: 1.6 }}>{text}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|