Tweets massenhaft löschen, ganz ohne Erweiterung
Ein kostenloses Skript zum Einfügen in die Chrome-Konsole, um in Ihren alten X-Posts aufzuräumen: erst Simulation, aktuelle Tweets geschützt, stapelweises Löschen mit Obergrenze. Keine Erweiterung, kein fremder Dienst, nichts verlässt Ihren Browser.
DRY_RUN = true): Es listet auf, was gelöscht würde, ohne etwas anzurühren. Laden Sie zuerst Ihr X-Archiv herunter, prüfen Sie die Simulation und stellen Sie erst danach auf das echte Löschen um. Die Automatisierung der Oberfläche ist in den Nutzungsbedingungen von X nicht vorgesehen: Gehen Sie behutsam vor, auf eigenes Risiko.
Die Vorgehensweise
- Öffnen Sie Ihr Profil: Rufen Sie
x.com/dein_nutzernameauf, Reiter Posts, mit Ihrem Konto angemeldet. - Öffnen Sie die Konsole: Drücken Sie
F12(oderCmd+Option+Jauf dem Mac) und klicken Sie dann auf den Reiter Konsole. - Konfigurieren Sie das Skript: Kopieren Sie den Code unten, ersetzen Sie
dein_nutzernamedurch Ihre X-Kennung (ohne @), passen SieKEEP_RECENTan (Anzahl der aktuellen Tweets, die behalten werden) und tragen Sie bei Bedarf inKEEP_STATUS_IDSdie Kennungen der Tweets ein, die nie gelöscht werden sollen. - Simulieren Sie: Fügen Sie das Skript in die Konsole ein und bestätigen Sie mit der Eingabetaste. Mit
DRY_RUN = true(der Standardeinstellung) zeigt es die Liste der Tweets an, die gelöscht würden, ohne etwas zu löschen. - Löschen Sie: Wenn Ihnen die Simulation zusagt, stellen Sie
DRY_RUNauffalseund starten Sie neu. Das Skript löscht höchstens 40 Tweets pro Durchlauf, mit Pausen von 6 bis 14 Sekunden. - Wiederholen Sie: Starten Sie das Skript so oft wie nötig, um den Rest stapelweise abzuarbeiten.
Das Skript
(() => {
// ============ KONFIGURATION ============
const HANDLE = "dein_nutzername"; // deine X-Kennung, ohne @
const KEEP_RECENT = 15; // loescht NIE die 15 neuesten Tweets (ganz oben)
const KEEP_STATUS_IDS = [ // ZUSAETZLICHER Schutz (optional): IDs, die nie geloescht werden
"1234567890123456789",
];
let DRY_RUN = true; // true = Simulation (nur Liste). false = ECHTES LOESCHEN.
const MIN_DELAY_MS = 6000;
const MAX_DELAY_MS = 14000;
const MAX_DELETIONS = 40; // Obergrenze pro Durchlauf (erneut starten, um fortzufahren)
// =======================================
const me = HANDLE.toLowerCase();
const KEEP = new Set(KEEP_STATUS_IDS.map(String));
const protectedRecent = new Set();
const RX_DELETE = /(Löschen|delete|supprimer)/i;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const rand = (a, b) => Math.floor(a + Math.random() * (b - a));
const skipped = new Set();
if (!location.pathname.toLowerCase().startsWith("/" + me)) {
console.warn(`%c[STOPP] Gehe zu https://x.com/${HANDLE} (Reiter Posts) und starte erneut.`,
"color:#e0245e;font-weight:bold");
return;
}
const articles = () => [...document.querySelectorAll('article[data-testid="tweet"]')];
const idOf = (art) => {
const href = [...art.querySelectorAll('a[href*="/status/"]')]
.map((x) => x.getAttribute("href"))
.find((h) => new RegExp(`^/${HANDLE}/status/\\d+`, "i").test(h));
if (!href) return null;
const m = href.match(/status\/(\d+)/);
return m ? m[1] : null;
};
// Laedt und schuetzt die KEEP_RECENT neuesten Tweets (die ganz oben).
async function protectRecent() {
const order = [], seen = new Set();
let noNew = 0;
window.scrollTo(0, 0);
await sleep(1200);
while (order.length < KEEP_RECENT && noNew < 6) {
let added = 0;
for (const art of articles()) {
const id = idOf(art);
if (id && !seen.has(id)) { seen.add(id); order.push(id); added++; }
}
noNew = added ? 0 : noNew + 1;
if (order.length >= KEEP_RECENT) break;
const cs = articles();
(cs[cs.length - 1] || document.body).scrollIntoView({ block: "end" });
await sleep(1400);
}
for (const id of order.slice(0, KEEP_RECENT)) protectedRecent.add(id);
return order.slice(0, KEEP_RECENT);
}
const eligible = (art) => {
const id = idOf(art);
if (!id) return null;
if (protectedRecent.has(id)) return null; // einer der neuesten
if (KEEP.has(id)) return null; // zusaetzlich geschuetzte ID
if (skipped.has(id)) return null;
return id;
};
async function deleteOne(art, id) {
const caret = art.querySelector('[data-testid="caret"]');
if (!caret) { skipped.add(id); return false; }
caret.click();
await sleep(700);
const del = [...document.querySelectorAll('[role="menuitem"]')]
.find((i) => RX_DELETE.test((i.innerText || "").trim()));
if (!del) { document.body.click(); skipped.add(id); return false; }
del.click();
await sleep(700);
const confirmBtn = document.querySelector('[data-testid="confirmationSheetConfirm"]');
if (!confirmBtn) { skipped.add(id); return false; }
confirmBtn.click();
await sleep(1500);
return true;
}
async function dryScan() {
const seen = new Set();
let noNew = 0;
console.log("%c[SIMULATION] Es wird nichts geloescht. Zaehle...", "color:#1d9bf0;font-weight:bold");
while (noNew < 4) {
let added = 0;
for (const art of articles()) {
const id = eligible(art);
if (id && !seen.has(id)) { seen.add(id); added++; }
}
noNew = added ? 0 : noNew + 1;
const cs = articles();
(cs[cs.length - 1] || document.body).scrollIntoView({ block: "end" });
await sleep(1200);
}
console.log(`%c[SIMULATION] ${seen.size} Tweet(s) WUERDEN geloescht (die neuesten sind geschuetzt).`,
"color:#1d9bf0;font-weight:bold");
console.log([...seen].slice(0, 60));
}
async function run() {
let count = 0, guard = 0, noHit = 0, started = false;
while (count < MAX_DELETIONS && guard++ < 1500) {
const pair = articles().map((a) => [a, eligible(a)]).find(([, id]) => id);
if (!pair) {
if ((started && noHit >= 6) || noHit >= 40) break;
const cs = articles();
(cs[cs.length - 1] || document.body).scrollIntoView({ block: "end" });
await sleep(1500);
noHit++;
continue;
}
started = true; noHit = 0;
const [node, id] = pair;
node.scrollIntoView({ block: "center" });
await sleep(600);
const ok = await deleteOne(node, id);
if (ok) count++;
console.log(`${ok ? "geloescht" : "ignoriert"} ${id} (gesamt: ${count}/${MAX_DELETIONS})`);
await sleep(rand(MIN_DELAY_MS, MAX_DELAY_MS));
}
console.log(`%c[ENDE] ${count} in dieser Runde geloescht. Starte das Skript erneut, falls noch welche uebrig sind.`,
"color:#00ba7c;font-weight:bold");
}
(async () => {
console.log(`Lade und schuetze die ${KEEP_RECENT} neuesten Tweets...`);
const recent = await protectRecent();
if (recent.length < KEEP_RECENT) {
console.warn(`%c[STOPP] Nur ${recent.length} Tweets geladen (X hat gedrosselt). Lade die Seite neu (F5) und versuche es erneut.`,
"color:#e0245e;font-weight:bold");
return;
}
console.log("%cGeschuetzt (die neuesten):", "color:#00ba7c;font-weight:bold", recent);
if (DRY_RUN) { await dryScan(); return; }
await run();
})();
})();
Wie das Skript Sie schützt
Vier Schutzvorkehrungen sind eingebaut. Zuerst die Simulation als Standard: Solange DRY_RUN auf true steht, wird nichts gelöscht, Sie erhalten nur die Liste der betroffenen Tweets. Dann der Schutz der aktuellen Tweets: Das Skript merkt sich zunächst Ihre neuesten Tweets (standardmäßig 15) und rührt sie nie an. Danach die Positivliste: Jede in KEEP_STATUS_IDS eingetragene Kennung ist unantastbar, praktisch für Ihre angehefteten Tweets oder Ankündigungen. Und schließlich das behutsame Tempo: zufällige Pausen von 6 bis 14 Sekunden zwischen jeder Löschung und automatischer Stopp nach 40, um die Beschränkungen von X nicht auszulösen.
Denken Sie vor dem großen Aufräumen daran, das Wichtige zu sichern: Der E-Mail- und URL-Extraktor holt alle Links aus einem Tweet-Export, der Wortzähler sagt Ihnen, wie viel Ihre Jahre voller Posts wogen, und der Textbereiniger bringt ein Kopieren-Einfügen aus dem Archiv wieder in Form. Und für Ihre künftigen Tweets stehen der stilisierte Unicode-Text und der Groß-/Kleinschreibungs-Konverter bereit. Alle Tools der Text-Toolbox laufen in Ihrem Browser, genau wie diese Anleitung, ganz im Sinne unserer Datenschutzerklärung.
Häufige Fragen
Ist das Löschen rückgängig zu machen?
Nein. Ein gelöschter Tweet ist endgültig weg, X bietet keinen Papierkorb. Deshalb wird das Skript im Simulationsmodus geliefert (DRY_RUN = true): Führen Sie es zunächst unverändert aus, um die Liste dessen zu erhalten, was gelöscht würde, prüfen Sie sie, und stellen Sie erst auf false um, wenn Sie sicher sind. Denken Sie auch daran, vorher Ihr X-Archiv herunterzuladen (Einstellungen, dann Dein Konto, dann Ein Archiv deiner Daten herunterladen).
Kann das Skript die Tweets anderer Personen löschen?
Nein. X zeigt die Schaltfläche Löschen nur bei Ihren eigenen Posts an: Das Skript kann nur auf dem Konto handeln, mit dem Sie angemeldet sind, auf Ihrer eigenen Profilseite. Es rührt weder die Antworten anderer noch irgendwelche fremden Inhalte an.
Warum eine Obergrenze von 40 Löschungen und zufällige Wartezeiten?
Um die Plattform zu schonen. Zu schnelle oder zu massenhafte Löschungen können die Beschränkungen von X auslösen oder sogar zu einer vorübergehenden Sperrung des Kontos führen. Das Skript wartet zwischen 6 und 14 Sekunden zwischen jeder Löschung und stoppt nach 40: Starten Sie es erneut, um stapelweise fortzufahren.
Ist das von X erlaubt?
Sie löschen Ihre eigenen Inhalte, was Ihr gutes Recht ist. Die Automatisierung der Weboberfläche ist jedoch in den Nutzungsbedingungen von X nicht vorgesehen: Verwenden Sie dieses Skript mit Maß und auf eigenes Risiko. Die offizielle Alternative ist das manuelle Löschen oder spezialisierte Dienste, die über die API arbeiten.
Wie schütze ich bestimmte einzelne Tweets?
Zwei Schutzmechanismen greifen zusammen: KEEP_RECENT behält automatisch Ihre neuesten Tweets (standardmäßig 15), und KEEP_STATUS_IDS akzeptiert eine Liste von Kennungen, die nie gelöscht werden sollen. Die Kennung eines Tweets ist die große Zahl am Ende seiner Adresse: x.com/nutzername/status/1234567890123456789.