Welcome! Share code as fast as possible.

;(async function () {

  // ─────────────────────────────────────────────
  //  CONFIG  (tweak if needed)
  // ─────────────────────────────────────────────
  const CFG = {
    BATCH_SIZE:          10,    // comments deleted per round
    DELAY_CLICK:        350,    // ms between each checkbox click
    DELAY_AFTER_SELECT: 1800,   // ms after clicking "Select"
    DELAY_AFTER_DELETE: 2800,   // ms after confirm deletion
    RATE_LIMIT_PAUSE:  25000,   // ms pause between batches (avoids IG rate-limit)
    WATCH_MODE:         true,   // after cleanup, keep watching for new comments
    WATCH_INTERVAL:   120000,   // ms between watch-mode sweeps (2 min)
    MAX_FIND_WAIT:      8000,   // ms to wait for any element before giving up
  }

  // ─────────────────────────────────────────────
  //  LOGGER  (styled console output)
  // ─────────────────────────────────────────────
  const log = {
    info:  (...a) => console.log('%c ℹ️ ', 'color:#4fc3f7;font-weight:bold', ...a),
    ok:    (...a) => console.log('%c ✅ ', 'color:#81c784;font-weight:bold', ...a),
    warn:  (...a) => console.warn('%c ⚠️ ', 'color:#ffb74d;font-weight:bold', ...a),
    err:   (...a) => console.error('%c ❌ ', 'color:#e57373;font-weight:bold', ...a),
    step:  (...a) => console.log('%c 🗑️ ', 'color:#ce93d8;font-weight:bold', ...a),
  }

  const delay = (ms) => new Promise(r => setTimeout(r, ms))

  // ─────────────────────────────────────────────
  //  CLICK  — two strategies, no double-toggle
  // ─────────────────────────────────────────────
  const nativeClick = (el) => el.click()

  const syntheticClick = (el) => {
    ;['mousedown','mouseup','click'].forEach(type =>
      el.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true }))
    )
  }

  // Try native first (works for most buttons), fall back to synthetic
  const click = (el, forceNative = false) => {
    if (forceNative) return nativeClick(el)
    try { syntheticClick(el) } catch { nativeClick(el) }
  }

  // ─────────────────────────────────────────────
  //  waitFor  — polls until fn() returns truthy
  // ─────────────────────────────────────────────
  const waitFor = async (fn, ms = CFG.MAX_FIND_WAIT) => {
    const end = Date.now() + ms
    while (Date.now() < end) {
      const el = fn()
      if (el) return el
      await delay(300)
    }
    return null
  }

  // ─────────────────────────────────────────────
  //  ELEMENT FINDERS  — text/aria-only, no classes
  // ─────────────────────────────────────────────

  /**
   * Find "Select" button.
   * Strategy 1: find <span>Select</span>, walk up to role=button / <button>
   * Strategy 2: querySelector role=button whose innerText is exactly "Select"
   */
  const findSelectBtn = () => {
    // S1 — span text walk-up
    for (const span of document.querySelectorAll('span')) {
      if (span.textContent.trim() === 'Select') {
        let el = span
        while (el && el.tagName !== 'BODY') {
          if (el.getAttribute('role') === 'button' || el.tagName === 'BUTTON') return el
          el = el.parentElement
        }
        return span.parentElement
      }
    }
    // S2 — direct role=button text
    return [...document.querySelectorAll('[role="button"], button')]
      .find(el => el.innerText?.trim() === 'Select') || null
  }

  /**
   * Find selectable comment items after "Select" mode is active.
   * Tries multiple aria-labels in order.
   */
  const findCommentItems = () => {
    const LABELS = [
      'Toggle checkbox',
      'Image with button',
      'Comment',
    ]
    for (const label of LABELS) {
      const els = [...document.querySelectorAll(`[aria-label="${label}"]`)]
      if (els.length > 0) return els
    }
    // Generic fallback: any checkbox role inside the feed
    const checks = [...document.querySelectorAll('[role="checkbox"]')]
    return checks.length > 0 ? checks : null
  }

  /**
   * Find the Delete action button (appears after items are selected).
   * Never matches "Confirm Delete" dialog — that's a separate finder.
   */
  const findDeleteBtn = () => {
    // S1 — aria-label="Delete"
    const byLabel = document.querySelector('[aria-label="Delete"]')
    if (byLabel && isVisible(byLabel)) return byLabel

    // S2 — role=button whose text is "Delete", must be visible, not inside a dialog
    return [...document.querySelectorAll('[role="button"], button')]
      .find(el =>
        el.innerText?.trim() === 'Delete' &&
        isVisible(el) &&
        !el.closest('[role="dialog"],[role="alertdialog"]')
      ) || null
  }

  /**
   * Find the confirm "Delete" button inside the confirmation dialog.
   */
  const findConfirmBtn = () => {
    // S1 — inside dialog
    const inDialog = [...document.querySelectorAll('[role="dialog"] button, [role="alertdialog"] button')]
      .find(el => el.innerText?.trim() === 'Delete')
    if (inDialog) return inDialog

    // S2 — any visible "Delete" button that's NOT the main delete action btn
    return [...document.querySelectorAll('button')]
      .find(el => el.innerText?.trim() === 'Delete' && isVisible(el)) || null
  }

  /**
   * Dismiss OK/error popups from Instagram rate-limiting.
   */
  const findOkBtn = () =>
    [...document.querySelectorAll('button, [role="button"]')]
      .find(el => el.innerText?.trim() === 'OK' && isVisible(el)) || null

  const isVisible = (el) =>
    el && el.offsetParent !== null && el.style.display !== 'none' && el.style.visibility !== 'hidden'

  // ─────────────────────────────────────────────
  //  AUTO-DISMISS error dialogs in background
  // ─────────────────────────────────────────────
  const dismisser = setInterval(() => {
    const ok = findOkBtn()
    if (ok) { log.warn('Rate-limit dialog dismissed'); click(ok, true) }
  }, 1200)

  // ─────────────────────────────────────────────
  //  ESCAPE — exit select mode cleanly
  // ─────────────────────────────────────────────
  const pressEscape = () =>
    document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))

  // ─────────────────────────────────────────────
  //  CORE LOOP
  // ─────────────────────────────────────────────
  let totalDeleted = 0

  const runOnce = async () => {
    log.info('Starting sweep…')

    while (true) {

      // 1 ── Find & click "Select"
      const selectBtn = await waitFor(findSelectBtn, 6000)
      if (!selectBtn) {
        log.ok(`No "Select" button found — sweep complete. Deleted this run: batch counted below.`)
        return
      }
      click(selectBtn)
      await delay(CFG.DELAY_AFTER_SELECT)

      // 2 ── Gather comment items
      const allItems = findCommentItems()
      if (!allItems || allItems.length === 0) {
        log.ok('No comment items visible — done.')
        pressEscape()
        return
      }

      const batch = allItems.slice(0, CFG.BATCH_SIZE)
      log.step(`Selecting ${batch.length} / ${allItems.length} comments…`)

      // 3 ── Click each checkbox exactly once, check aria-checked to avoid double-toggle
      for (const item of batch) {
        const already =
          item.getAttribute('aria-checked') === 'true' ||
          item.getAttribute('aria-selected') === 'true'
        if (!already) {
          click(item)
          await delay(CFG.DELAY_CLICK)
        }
      }
      await delay(1000)

      // 4 ── Click Delete action button
      const deleteBtn = await waitFor(findDeleteBtn, 6000)
      if (!deleteBtn) {
        log.warn('Delete button not found — escaping & retrying next batch')
        pressEscape()
        await delay(2000)
        continue
      }
      click(deleteBtn)
      await delay(1200)

      // 5 ── Confirm deletion
      const confirmBtn = await waitFor(findConfirmBtn, 6000)
      if (!confirmBtn) {
        log.err('Confirm dialog not found — stopping to prevent data loss')
        pressEscape()
        break
      }
      click(confirmBtn, true)  // native click for dialog buttons

      await delay(CFG.DELAY_AFTER_DELETE)
      totalDeleted += batch.length
      log.ok(`✔ ${totalDeleted} total deleted — pausing ${CFG.RATE_LIMIT_PAUSE / 1000}s…`)
      await delay(CFG.RATE_LIMIT_PAUSE)
      log.info('Resuming…')
    }
  }

  // ─────────────────────────────────────────────
  //  WATCH MODE  — re-sweep periodically
  // ─────────────────────────────────────────────
  await runOnce()

  if (CFG.WATCH_MODE) {
    log.info(`Watch mode ON — checking every ${CFG.WATCH_INTERVAL / 1000}s for new comments…`)
    log.info('To stop: run  window.__igStop = true  in the console.')

    window.__igStop = false

    const watchLoop = async () => {
      while (!window.__igStop) {
        await delay(CFG.WATCH_INTERVAL)
        if (window.__igStop) break
        log.info('Watch sweep starting…')
        await runOnce()
      }
      clearInterval(dismisser)
      log.ok(`Stopped. Total deleted this session: ${totalDeleted}`)
    }

    watchLoop()
  } else {
    clearInterval(dismisser)
    log.ok(`Script finished. Total deleted: ${totalDeleted}`)
  }

})()