-
-
Save ShabbirHasan1/541ef9ae5359b78c04579609b91b69be to your computer and use it in GitHub Desktop.
Remove retweets from profile
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (() => { | |
| // ========= 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