Skip to content

Instantly share code, notes, and snippets.

@ShabbirHasan1
Forked from jbt95/remove-retweets.js
Created March 16, 2026 20:42
Show Gist options
  • Select an option

  • Save ShabbirHasan1/541ef9ae5359b78c04579609b91b69be to your computer and use it in GitHub Desktop.

Select an option

Save ShabbirHasan1/541ef9ae5359b78c04579609b91b69be to your computer and use it in GitHub Desktop.
Remove retweets from profile
(() => {
// ========= CONFIG =========
const MAX_ACTIONS = 2000; // safety cap
const MAX_SCROLLS = 400; // safety cap
const MIN_DELAY_MS = 600; // slow down to look more human
const MAX_DELAY_MS = 1400;
const SCROLL_STEP_PX = 900;
// ========= HELPERS =========
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
const visible = (el) => {
if (!el) return false;
const style = window.getComputedStyle(el);
const rect = el.getBoundingClientRect();
return (
style &&
style.visibility !== "hidden" &&
style.display !== "none" &&
rect.width > 0 &&
rect.height > 0
);
};
const click = (el) => {
if (!el || !visible(el)) return false;
el.dispatchEvent(new MouseEvent("mouseover", { bubbles: true }));
el.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
el.click();
el.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));
return true;
};
const findUndoButtonsInArticle = (article) => {
// Most common X selectors:
// - button[data-testid="unretweet"] (older)
// - button[aria-label*="Undo repost"] / "Undo repost" / "Undo Retweet"
// - sometimes "Repost" state toggles; try multiple signals.
const candidates = Array.from(article.querySelectorAll('button, div[role="button"]')).filter(visible);
return candidates.filter((el) => {
const dt = (el.getAttribute("data-testid") || "").toLowerCase();
const aria = (el.getAttribute("aria-label") || "").toLowerCase();
const txt = (el.innerText || "").trim().toLowerCase();
return (
dt.includes("unretweet") ||
dt.includes("unrepost") ||
aria.includes("undo repost") ||
aria.includes("undo retweet") ||
txt === "undo repost" ||
txt === "undo retweet"
);
});
};
const waitForConfirmButton = async (timeoutMs = 2500) => {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
// common confirm testid
const btn =
document.querySelector('[data-testid="unretweetConfirm"]') ||
document.querySelector('[data-testid="unrepostConfirm"]') ||
// fallback: a visible button containing the expected text
Array.from(document.querySelectorAll("button, div[role='button']")).find((el) => {
if (!visible(el)) return false;
const t = (el.innerText || "").trim().toLowerCase();
return t === "undo repost" || t === "undo retweet" || t === "remove repost" || t === "unretweet";
});
if (btn && visible(btn)) return btn;
await sleep(100);
}
return null;
};
// ========= MAIN =========
let actions = 0;
let scrolls = 0;
const seen = new Set();
window.__UNRETWEET_STOP__ = false;
window.stopUnretweet = () => {
window.__UNRETWEET_STOP__ = true;
console.log("🛑 Stop requested. The script will stop after the current action.");
};
const run = async () => {
console.log("Starting… Open your profile tab where reposts appear. Call stopUnretweet() to stop.");
while (!window.__UNRETWEET_STOP__ && actions < MAX_ACTIONS && scrolls < MAX_SCROLLS) {
const articles = Array.from(document.querySelectorAll("article")).filter(visible);
let didSomething = false;
for (const article of articles) {
if (window.__UNRETWEET_STOP__ || actions >= MAX_ACTIONS) break;
// Use a stable-ish key so we don't re-process the same tweet forever
const statusLink = article.querySelector('a[href*="/status/"]');
const key = statusLink?.getAttribute("href") || (article.innerText || "").slice(0, 120);
if (seen.has(key)) continue;
seen.add(key);
// Bring into view so X attaches handlers / loads UI properly
try { article.scrollIntoView({ block: "center" }); } catch {}
await sleep(rand(120, 260));
const undoButtons = findUndoButtonsInArticle(article);
if (!undoButtons.length) continue;
// Click the first undo/unretweet control we found
if (!click(undoButtons[0])) continue;
// Wait for confirm, click it
const confirm = await waitForConfirmButton(3000);
if (confirm) {
await sleep(rand(150, 350));
click(confirm);
actions += 1;
didSomething = true;
console.log(`Undid repost/retweet #${actions}: ${key}`);
await sleep(rand(MIN_DELAY_MS, MAX_DELAY_MS));
} else {
// If no confirm appears, back off a bit and continue
await sleep(rand(500, 900));
}
}
if (!didSomething) {
// Load more
window.scrollBy(0, SCROLL_STEP_PX);
scrolls += 1;
await sleep(rand(900, 1500));
}
}
console.log(
`Done. actions=${actions}, scrolls=${scrolls}, stopped=${window.__UNRETWEET_STOP__}`
);
};
run();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment