Skip to content

Instantly share code, notes, and snippets.

@durkes
Last active February 13, 2026 02:39
Show Gist options
  • Select an option

  • Save durkes/e3f95c76530df0a0cb3f2278f32c9ffc to your computer and use it in GitHub Desktop.

Select an option

Save durkes/e3f95c76530df0a0cb3f2278f32c9ffc to your computer and use it in GitHub Desktop.
Script to delete all X (Twitter) posts and replies
/* While signed in, navigate to your Profile > Replies page
and run the following script from DevTools Console */
(() => {
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
const normalize = (s) => (s || "").replace(/\s+/g, " ").trim().toLowerCase();
const state = {
running: false,
processed: new Set(),
deleted: 0,
undidReposts: 0,
skipped: 0,
step: 0,
noNewRounds: 0,
lastProcessedCount: 0,
lastTargetKey: null
};
const getArticleKey = (article) => {
const link = article.querySelector('a[href*="/status/"]')?.href;
if (link) return link;
const dt = article.querySelector("time")?.getAttribute("datetime") || "";
const txt = article.innerText?.slice(0, 120) || "";
return normalize(dt + "|" + txt);
};
const findCaretButton = (article) => {
return article.querySelector('[data-testid="caret"]');
};
const findRepostButton = (article) => {
const buttons = article.querySelectorAll('button[data-testid="unretweet"], button[aria-label*="Repost"], button[aria-label*="repost"]');
for (const btn of buttons) {
const label = normalize(btn.getAttribute("aria-label") || btn.innerText || "");
if (label.includes("repost") || label.includes("reposted") || label.includes("undo") || label.includes("remove")) {
return btn;
}
}
return null;
};
const isRepost = (article) => {
return Array.from(article.querySelectorAll('*')).some(el =>
normalize(el.innerText).includes("you reposted") || normalize(el.innerText).includes("reposted")
);
};
const highlight = (article) => {
article.style.outline = "3px solid red";
article.style.outlineOffset = "4px";
};
const clearHighlights = () => {
document.querySelectorAll("article").forEach(a => {
a.style.outline = "";
a.style.outlineOffset = "";
});
};
const findMenuItem = (textIncludes) => {
const nodes = Array.from(document.querySelectorAll("[role='menuitem']"));
for (const n of nodes) {
const t = normalize(n.innerText);
if (t.includes(textIncludes)) return n;
}
return null;
};
const findConfirmButton = () => {
// Primary: reliable data-testid used in all recent scripts
let btn = document.querySelector('[data-testid="confirmationSheetConfirm"]');
if (btn) return btn;
// Fallback: look for button with "Delete" text (red button)
const buttons = document.querySelectorAll('button, [role="button"]');
for (const b of buttons) {
if (normalize(b.innerText) === "delete") {
return b;
}
}
return null;
};
const findNextTarget = () => {
const articles = Array.from(document.querySelectorAll("article"));
for (const article of articles) {
const key = getArticleKey(article);
if (!key || state.processed.has(key)) continue;
const caret = findCaretButton(article);
const repostBtn = findRepostButton(article);
if (repostBtn || caret) {
return { article, caret, repostBtn, key, isRepost: !!repostBtn && isRepost(article) };
}
}
return null;
};
const slowScrollLoad = async () => {
window.scrollTo(0, document.body.scrollHeight);
await sleep(2000);
};
const runStep = async () => {
state.step++;
clearHighlights();
const t = findNextTarget();
if (!t) return false;
state.lastTargetKey = t.key;
highlight(t.article);
t.article.scrollIntoView({ behavior: "smooth", block: "center" });
await sleep(1500);
let success = false;
if (t.repostBtn && t.isRepost) {
console.log(`[step ${state.step}] Undoing repost...`);
t.repostBtn.click();
await sleep(1200);
for (let i = 0; i < 3; i++) {
const undoItem = findMenuItem("undo repost") || findMenuItem("remove repost");
if (undoItem) {
undoItem.click();
await sleep(2000);
state.undidReposts++;
success = true;
break;
}
await sleep(800);
}
} else if (t.caret) {
console.log(`[step ${state.step}] Deleting own post/reply...`);
t.caret.click();
await sleep(1200);
for (let i = 0; i < 3; i++) {
const deleteItem = findMenuItem("delete");
if (deleteItem) {
deleteItem.click();
await sleep(1500); // Slightly longer wait for confirmation dialog
for (let j = 0; j < 5; j++) { // More retries for confirmation
const confirmBtn = findConfirmButton();
if (confirmBtn) {
confirmBtn.click();
console.log("Confirmation 'Delete' button clicked.");
await sleep(2500);
state.deleted++;
success = true;
break;
}
await sleep(800);
}
break;
}
await sleep(800);
}
}
if (success) {
state.processed.add(t.key);
return true;
} else {
console.warn("Failed to process this post. Skipping to avoid infinite loop.");
state.skipped++;
state.processed.add(t.key);
return true;
}
};
const loop = async () => {
if (state.running) return;
state.running = true;
console.log("Running full cleanup: deleting posts/replies + undoing reposts. Use window.__cleanupHelper.stop() to stop.");
while (state.running) {
const did = await runStep();
if (did) continue;
await slowScrollLoad();
if (state.processed.size === state.lastProcessedCount) state.noNewRounds++;
else state.noNewRounds = 0;
state.lastProcessedCount = state.processed.size;
if (state.noNewRounds >= 8) {
console.log("No more posts found after extensive scrolling. Done!");
console.log(`Stats: Deleted: ${state.deleted}, Undid reposts: ${state.undidReposts}, Skipped: ${state.skipped}`);
break;
}
}
state.running = false;
};
window.__cleanupHelper = {
start: () => loop(),
stop: () => { state.running = false; console.log("Stopped."); },
stats: () => ({
steps: state.step,
deleted: state.deleted,
undidReposts: state.undidReposts,
skipped: state.skipped,
processed: state.processed.size,
lastTarget: state.lastTargetKey
})
};
loop();
})();
@stiegosaurus
Copy link
Copy Markdown

Well done! This is working as of 2026-02-12

@durkes
Copy link
Copy Markdown
Author

durkes commented Feb 13, 2026

Excellent 💪

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment