Skip to content

Instantly share code, notes, and snippets.

@gerwld
Last active October 25, 2025 21:38
Show Gist options
  • Select an option

  • Save gerwld/c68f28cf2f111c4517b98066faaa66a4 to your computer and use it in GitHub Desktop.

Select an option

Save gerwld/c68f28cf2f111c4517b98066faaa66a4 to your computer and use it in GitHub Desktop.
/**
* 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