Last active
October 25, 2025 21:38
-
-
Save gerwld/c68f28cf2f111c4517b98066faaa66a4 to your computer and use it in GitHub Desktop.
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
| /** | |
| * More robust Instagram "unlike all" script for the Likes activity page. | |
| * Paste on: https://www.instagram.com/your_activity/interactions/likes | |
| * | |
| * Note: Still brittle (depends on Instagram's DOM). Use at your own risk. | |
| */ | |
| ; (async function () { | |
| const DELETION_BATCH_SIZE = 9 | |
| const DELAY_BETWEEN_ACTIONS_MS = 1200 | |
| const DELAY_BETWEEN_WENTWRONG_MS = 3000000 | |
| const DELAY_BETWEEN_CHECKBOX_CLICKS_MS = 100 | |
| const MAX_RETRIES = 60 | |
| const delay = (ms) => new Promise((r) => setTimeout(r, ms)) | |
| const visible = (el) => { | |
| if (!el) return false | |
| const rect = el.getBoundingClientRect() | |
| return rect.width > 0 && rect.height > 0 | |
| } | |
| const waitFor = async (fn, timeout = 30_000, interval = 200) => { | |
| const start = Date.now() | |
| while (Date.now() - start < timeout) { | |
| try { | |
| const v = await fn() | |
| if (v) return v | |
| } catch (e) { } | |
| await delay(interval) | |
| } | |
| return null | |
| } | |
| // Try several heuristics to find the "Select" button | |
| const findSelectButton = () => { | |
| // 1) button with exact text "Select" (English required as your note said) | |
| // Use XPath to directly select the div | |
| const xpath = "/html/body/div[1]/div/div/div[2]/div/div/div[1]/div[1]/div[1]/section/main/div/article/div/div[2]/div/div/div[1]/div/div/div/div/div/div[2]/div[2]"; | |
| const byText = document.evaluate( | |
| xpath, | |
| document, | |
| null, | |
| XPathResult.FIRST_ORDERED_NODE_TYPE, | |
| null | |
| ).singleNodeValue; | |
| if (byText) return byText; | |
| // 2) button with aria-label "Select" | |
| const byAria = document.querySelector('button[aria-label="Select"], [role="button"][aria-label="Select"]') | |
| if (byAria) return byAria | |
| // 3) any button whose text contains "Select" (looser) | |
| const byContains = Array.from(document.querySelectorAll('button, [role="button"]')) | |
| .find((b) => b.innerText && b.innerText.toLowerCase().includes('select')) | |
| if (byContains) return byContains | |
| // 4) fallback: second role="button" (older heuristic your script used) | |
| const roleButtons = document.querySelectorAll('[role="button"]') | |
| if (roleButtons && roleButtons.length >= 2) return roleButtons[1] | |
| return null | |
| } | |
| // Collect candidate checkbox-like elements (many Instagram variants) | |
| const getCheckboxCandidates = () => { | |
| const candidates = [ | |
| ...document.querySelectorAll('[role="checkbox"]'), | |
| ...document.querySelectorAll('input[type="checkbox"]'), | |
| ...document.querySelectorAll('[aria-label="Toggle checkbox"]'), | |
| // some Instagram UIs use buttons with aria-checked | |
| ...Array.from(document.querySelectorAll('[aria-checked]')).filter((n) => n.getAttribute('aria-checked') !== null) | |
| ] | |
| // keep unique and visible ones | |
| return Array.from(new Set(candidates)).filter(visible) | |
| } | |
| const clickSafe = async (el) => { | |
| if (!el) throw new Error('Element not found to click') | |
| try { | |
| el.click() | |
| } catch (err) { | |
| // fallback: attempt dispatching mouse events | |
| const ev = document.createEvent('MouseEvents') | |
| ev.initEvent('click', true, true) | |
| el.dispatchEvent(ev) | |
| } | |
| } | |
| const unlikeSelectedPosts = async () => { | |
| try { | |
| // Unlike button might be labelled "Unlike" on each selected item or one central button | |
| // Try finding a central "Unlike" aria-label button first | |
| // XPath for the "Unlike" div | |
| const unlikeXPath = "/html/body/div[1]/div/div/div[2]/div/div/div[1]/div[1]/div[1]/section/main/div/article/div/div[2]/div/div/div[1]/div/div/div/div/div/div[4]/div/div/div[2]/div/div/div[2]/div/div/div/div"; | |
| // Use document.evaluate to get the element | |
| const unlikeBtn = document.evaluate( | |
| unlikeXPath, | |
| document, | |
| null, | |
| XPathResult.FIRST_ORDERED_NODE_TYPE, | |
| null | |
| ).singleNodeValue; | |
| // Example usage: click it if it exists | |
| if (unlikeBtn) { | |
| unlikeBtn.click(); | |
| } | |
| if (!unlikeBtn) { | |
| // sometimes it's a button with text "Unlike" | |
| unlikeBtn = Array.from(document.querySelectorAll('button, [role="button"]')) | |
| .find((b) => b.innerText && b.innerText.trim().toLowerCase() === 'unlike') | |
| } | |
| if (!unlikeBtn) { | |
| console.warn('Could not find a central "Unlike" button; trying to click individual "Unlike" icons...') | |
| // fallback: try to click all visible unlike icons inside items | |
| const itemUnlikes = Array.from(document.querySelectorAll('[aria-label="Unlike"], button[aria-label="Unlike"]')) | |
| .filter(visible) | |
| for (const btn of itemUnlikes) { | |
| await clickSafe(btn) | |
| await delay(DELAY_BETWEEN_ACTIONS_MS) | |
| } | |
| return | |
| } | |
| await clickSafe(unlikeBtn) | |
| await delay(DELAY_BETWEEN_ACTIONS_MS) | |
| // Confirm dialog (if present) | |
| const confirmBtn = Array.from(document.querySelectorAll('button, [role="button"]')) | |
| .find((b) => b.innerText && b.innerText.trim().toLowerCase() === 'confirm') | |
| if (confirmBtn) { | |
| await clickSafe(confirmBtn) | |
| } else { | |
| // some UIs use the first button with tabindex=0 as confirm — try that as fallback | |
| const t0 = document.querySelector('button[tabindex="0"]') | |
| if (t0) await clickSafe(t0) | |
| } | |
| await delay(DELAY_BETWEEN_ACTIONS_MS) | |
| } catch (err) { | |
| console.error('Error while unliking selected posts:', err) | |
| } | |
| } | |
| // Scroll the activity container to load more items (best-effort) | |
| const scrollActivity = async () => { | |
| // try to find the scrollable container | |
| const scrollers = Array.from(document.querySelectorAll('div')).filter((d) => { | |
| const s = getComputedStyle(d) | |
| return (s.overflow === 'auto' || s.overflowY === 'auto' || s.overflowY === 'scroll') && d.scrollHeight > d.clientHeight | |
| }) | |
| const container = scrollers[0] || document.scrollingElement || document.body | |
| container.scrollBy({ top: 1000, behavior: 'smooth' }) | |
| await delay(800) | |
| } | |
| const deleteActivity = async () => { | |
| while (true) { | |
| console.log('--- New batch attempt ---') | |
| // 1) find Select button | |
| const selectBtn = findSelectButton() | |
| if (!selectBtn) { | |
| console.warn('Select button not found. Will attempt to scroll and retry.') | |
| // maybe more items are below; scroll and retry a few times | |
| await scrollActivity() | |
| const found = await waitFor(() => findSelectButton(), 5000, 500) | |
| if (!found) { | |
| console.log('No "Select" button found after scrolling; assuming no more likes to remove.') | |
| break | |
| } | |
| } | |
| try { | |
| const btnToClick = findSelectButton() | |
| console.log('Clicking Select button...') | |
| await clickSafe(btnToClick) | |
| } catch (err) { | |
| console.error('Failed to click Select button:', err) | |
| break | |
| } | |
| // 2) wait for checkboxes to appear (try multiple heuristics) | |
| const boxes = await waitFor(() => { | |
| const arr = getCheckboxCandidates() | |
| return arr.length ? arr : null | |
| }, 5000, 300) | |
| if (!boxes || boxes.length === 0) { | |
| console.warn('No checkboxes appeared after opening selection. Attempting to scroll and re-check...') | |
| // try scrolling the activity container and re-check | |
| await scrollActivity() | |
| await delay(800) | |
| const boxes2 = getCheckboxCandidates() | |
| if (!boxes2 || boxes2.length === 0) { | |
| // Check for an error popup (rate-limit or "went wrong") | |
| const possibleError = Array.from(document.querySelectorAll('div, button')) | |
| .find((n) => (n.innerText && /went wrong|error|try again/i.test(n.innerText)) || n.className && /_a9--|_ap36|_a9_1/.test(n.className)) | |
| if (possibleError) { | |
| console.warn('Error/rate-limit dialog detected. Clicking it (if clickable) and pausing...') | |
| try { await clickSafe(possibleError) } catch (e) { } | |
| console.log(`Pausing for ${DELAY_BETWEEN_WENTWRONG_MS / 1000}s before retry.`) | |
| await delay(DELAY_BETWEEN_WENTWRONG_MS) | |
| continue // restart loop | |
| } else { | |
| console.log('No checkboxes found and no obvious error dialog. Assuming no more likes to remove.') | |
| break | |
| } | |
| } | |
| } | |
| // Recompute checkboxes after any scrolling attempts | |
| const checkboxes = getCheckboxCandidates() | |
| console.log('Checkboxes found:', checkboxes.length) | |
| if (!checkboxes || checkboxes.length === 0) { | |
| console.log('No checkboxes to select; ending.') | |
| break | |
| } | |
| // 3) Select up to DELETION_BATCH_SIZE items (skip first two if you still want that behavior) | |
| const startIndex = 2 // keep original behavior: skip first two entries | |
| const endIndex = Math.min(startIndex + DELETION_BATCH_SIZE, checkboxes.length) | |
| const toClick = checkboxes.slice(startIndex, endIndex) | |
| console.log(`Selecting ${toClick.length} items (indexes ${startIndex}..${endIndex - 1})`) | |
| for (const cb of toClick) { | |
| try { | |
| await clickSafe(cb) | |
| } catch (err) { | |
| console.debug('Failed clicking checkbox candidate:', err) | |
| } | |
| await delay(DELAY_BETWEEN_CHECKBOX_CLICKS_MS) | |
| } | |
| // 4) Click unlike/delete for selected items | |
| await delay(DELAY_BETWEEN_ACTIONS_MS) | |
| await unlikeSelectedPosts() | |
| // 5) allow UI to refresh and wait for select button to reappear (or loop continues) | |
| await delay(DELAY_BETWEEN_ACTIONS_MS) | |
| await waitFor(() => findSelectButton(), 5000, 500) | |
| await delay(DELAY_BETWEEN_ACTIONS_MS) | |
| } | |
| } | |
| try { | |
| console.log('Starting improved Instagram unlike script...') | |
| await deleteActivity() | |
| console.log('Finished running. If you still have likes, the DOM selectors likely need updating.') | |
| } catch (err) { | |
| console.error('Fatal error:', err) | |
| } | |
| })() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment