Free tool · No sign-up

See who doesn’t follow you back

This scans the accounts you follow on Instagram and lists the ones that don’t follow you back. No password, no app to install, and your data never leaves your browser.

Read the file first
  • Runs in your browser
  • Nothing is uploaded
  • CSV export included
instagram-unfollowers.js
(() => {  "use strict";  if (location.hostname !== "www.instagram.com") {    alert("Open https://www.instagram.com first, then paste this script again.");    return;  }  const APP_ID = "iu-app";  const VERSION = "1.0.1";  const CLEANUP_EVENT = "iu-cleanup";  const STYLE_ID = "iu-style";  const STORAGE_KEY = "iu_state_v3";  const CHECKPOINT_KEY = "iu_scan_v1";  const CHECKPOINT_TTL = 24 * 60 * 60 * 1000;  const IG_HEADERS = { "x-ig-app-id": "936619743392459", "x-requested-with": "XMLHttpRequest" };  const DEFAULT_TIMINGS = {    scanDelayMin: 1000,    scanDelayMax: 1300,    scanPauseEveryPages: 7,    scanPauseMs: 10000,    usersPerRequest: 50,    unfollowDelayMin: 4000,    unfollowDelayMax: 4800,    unfollowPauseEvery: 5,    unfollowPauseMs: 300000,  };  const SCAN_MICRO_DELAY_MIN = 500;  const SCAN_MICRO_DELAY_MAX = 2000;  const SCAN_PAUSE_JITTER_MS = 5000;  const PROGRESS_TITLE_ID = "iu-progress-title";  const PANEL_WIDTH = 380;  const PANEL_MARGIN = 18;  const MAX_RETRIES = 3;  const I18N = {    en: {      title: "Instagram Unfollower",      subtitle: "See who doesn't follow you back",      welcomeTitle: "Ready when you are",      welcomeBody:        "We'll compare the people you follow with your followers. Nothing is changed on your account during the scan.",      scanBtn: "Scan now",      scanning: "Scanning",      loadingFollowing: "Loading the people you follow",      loadingFollowers: "Loading your followers",      paused: "Paused",      pause: "Pause",      resume: "Resume",      cancel: "Cancel",      ofTotal: "{current} of {total}",      ofUnknown: "{current} so far",      scanCompletedToast: "{count} non-followers found",      scanFailed: "Scan failed",      retry: "Try again",      goBack: "Back to results",      search: "Search by name or username",      filterVerified: "Verified",      filterPrivate: "Private",      filterNoAvatar: "No profile picture",      filterShowHidden: "Hidden users",      foundCount: "{count} non-followers",      foundOne: "1 non-follower",      foundNone: "Nice — everyone you follow follows you back.",      noMatches: "No users match your filters.",      hide: "Hide",      unhide: "Unhide",      hideTooltip: "Hide from this list",      unhideTooltip: "Show again",      openProfile: "Open profile",      copy: "Copy",      copyAll: "Copy all",      copiedToast: "Copied {count} usernames",      selectAll: "Select all",      selectNone: "Clear",      selectedCount: "{count} selected",      unfollow: "Unfollow",      unfollowConfirmTitle: "Unfollow {count} users?",      unfollowConfirmBody:        "This will run slowly to protect your account from rate limiting. You can pause at any time, but already-completed unfollows cannot be reversed from this tool.",      unfollowConfirmBtn: "Yes, unfollow {count}",      unfollowing: "Unfollowing",      currently: "Currently",      nextActionIn: "Next action in {seconds}s",      cooldownIn: "Cooldown — {seconds}s",      scanPause: "Long pause — {seconds}s",      unfollowDoneTitle: "Done",      unfollowDoneBody: "{ok} unfollowed, {fail} failed",      unfollowBlocked:        "Instagram blocked this action, so {count} remaining users were skipped. Wait a few hours before trying again.",      settings: "Settings",      settingsTitle: "Timing settings",      settingsBody:        "Lower delays make Instagram more likely to throttle or block your account. Keep these conservative.",      minScanDelay: "Min scan delay (ms)",      maxScanDelay: "Max scan delay (ms)",      scanPauseEvery: "Long pause every N pages",      scanPauseLength: "Long pause length (ms)",      usersPerRequest: "Users requested per page",      minUnfollowDelay: "Min unfollow delay (ms)",      maxUnfollowDelay: "Max unfollow delay (ms)",      unfollowPauseEvery: "Cooldown every N unfollows",      unfollowPauseLength: "Cooldown length (ms)",      restoreDefaults: "Restore defaults",      save: "Save",      saved: "Settings saved",      cookieMissing: "Could not read your login cookie. Make sure you are signed in.",      csrfMissing: "Could not read csrftoken cookie.",      requestFailed: "Request failed: {status}",      tooManyRequests:        "Instagram is rate-limiting requests. Try again later or increase delays in settings.",      sessionExpired:        "Instagram signed you out. Sign in again, paste the script again and press Resume. The scan continues where it stopped.",      scanBlocked:        "Instagram temporarily refused list requests for this account. Wait a few hours, paste the script again and press Resume.",      networkError: "The connection dropped. Check the network and press Resume.",      keepTabVisible:        "Keep this tab in front. Chrome slows background tabs down to one step per minute.",      resumeScan: "Resume scan",      startOver: "Start over",      resumeHint:        "A previous scan stopped at {loaded} of {total} followers. Continue from there instead of starting again.",      resumeHintUnknown:        "A previous scan stopped partway. Continue from there instead of starting again.",      partialTitle: "Follower list incomplete",      partialBody:        "{loaded} of {total} followers were loaded. Results and unfollowing stay unavailable until the full list is checked. Your progress is saved.",      partialBodyUnknown:        "{loaded} followers were loaded before the scan stopped. Results and unfollowing stay unavailable until the full list is checked. Your progress is saved.",      incompleteList:        "Instagram returned an incomplete list ({loaded} of {total}). No non-follower results can be confirmed. Your progress is saved; wait before resuming.",      limitedList:        "Instagram is limiting this list. No non-follower results can be confirmed. Wait before trying again.",      scanNotice:        "Large accounts can take 20 minutes or more. Instagram may still interrupt a scan. If it does, wait before resuming; repeating the scan immediately can prolong restrictions.",      close: "Close",      langSwitch: "Switch to Turkish",      minimize: "Minimize",      expand: "Expand",      langCode: "TR",      pillScanning: "Scanning {current}/{total}",      pillUnfollowing: "Unfollowing {current}/{total}",      pillResults: "{count} non-followers",      pillIdle: "Open",    },    tr: {      title: "Instagram Takip Etmeyenler",      subtitle: "Seni geri takip etmeyenleri gör",      welcomeTitle: "Hazır olduğunda başlat",      welcomeBody:        "Takip ettiklerin ve takipçilerin karşılaştırılır. Tarama sırasında hesabında hiçbir şey değişmez.",      scanBtn: "Taramayı başlat",      scanning: "Taranıyor",      loadingFollowing: "Takip ettiklerin yükleniyor",      loadingFollowers: "Takipçilerin yükleniyor",      paused: "Duraklatıldı",      pause: "Duraklat",      resume: "Devam et",      cancel: "İptal",      ofTotal: "{current} / {total}",      ofUnknown: "Şu ana kadar {current}",      scanCompletedToast: "{count} takip etmeyen bulundu",      scanFailed: "Tarama başarısız",      retry: "Tekrar dene",      goBack: "Sonuçlara dön",      search: "İsim veya kullanıcı adı ara",      filterVerified: "Onaylı",      filterPrivate: "Gizli",      filterNoAvatar: "Profil resmi yok",      filterShowHidden: "Gizlenen kullanıcılar",      foundCount: "{count} takip etmeyen",      foundOne: "1 takip etmeyen",      foundNone: "Harika — takip ettiğin herkes seni takip ediyor.",      noMatches: "Filtrelerinle eşleşen kullanıcı yok.",      hide: "Gizle",      unhide: "Göster",      hideTooltip: "Bu listeden gizle",      unhideTooltip: "Tekrar göster",      openProfile: "Profili aç",      copy: "Kopyala",      copyAll: "Tümünü kopyala",      copiedToast: "{count} kullanıcı adı kopyalandı",      selectAll: "Tümünü seç",      selectNone: "Temizle",      selectedCount: "{count} seçili",      unfollow: "Takibi bırak",      unfollowConfirmTitle: "{count} kullanıcının takibi bırakılsın mı?",      unfollowConfirmBody:        "Hesabını korumak için işlem yavaş çalışır. İstediğin zaman duraklatabilirsin, ama tamamlanan işlemler bu araçtan geri alınamaz.",      unfollowConfirmBtn: "Evet, {count} kişiyi bırak",      unfollowing: "Takip bırakılıyor",      currently: "Şu an",      nextActionIn: "Sonraki işlem {seconds} sn sonra",      cooldownIn: "Mola — {seconds} sn",      scanPause: "Uzun mola — {seconds} sn",      unfollowDoneTitle: "Tamamlandı",      unfollowDoneBody: "{ok} başarılı, {fail} başarısız",      unfollowBlocked:        "Instagram bu işlemi engelledi, kalan {count} kişi atlandı. Tekrar denemeden önce birkaç saat bekle.",      settings: "Ayarlar",      settingsTitle: "Hız ayarları",      settingsBody:        "Düşük gecikmeler Instagram'ın hesabını kısıtlamasına neden olabilir. Yavaş tut.",      minScanDelay: "Min tarama gecikmesi (ms)",      maxScanDelay: "Maks tarama gecikmesi (ms)",      scanPauseEvery: "Her N sayfada uzun mola",      scanPauseLength: "Uzun mola süresi (ms)",      usersPerRequest: "Sayfa başına istenen kullanıcı",      minUnfollowDelay: "Min takip bırakma gecikmesi (ms)",      maxUnfollowDelay: "Maks takip bırakma gecikmesi (ms)",      unfollowPauseEvery: "Her N takip bırakmada mola",      unfollowPauseLength: "Mola süresi (ms)",      restoreDefaults: "Varsayılana dön",      save: "Kaydet",      saved: "Ayarlar kaydedildi",      cookieMissing: "Giriş çerezi okunamadı. Giriş yaptığından emin ol.",      csrfMissing: "csrftoken çerezi okunamadı.",      requestFailed: "İstek başarısız: {status}",      tooManyRequests:        "Instagram istekleri kısıtlıyor. Sonra dene veya ayarlardan gecikmeleri artır.",      sessionExpired:        "Instagram oturumunu kapattı. Tekrar giriş yap, kodu yeniden yapıştır ve Devam et'e bas. Tarama kaldığı yerden sürer.",      scanBlocked:        "Instagram bu hesap için liste isteklerini geçici olarak reddetti. Birkaç saat bekle, kodu yeniden yapıştır ve Devam et'e bas.",      networkError: "Bağlantı koptu. Ağı kontrol et ve Devam et'e bas.",      keepTabVisible:        "Bu sekmeyi önde tut. Chrome arka plandaki sekmeleri dakikada bir adıma yavaşlatır.",      resumeScan: "Taramaya devam et",      startOver: "Baştan başla",      resumeHint:        "Önceki tarama {total} takipçinin {loaded} tanesinde durdu. Baştan başlamak yerine oradan devam edebilirsin.",      resumeHintUnknown:        "Önceki tarama yarıda kaldı. Baştan başlamak yerine oradan devam edebilirsin.",      partialTitle: "Takipçi listesi eksik",      partialBody:        "{total} takipçinin {loaded} tanesi yüklendi. Listenin tamamı kontrol edilene kadar sonuçlar ve takip bırakma kapalı. İlerlemen kaydedildi.",      partialBodyUnknown:        "Tarama durmadan önce {loaded} takipçi yüklendi. Listenin tamamı kontrol edilene kadar sonuçlar ve takip bırakma kapalı. İlerlemen kaydedildi.",      incompleteList:        "Instagram eksik liste döndürdü ({loaded} / {total}). Geri takip etmeyenler henüz doğrulanamadı. İlerlemen kaydedildi; devam etmeden önce bekle.",      limitedList:        "Instagram bu listeyi kısıtlıyor. Geri takip etmeyenler henüz doğrulanamadı. Tekrar denemeden önce bekle.",      scanNotice:        "Büyük hesaplarda tarama 20 dakika veya daha uzun sürebilir. Instagram yine de taramayı kesebilir. Böyle olursa devam etmeden önce bekle; hemen tekrarlamak kısıtlamayı uzatabilir.",      close: "Kapat",      langSwitch: "İngilizce'ye geç",      minimize: "Küçült",      expand: "Aç",      langCode: "EN",      pillScanning: "Taranıyor {current}/{total}",      pillUnfollowing: "Bırakılıyor {current}/{total}",      pillResults: "{count} takip etmeyen",      pillIdle: "Aç",    },  };  const SVG = {    minimize:      '<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><rect x="3" y="7.25" width="10" height="1.5" rx="0.75" fill="currentColor"/></svg>',    close:      '<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>',    gear: '<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path fill="currentColor" d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zm6.7 2.5a6.7 6.7 0 0 0-.1-1.1l1.4-1.1-1.5-2.6-1.7.7a6.6 6.6 0 0 0-1.9-1.1L10.5 1h-3l-.4 1.8a6.6 6.6 0 0 0-1.9 1.1l-1.7-.7L1.9 5.8 3.3 6.9a6.7 6.7 0 0 0 0 2.2L1.9 10.2l1.5 2.6 1.7-.7a6.6 6.6 0 0 0 1.9 1.1L7.5 15h3l.4-1.8a6.6 6.6 0 0 0 1.9-1.1l1.7.7 1.5-2.6-1.4-1.1c.1-.4.1-.7.1-1.1z"/></svg>',    open: '<svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true"><path d="M6 3h7v7M13 3L6.5 9.5M9 13H3V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/></svg>',    sparkle:      '<svg viewBox="0 0 24 24" width="36" height="36" aria-hidden="true"><path fill="currentColor" d="M12 2l1.8 5.4L19 9l-5.2 1.6L12 16l-1.8-5.4L5 9l5.2-1.6L12 2zm6 11l1 2.8L22 17l-3 .8L18 21l-1-3.2L14 17l3-1.2 1-2.8z"/></svg>',    alert:      '<svg viewBox="0 0 24 24" width="36" height="36" aria-hidden="true"><path fill="currentColor" d="M12 2 1 21h22L12 2zm0 6 7.5 13h-15L12 8zm-1 4v4h2v-4h-2zm0 5v2h2v-2h-2z"/></svg>',    check:      '<svg viewBox="0 0 16 16" width="36" height="36" aria-hidden="true"><path fill="currentColor" d="M14 4.5L6 12.5l-4-4L3 7.5l3 3 7-7z"/></svg>',  };  const persisted = loadStored();  const state = {    mode: "idle",    scanPaused: false,    scanCancelled: false,    unfollowPaused: false,    unfollowBlocked: 0,    progress: { current: 0, total: 0, label: "scanning", note: "" },    waitUntil: 0,    waitReason: "",    users: [],    followingCount: 0,    followersCount: 0,    selected: new Set(),    hidden: new Set(persisted.hidden || []),    log: [],    search: "",    filters: persisted.filters || {      verified: true,      private: true,      noAvatar: true,      showHidden: false,    },    timings: loadTimings(persisted.timings),    panelPos: persisted.panelPos || null,    minimized: Boolean(persisted.minimized),    language: persisted.lang === "tr" ? "tr" : "en",    error: "",    checkpoint: loadCheckpoint(),    partial: false,    coverage: "",  };  let countdownTimer = null;  let toastTimer = null;  let dialogCounter = 0;  let closeActiveDialog = null;  let wakeSleep = null;  let destroyed = false;  let activeRequest = null;  function loadTimings(saved = {}) {    const timings = { ...DEFAULT_TIMINGS };    for (const key of Object.keys(timings)) {      if (Number.isFinite(saved?.[key]) && saved[key] >= 0) timings[key] = saved[key];    }    const scanKeys = ["scanDelayMin", "scanDelayMax", "scanPauseEveryPages", "scanPauseMs"];    const oldPresets = [      [700, 1500, 5, 8000],      [1500, 3000, 5, 20000],      [1500, 3300, 7, 10000],    ];    if (      oldPresets.some((preset) => scanKeys.every((key, index) => saved?.[key] === preset[index]))    ) {      for (const key of scanKeys) timings[key] = DEFAULT_TIMINGS[key];    }    const oldUnfollowPreset = [5000, 9000, 5, 300000];    const unfollowKeys = [      "unfollowDelayMin",      "unfollowDelayMax",      "unfollowPauseEvery",      "unfollowPauseMs",    ];    if (unfollowKeys.every((key, index) => saved?.[key] === oldUnfollowPreset[index])) {      for (const key of unfollowKeys) timings[key] = DEFAULT_TIMINGS[key];    }    timings.usersPerRequest = Math.min(200, Math.max(1, Math.round(timings.usersPerRequest)));    return timings;  }  function loadStored() {    try {      const raw = localStorage.getItem(STORAGE_KEY);      return raw ? JSON.parse(raw) : {};    } catch {      return {};    }  }  function createCheckpoint(viewerId) {    return {      viewerId: String(viewerId),      savedAt: Date.now(),      following: [],      followingCursor: "",      followingDone: false,      followerIds: [],      followersCursor: "",      followersDone: false,      stopReason: "",    };  }  function loadCheckpoint() {    try {      const raw = localStorage.getItem(CHECKPOINT_KEY);      if (!raw) return null;      const parsed = JSON.parse(raw);      if (!parsed || typeof parsed !== "object" || !parsed.viewerId) return null;      if (!Array.isArray(parsed.following) || !Array.isArray(parsed.followerIds)) return null;      if (Date.now() - Number(parsed.savedAt || 0) > CHECKPOINT_TTL) return null;      if (parsed.followingDone && parsed.followersDone) return null;      if (!parsed.following.length && !parsed.followerIds.length) return null;      return { ...createCheckpoint(parsed.viewerId), ...parsed };    } catch {      return null;    }  }  function saveCheckpoint(checkpoint) {    if (!checkpoint) return;    checkpoint.savedAt = Date.now();    try {      localStorage.setItem(CHECKPOINT_KEY, JSON.stringify(checkpoint));    } catch {}  }  function clearCheckpoint() {    state.checkpoint = null;    state.partial = false;    try {      localStorage.removeItem(CHECKPOINT_KEY);    } catch {}  }  function resumeHintText(checkpoint) {    const loaded = checkpoint.followerIds.length;    if (checkpoint.followersTotal) {      return t("resumeHint", {        loaded: formatCount(loaded),        total: formatCount(checkpoint.followersTotal),      });    }    return t("resumeHintUnknown");  }  function formatCount(value) {    const number = Number(value) || 0;    try {      return number.toLocaleString(state.language === "tr" ? "tr-TR" : "en-US");    } catch {      return String(number);    }  }  function persist() {    try {      localStorage.setItem(        STORAGE_KEY,        JSON.stringify({          hidden: [...state.hidden],          timings: state.timings,          filters: state.filters,          panelPos: state.panelPos,          minimized: state.minimized,          lang: state.language,        }),      );    } catch {}  }  function t(key, vars) {    const dict = I18N[state.language] || I18N.en;    const template = dict[key] ?? I18N.en[key] ?? key;    if (!vars) return template;    return template.replace(/\{(\w+)\}/g, (_, name) => vars[name] ?? "");  }  function cleanupExisting() {    document.dispatchEvent(new Event(CLEANUP_EVENT));    if (typeof window.__iuCleanup === "function") {      try {        window.__iuCleanup();      } catch {}    }    document.getElementById(APP_ID)?.remove();    document.getElementById(STYLE_ID)?.remove();  }  function injectStyles() {    const style = document.createElement("style");    style.id = STYLE_ID;    style.textContent = CSS;    document.head.appendChild(style);  }  function mount() {    const root = document.createElement("div");    root.id = APP_ID;    document.body.appendChild(root);    window.__iuCleanup = unmount;    document.addEventListener(CLEANUP_EVENT, unmount);    document.addEventListener("visibilitychange", onVisibilityChange); window.addEventListener("resize", applyPanelPosition);    renderShell();  }  function unmount() {    if (destroyed) return;    destroyed = true;    state.scanCancelled = true;    state.unfollowCancelled = true;    state.scanPaused = false;    state.unfollowPaused = false;    activeRequest?.abort();    wakeUp();    stopCountdown();    document.removeEventListener(CLEANUP_EVENT, unmount);    document.removeEventListener("visibilitychange", onVisibilityChange); window.removeEventListener("resize", applyPanelPosition);    closeActiveDialog?.();    if (toastTimer) {      clearTimeout(toastTimer);      toastTimer = null;    }    document.getElementById(APP_ID)?.remove();    document.getElementById(STYLE_ID)?.remove();    if (window.__iuCleanup === unmount) window.__iuCleanup = null;  }  function renderShell() {    const root = document.getElementById(APP_ID);    if (!root) return;    if (state.minimized) {      root.innerHTML = ` <button class="iu-pill" data-action="expand" type="button" aria-label="${escapeAttr(t("expand"))}"> <span class="iu-pill-dot ${pillStateClass()}"></span> <span data-pill-label>${escapeHTML(pillLabel())}</span> </button> `;      root        .querySelector("[data-action='expand']")        ?.addEventListener("click", () => setMinimized(false));      applyPanelPosition();      return;    }    root.innerHTML = ` <section class="iu-panel" role="dialog" aria-label="${escapeAttr(t("title"))}"> <header class="iu-header" data-drag> <div class="iu-brand"> <span class="iu-brand-dot" aria-hidden="true">${SVG.sparkle}</span> <div class="iu-brand-text"> <strong>${escapeHTML(t("title"))} <span class="iu-version">v${VERSION}</span></strong> <span data-subtitle>${escapeHTML(t("subtitle"))}</span> </div> </div> <div class="iu-header-actions"> <button type="button" data-action="settings" aria-label="${escapeAttr(t("settings"))}" title="${escapeAttr(t("settings"))}">${SVG.gear}</button> <button type="button" data-action="language" aria-label="${escapeAttr(t("langSwitch"))}" title="${escapeAttr(t("langSwitch"))}"><span data-lang aria-hidden="true">${escapeHTML(t("langCode"))}</span></button> <button type="button" data-action="minimize" aria-label="${escapeAttr(t("minimize"))}" title="${escapeAttr(t("minimize"))}">${SVG.minimize}</button> <button type="button" data-action="close" aria-label="${escapeAttr(t("close"))}" title="${escapeAttr(t("close"))}">${SVG.close}</button> </div> </header> <div class="iu-body" data-body></div> </section> `;    bindHeader(root);    bindDrag(root.querySelector("[data-drag]"));    applyPanelPosition();    renderBody();  }  function bindHeader(root) {    root.querySelector("[data-action='close']")?.addEventListener("click", () => unmount());    root      .querySelector("[data-action='minimize']")      ?.addEventListener("click", () => setMinimized(true));    root.querySelector("[data-action='settings']")?.addEventListener("click", showSettings);    root.querySelector("[data-action='language']")?.addEventListener("click", toggleLanguage);  }  function applyPanelPosition() {    const root = document.getElementById(APP_ID);    if (!root) return;    const node = root.querySelector(".iu-panel") || root.querySelector(".iu-pill");    if (!node) return;    const pos = state.panelPos;    if (pos && Number.isFinite(pos.x) && Number.isFinite(pos.y)) {      const max = panelBounds(node);      node.style.left = clamp(pos.x, PANEL_MARGIN, max.x) + "px";      node.style.top = clamp(pos.y, PANEL_MARGIN, max.y) + "px";      node.style.right = "auto";      node.style.bottom = "auto";    } else {      node.style.left = "auto";      node.style.top = "auto";      node.style.right = PANEL_MARGIN + "px";      node.style.bottom = PANEL_MARGIN + "px";    }  }  function panelBounds(node) {    // offset* ignores the opening animation's transform, unlike getBoundingClientRect.    const viewport = document.documentElement;    return {      x: Math.max(PANEL_MARGIN, viewport.clientWidth - node.offsetWidth - PANEL_MARGIN),      y: Math.max(PANEL_MARGIN, viewport.clientHeight - node.offsetHeight - PANEL_MARGIN),    };  }  function bindDrag(handle) {    if (!handle) return;    const panel = handle.closest(".iu-panel");    if (!panel) return;    let startX = 0;    let startY = 0;    let originX = 0;    let originY = 0;    let dragging = false;    handle.addEventListener("pointerdown", (event) => {      if (event.target.closest("button")) return;      dragging = true;      const rect = panel.getBoundingClientRect();      originX = rect.left;      originY = rect.top;      startX = event.clientX;      startY = event.clientY;      panel.style.left = originX + "px";      panel.style.top = originY + "px";      panel.style.right = "auto";      panel.style.bottom = "auto";      handle.setPointerCapture(event.pointerId);      handle.classList.add("iu-dragging");    });    handle.addEventListener("pointermove", (event) => {      if (!dragging) return;      const max = panelBounds(panel);      const x = clamp(originX + (event.clientX - startX), PANEL_MARGIN, max.x);      const y = clamp(originY + (event.clientY - startY), PANEL_MARGIN, max.y);      panel.style.left = x + "px";      panel.style.top = y + "px";    });    const stop = (event) => {      if (!dragging) return;      dragging = false;      handle.releasePointerCapture?.(event.pointerId);      handle.classList.remove("iu-dragging");      const rect = panel.getBoundingClientRect();      state.panelPos = { x: rect.left, y: rect.top };      persist();    };    handle.addEventListener("pointerup", stop);    handle.addEventListener("pointercancel", stop);  }  function setMinimized(value) {    state.minimized = Boolean(value);    persist();    renderShell();  }  function pillLabel() {    if (state.mode === "scanning") {      return t("pillScanning", {        current: formatCount(state.progress.current),        total: state.progress.total ? formatCount(state.progress.total) : "?",      });    }    if (state.mode === "unfollowing") {      return t("pillUnfollowing", { current: state.progress.current, total: state.progress.total });    }    if (state.mode === "results" && state.users.length) {      if (state.partial) return t("partialTitle");      return t("pillResults", {        count: state.users.filter((u) => u.follows_viewer === false && !state.hidden.has(u.id))          .length,      });    }    return t("pillIdle");  }  function pillStateClass() {    if (state.mode === "scanning" || state.mode === "unfollowing") return "iu-pill-dot--active";    if (state.error) return "iu-pill-dot--error";    return "";  }  function renderBody() {    const body = document.querySelector(`#${APP_ID} [data-body]`);    if (!body) return;    if (state.mode === "scanning") {      body.innerHTML = renderScanView();      bindScan(body);    } else if (state.mode === "results") {      body.innerHTML = renderResultsView();      bindResults(body);    } else if (state.mode === "unfollowing" || state.mode === "unfollowDone") {      body.innerHTML = renderUnfollowView();      bindUnfollow(body);    } else {      body.innerHTML = renderIdleView();      bindIdle(body);    }    if (state.mode === "scanning" || state.mode === "unfollowing") startCountdown();    else stopCountdown();    applyPanelPosition();  }  function renderIdleView() {    const checkpoint = state.checkpoint;    const resumeActions = checkpoint      ? ` <div class="iu-welcome-actions"> <button type="button" class="iu-btn iu-btn--primary iu-btn--lg" data-action="resume-scan">${escapeHTML(t("resumeScan"))}</button> <button type="button" class="iu-btn iu-btn--ghost" data-action="restart-scan">${escapeHTML(t("startOver"))}</button> </div>`      : "";    if (state.error) {      return ` <div class="iu-welcome"> <div class="iu-welcome-icon iu-welcome-icon--error">${SVG.alert}</div> <h2>${escapeHTML(t("scanFailed"))}</h2> <p>${escapeHTML(state.error)}</p> ${checkpoint ? resumeActions : ` <button type="button" class="iu-btn iu-btn--primary" data-action="scan">${escapeHTML(t("retry"))}</button>`} </div> `;    }    if (checkpoint) {      return ` <div class="iu-welcome"> <div class="iu-welcome-icon">${SVG.sparkle}</div> <h2>${escapeHTML(t("welcomeTitle"))}</h2> <p>${escapeHTML(resumeHintText(checkpoint))}</p> ${checkpoint.stopReason ? `<p>${escapeHTML(checkpoint.stopReason)}</p>` : ""} ${resumeActions} </div> `;    }    return ` <div class="iu-welcome"> <div class="iu-welcome-icon">${SVG.sparkle}</div> <h2>${escapeHTML(t("welcomeTitle"))}</h2> <p>${escapeHTML(t("welcomeBody"))}</p> <p class="iu-welcome-note">${escapeHTML(t("scanNotice"))}</p> <button type="button" class="iu-btn iu-btn--primary iu-btn--lg" data-action="scan">${escapeHTML(t("scanBtn"))}</button> </div> `;  }  function bindIdle(body) {    body.querySelector("[data-action='scan']")?.addEventListener("click", () => startScan());    bindScanResume(body);  }  function bindScanResume(body) {    body      .querySelector("[data-action='resume-scan']")      ?.addEventListener("click", () => startScan({ resume: true }));    body.querySelector("[data-action='restart-scan']")?.addEventListener("click", () => {      clearCheckpoint();      startScan();    });  }  function renderBar(percent, valueText, known) {    const now = known ? ` aria-valuenow="${percent}"` : "";    return ` <div class="iu-bar" role="progressbar" aria-labelledby="${PROGRESS_TITLE_ID}" aria-valuemin="0" aria-valuemax="100"${now} aria-valuetext="${escapeAttr(valueText)}" data-bar> <span data-progress-bar style="width:${percent}%"></span> </div>`;  }  function renderScanView() {    const { current, total, label, note } = state.progress;    const percent = total ? Math.min(100, Math.round((current / total) * 100)) : 0;    const counter = progressCounter(current, total);    return ` <div class="iu-progress"> <div class="iu-progress-head"> <h2 id="${PROGRESS_TITLE_ID}" data-progress-label>${escapeHTML(state.scanPaused ? t("paused") : t(label))}</h2> <p data-progress-note>${escapeHTML(note)}</p> </div> ${renderBar(percent, counter, Boolean(total))} <div class="iu-progress-meta"> <span data-progress-counter>${escapeHTML(counter)}</span> <span data-countdown class="iu-muted"></span> </div> <div class="iu-progress-actions"> <button type="button" class="iu-btn" data-action="pause-scan">${escapeHTML(t(state.scanPaused ? "resume" : "pause"))}</button> <button type="button" class="iu-btn iu-btn--ghost" data-action="cancel-scan">${escapeHTML(t("cancel"))}</button> </div> </div> `;  }  function bindScan(body) {    body.querySelector("[data-action='pause-scan']")?.addEventListener("click", () => {      state.scanPaused = !state.scanPaused;      const btn = body.querySelector("[data-action='pause-scan']");      if (btn) btn.textContent = t(state.scanPaused ? "resume" : "pause");      const label = body.querySelector("[data-progress-label]");      if (label) label.textContent = state.scanPaused ? t("paused") : t(state.progress.label);      wakeUp();    });    body.querySelector("[data-action='cancel-scan']")?.addEventListener("click", () => {      state.scanCancelled = true;      state.scanPaused = false;      activeRequest?.abort();      wakeUp();    });  }  function renderPartialNotice() {    const checkpoint = state.checkpoint;    if (!state.partial || !checkpoint) return "";    const loaded = formatCount(checkpoint.followerIds.length);    const body = checkpoint.followersTotal      ? t("partialBody", { loaded, total: formatCount(checkpoint.followersTotal) })      : t("partialBodyUnknown", { loaded });    return ` <div class="iu-notice" role="status"> <strong>${escapeHTML(t("partialTitle"))}</strong> <p>${escapeHTML(body)}</p> ${checkpoint.stopReason ? `<p class="iu-notice-reason">${escapeHTML(checkpoint.stopReason)}</p>` : ""} <div class="iu-notice-actions"> <button type="button" class="iu-btn iu-btn--primary iu-btn--small" data-action="resume-scan">${escapeHTML(t("resumeScan"))}</button> <button type="button" class="iu-btn iu-btn--ghost iu-btn--small" data-action="restart-scan">${escapeHTML(t("startOver"))}</button> </div> </div> `;  }  function renderResultsView() {    if (state.partial) return `<div class="iu-results">${renderPartialNotice()}</div>`;    const display = getDisplayUsers();    const totalNonFollowers = state.users.filter(      (u) => u.follows_viewer === false && !state.hidden.has(u.id),    ).length;    const allSelected = display.length > 0 && display.every((u) => state.selected.has(u.id));    let summary;    if (totalNonFollowers === 0) summary = t("foundNone");    else if (totalNonFollowers === 1) summary = t("foundOne");    else summary = t("foundCount", { count: totalNonFollowers });    return ` <div class="iu-results"> ${renderPartialNotice()} ${state.coverage ? `<div class="iu-notice" role="status"><p>${escapeHTML(state.coverage)}</p></div>` : ""} <div class="iu-results-summary">${escapeHTML(summary)}</div> <div class="iu-search-row"> <input class="iu-search" type="search" data-search placeholder="${escapeAttr(t("search"))}" value="${escapeAttr(state.search)}" autocomplete="off" spellcheck="false" > </div> <div class="iu-filters"> ${filterChip("verified", t("filterVerified"))} ${filterChip("private", t("filterPrivate"))} ${filterChip("noAvatar", t("filterNoAvatar"))} ${filterChip("showHidden", t("filterShowHidden"), state.hidden.size > 0 || state.filters.showHidden)} </div> <div class="iu-list" data-list>${renderUserList(display)}</div> <div class="iu-actionbar"> <div class="iu-actionbar-left"> <button type="button" class="iu-btn iu-btn--small" data-action="select-all" ${display.length ? "" : "disabled"}> ${escapeHTML(allSelected ? t("selectNone") : t("selectAll"))} </button> <span class="iu-muted" data-selected-label>${escapeHTML(t("selectedCount", { count: state.selected.size }))}</span> </div> <div class="iu-actionbar-right"> <button type="button" class="iu-btn iu-btn--small" data-action="copy" ${display.length ? "" : "disabled"}>${escapeHTML(state.selected.size ? t("copy") : t("copyAll"))}</button> <button type="button" class="iu-btn iu-btn--danger iu-btn--small" data-action="unfollow" ${state.selected.size ? "" : "disabled"}>${escapeHTML(t("unfollow"))}${state.selected.size ? " (" + state.selected.size + ")" : ""}</button> </div> </div> </div> `;  }  function filterChip(key, label, visible = true) {    if (!visible && key === "showHidden") return "";    const active = state.filters[key];    return ` <button type="button" class="iu-chip ${active ? "iu-chip--on" : ""}" data-filter="${escapeAttr(key)}" aria-pressed="${active ? "true" : "false"}"> <span class="iu-chip-tick">${active ? SVG.check : ""}</span> ${escapeHTML(label)} </button> `;  }  function renderUserList(display) {    if (!display.length) {      return `<div class="iu-list-empty">${escapeHTML(t("noMatches"))}</div>`;    }    return display.map(renderUserRow).join("");  }  function renderUserRow(user) {    const hidden = state.hidden.has(user.id);    const checked = state.selected.has(user.id);    const tags = [];    if (user.is_verified)      tags.push(`<span class="iu-tag iu-tag--blue">${escapeHTML(t("filterVerified"))}</span>`);    if (user.is_private) tags.push(`<span class="iu-tag">${escapeHTML(t("filterPrivate"))}</span>`);    return ` <div class="iu-row ${hidden ? "iu-row--hidden" : ""} ${checked ? "iu-row--selected" : ""}" data-row="${escapeAttr(user.id)}" role="button" tabindex="0"> <input type="checkbox" class="iu-row-check" data-select="${escapeAttr(user.id)}" ${checked ? "checked" : ""} aria-label="${escapeAttr(user.username)}"> <img class="iu-avatar" src="${escapeAttr(user.profile_pic_url || "")}" alt="" loading="lazy" onerror="this.style.visibility='hidden'"> <div class="iu-row-text"> <div class="iu-row-name"> <a href="/${encodeURIComponent(user.username)}/" target="_blank" rel="noopener noreferrer" data-stop>@${escapeHTML(user.username)}</a> ${tags.join(" ")} </div> <div class="iu-row-sub">${escapeHTML(user.full_name || "")}</div> </div> <div class="iu-row-actions"> <a class="iu-icon-btn" href="/${encodeURIComponent(user.username)}/" target="_blank" rel="noopener noreferrer" data-stop title="${escapeAttr(t("openProfile"))}" aria-label="${escapeAttr(t("openProfile"))}">${SVG.open}</a> <button type="button" class="iu-icon-btn iu-text-btn" data-hide="${escapeAttr(user.id)}" data-stop title="${escapeAttr(hidden ? t("unhideTooltip") : t("hideTooltip"))}">${escapeHTML(hidden ? t("unhide") : t("hide"))}</button> </div> </div> `;  }  function bindResults(body) {    const search = body.querySelector("[data-search]");    if (search) {      search.addEventListener("input", (event) => {        state.search = event.target.value;        const list = body.querySelector("[data-list]");        if (list) list.innerHTML = renderUserList(getDisplayUsers());        updateActionBar(body);      });    }    body.querySelectorAll("[data-filter]").forEach((el) => {      el.addEventListener("click", () => {        const key = el.getAttribute("data-filter");        state.filters[key] = !state.filters[key];        pruneSelectionToDisplayed();        persist();        renderBody();      });    });    body.addEventListener("change", (event) => {      const checkbox = event.target.closest("[data-select]");      if (!checkbox) return;      const id = checkbox.getAttribute("data-select");      if (checkbox.checked) state.selected.add(id);      else state.selected.delete(id);      const row = checkbox.closest("[data-row]");      if (row) row.classList.toggle("iu-row--selected", checkbox.checked);      updateActionBar(body);    });    body.addEventListener("click", (event) => {      const hideBtn = event.target.closest("[data-hide]");      if (hideBtn) {        event.preventDefault();        event.stopPropagation();        const id = hideBtn.getAttribute("data-hide");        if (state.hidden.has(id)) state.hidden.delete(id);        else {          state.hidden.add(id);          state.selected.delete(id);        }        persist();        renderBody();        return;      }      if (event.target.closest("[data-stop]")) {        event.stopPropagation();        return;      }      const row = event.target.closest("[data-row]");      if (!row || event.target.closest(".iu-row-check")) return;      const id = row.getAttribute("data-row");      const checkbox = row.querySelector("[data-select]");      if (!checkbox) return;      if (state.selected.has(id)) {        state.selected.delete(id);        checkbox.checked = false;      } else {        state.selected.add(id);        checkbox.checked = true;      }      row.classList.toggle("iu-row--selected", checkbox.checked);      updateActionBar(body);    });    body.addEventListener("keydown", (event) => {      if (event.key !== " " && event.key !== "Enter") return;      const row = event.target.closest("[data-row]");      if (        !row ||        event.target.tagName === "INPUT" ||        event.target.tagName === "A" ||        event.target.tagName === "BUTTON"      )        return;      event.preventDefault();      row.click();    });    body.querySelector("[data-action='select-all']")?.addEventListener("click", () => {      const display = getDisplayUsers();      const allSelected = display.length && display.every((u) => state.selected.has(u.id));      if (allSelected) display.forEach((u) => state.selected.delete(u.id));      else display.forEach((u) => state.selected.add(u.id));      renderBody();    });    body.querySelector("[data-action='copy']")?.addEventListener("click", copyUsernames);    body.querySelector("[data-action='unfollow']")?.addEventListener("click", confirmUnfollow);    bindScanResume(body);  }  function updateActionBar(body) {    const root = body || document.querySelector(`#${APP_ID} [data-body]`);    if (!root) return;    const display = getDisplayUsers();    const allSelected = display.length > 0 && display.every((u) => state.selected.has(u.id));    const selectAllBtn = root.querySelector("[data-action='select-all']");    if (selectAllBtn) {      selectAllBtn.disabled = display.length === 0;      selectAllBtn.textContent = allSelected ? t("selectNone") : t("selectAll");    }    const copyBtn = root.querySelector("[data-action='copy']");    if (copyBtn) copyBtn.disabled = display.length === 0;    updateSelectedLabel(root);  }  function updateSelectedLabel(body) {    const root = body || document.querySelector(`#${APP_ID} [data-body]`);    if (!root) return;    const label = root.querySelector("[data-selected-label]");    if (label) label.textContent = t("selectedCount", { count: state.selected.size });    const unfollowBtn = root.querySelector("[data-action='unfollow']");    if (unfollowBtn) {      unfollowBtn.disabled = state.selected.size === 0;      unfollowBtn.textContent =        t("unfollow") + (state.selected.size ? " (" + state.selected.size + ")" : "");    }    const copyBtn = root.querySelector("[data-action='copy']");    if (copyBtn) copyBtn.textContent = state.selected.size ? t("copy") : t("copyAll");  }  function renderUnfollowView() {    const total = state.progress.total;    const done = state.progress.current;    const percent = total ? Math.round((done / total) * 100) : 0;    const last = state.log[state.log.length - 1];    const summary = unfollowSummary();    return ` <div class="iu-progress"> <div class="iu-progress-head"> <h2 id="${PROGRESS_TITLE_ID}" data-progress-label>${escapeHTML(unfollowTitle())}</h2> <p data-progress-note>${escapeHTML(state.progress.note || "")}</p> </div> ${renderBar(percent, summary, Boolean(total))} <div class="iu-progress-meta"> <span data-progress-counter>${escapeHTML(summary)}</span> <span data-countdown class="iu-muted"></span> </div> <div class="iu-current" data-current ${last ? "" : "hidden"}> <span class="iu-muted">${escapeHTML(t("currently"))}</span> <strong data-current-user>${last ? "@" + escapeHTML(last.user.username) : ""}</strong> <span class="iu-tag ${last && !last.ok ? "iu-tag--red" : "iu-tag--green"}" data-current-tag title="${escapeAttr(last?.reason || "")}">${last && !last.ok ? "✕" : "✓"}</span> </div> ${state.unfollowBlocked ? ` <div class="iu-blocked">${escapeHTML(t("unfollowBlocked", { count: state.unfollowBlocked }))}</div>` : ""} <div class="iu-progress-actions"> ${state.mode === "unfollowing" ? ` <button type="button" class="iu-btn" data-action="pause-unfollow">${escapeHTML(t(state.unfollowPaused ? "resume" : "pause"))}</button> <button type="button" class="iu-btn iu-btn--ghost" data-action="cancel-unfollow">${escapeHTML(t("cancel"))}</button> ` : ` <button type="button" class="iu-btn iu-btn--primary" data-action="back-results">${escapeHTML(t("goBack"))}</button> `} </div> </div> `;  }  function unfollowTitle() {    if (state.mode === "unfollowDone") return t("unfollowDoneTitle");    return state.unfollowPaused ? t("paused") : t("unfollowing");  }  function unfollowSummary() {    if (state.mode === "unfollowDone") {      return t("unfollowDoneBody", {        ok: state.log.filter((e) => e.ok).length,        fail: state.log.filter((e) => !e.ok).length,      });    }    return t("ofTotal", { current: state.progress.current, total: state.progress.total });  }  function bindUnfollow(body) {    body.querySelector("[data-action='pause-unfollow']")?.addEventListener("click", () => {      state.unfollowPaused = !state.unfollowPaused;      const btn = body.querySelector("[data-action='pause-unfollow']");      if (btn) btn.textContent = t(state.unfollowPaused ? "resume" : "pause");      const label = body.querySelector("[data-progress-label]");      if (label) label.textContent = unfollowTitle();      wakeUp();    });    body.querySelector("[data-action='cancel-unfollow']")?.addEventListener("click", () => {      state.unfollowCancelled = true;      state.unfollowPaused = false;      wakeUp();    });    body.querySelector("[data-action='back-results']")?.addEventListener("click", () => {      state.mode = "results";      state.log = [];      state.unfollowCancelled = false;      renderBody();    });  }  async function startScan(options = {}) {    if (destroyed || state.mode === "scanning" || state.mode === "unfollowing") return;    state.error = "";    state.mode = "scanning";    state.scanPaused = false;    state.scanCancelled = false;    state.unfollowCancelled = false;    state.partial = false;    state.coverage = "";    state.users = [];    state.followingCount = 0;    state.followersCount = 0;    state.selected.clear();    state.log = [];    state.progress = { current: 0, total: 0, label: "loadingFollowing", note: visibilityNote() };    renderBody();    let checkpoint = null;    try {      const viewerId = getCookie("ds_user_id");      if (!viewerId) throw new Error(t("cookieMissing"));      const previous = options.resume ? state.checkpoint : null;      checkpoint =        previous && previous.viewerId === String(viewerId) ? previous : createCheckpoint(viewerId);      checkpoint.stopReason = "";      state.checkpoint = checkpoint;      if (!checkpoint.followingDone) {        const following = await scanList(checkpoint, viewerId, "following");        if (state.scanCancelled || destroyed) return resetToIdle();        checkpoint.following = following;        checkpoint.followingCursor = "";        checkpoint.followingDone = true;        saveCheckpoint(checkpoint);      }      if (state.scanCancelled || destroyed) return resetToIdle();      if (!checkpoint.followersDone) {        const followers = await scanList(checkpoint, viewerId, "followers");        if (state.scanCancelled || destroyed) return resetToIdle();        checkpoint.followerIds = followers.map((user) => user.id);        checkpoint.followersCursor = "";        checkpoint.followersDone = true;      }      showResults(checkpoint, true);      clearCheckpoint();    } catch (error) {      if (destroyed) return;      if (state.scanCancelled) return resetToIdle();      console.error("[iu] scan failed:", error);      const message = error?.message || String(error) || t("scanFailed");      if (checkpoint) {        checkpoint.stopReason = message;        saveCheckpoint(checkpoint);      }      if (checkpoint && checkpoint.followingDone && checkpoint.following.length) {        showResults(checkpoint, false);        return;      }      state.error = message;      state.mode = "idle";      renderBody();    }  }  function showResults(checkpoint, complete) {    state.followingCount = checkpoint.following.length;    state.followersCount = checkpoint.followerIds.length;    state.users = addFollowBackStatus(checkpoint.following, checkpoint.followerIds, complete);    state.coverage = "";    state.partial = !complete;    state.mode = "results";    if (complete) {      const nonFollowers = state.users.filter(        (u) => !u.follows_viewer && !state.hidden.has(u.id),      ).length;      toast(t("scanCompletedToast", { count: nonFollowers }));    }    renderBody();  }  function resetToIdle() {    if (destroyed) return;    state.mode = "idle";    state.users = [];    state.scanCancelled = false;    state.error = "";    renderBody();  }  async function scanList(checkpoint, viewerId, kind) {    const isFollowing = kind === "following";    const cursorKey = isFollowing ? "followingCursor" : "followersCursor";    const total = isFollowing ? checkpoint.followingTotal : checkpoint.followersTotal;    const label = isFollowing ? "loadingFollowing" : "loadingFollowers";    const seed = isFollowing ? checkpoint.following : checkpoint.followerIds.map((id) => ({ id }));    const onPage = (results, nextCursor) => {      if (isFollowing) checkpoint.following = results;      else checkpoint.followerIds = results.map((user) => user.id);      checkpoint[cursorKey] = nextCursor;      saveCheckpoint(checkpoint);      state.progress = { current: results.length, total, label, note: visibilityNote() };      updateProgressDOM();    };    state.progress = { current: seed.length, total, label, note: visibilityNote() };    updateProgressDOM();    try {      return await fetchFriendshipList(viewerId, kind, onPage, {        cursor: checkpoint[cursorKey],        seed,        total,      });    } catch (error) {      if (error?.kind !== "http" || error?.status !== 400 || !checkpoint[cursorKey]) throw error;      console.warn(`[iu] stored ${kind} cursor was rejected, restarting that list`);      checkpoint[cursorKey] = "";      if (isFollowing) checkpoint.following = [];      else checkpoint.followerIds = [];      return fetchFriendshipList(viewerId, kind, onPage, { total });    }  }  async function fetchFriendshipList(viewerId, kind, onPage, options = {}) {    const results = Array.isArray(options.seed) ? [...options.seed] : [];    let cursor = options.cursor || "";    let page = 0;    const seenCursors = new Set(cursor ? [cursor] : []);    while (true) {      if (state.scanPaused) await waitWhile(() => state.scanPaused && !state.scanCancelled);      if (state.scanCancelled) return dedupe(results);      const json = await igFetch(        friendshipListUrl(viewerId, kind, cursor, state.timings.usersPerRequest),      );      if (state.scanCancelled || destroyed) return dedupe(results);      if (!Array.isArray(json?.users)) throw new Error(t("scanFailed"));      if (        json.should_limit_list_of_followers === true ||        json.should_limit_list_of_followings === true      ) {        throw scanError("incomplete", t("limitedList"));      }      const users = json.users.map(normalizeUser).filter((u) => u.id && u.username);      results.push(...users);      const nextCursor = json.next_max_id == null ? "" : String(json.next_max_id);      const finished = json.has_more === false || !nextCursor;      if (finished && json.has_more === true && !nextCursor) throw new Error(t("scanFailed"));      if (!finished && (!users.length || seenCursors.has(nextCursor)))        throw new Error(t("scanFailed"));      onPage(dedupe(results), finished ? "" : nextCursor);      if (finished) break;      seenCursors.add(nextCursor);      cursor = nextCursor;      page += 1;      await waitBeforeNextScanPage(page);    }    return dedupe(results);  }  async function waitBeforeNextScanPage(page) {    await sleepWithCountdown(      randomBetween(SCAN_MICRO_DELAY_MIN, SCAN_MICRO_DELAY_MAX),      "scanPause",    );    await sleepWithCountdown(      randomBetween(state.timings.scanDelayMin, state.timings.scanDelayMax),      "scanPause",    );    if (state.timings.scanPauseEveryPages > 0 && page % state.timings.scanPauseEveryPages === 0) {      await sleepWithCountdown(        Math.max(          0,          state.timings.scanPauseMs +            (Math.random() * SCAN_PAUSE_JITTER_MS * 2 - SCAN_PAUSE_JITTER_MS),        ),        "scanPause",      );    }  }  function friendshipListUrl(viewerId, kind, cursor = "", count = DEFAULT_TIMINGS.usersPerRequest) {    if (kind !== "following" && kind !== "followers") throw new Error("Unknown friendship list");    const safeCount = Math.min(      200,      Math.max(1, Math.round(Number(count) || DEFAULT_TIMINGS.usersPerRequest)),    );    const base = `/api/v1/friendships/${encodeURIComponent(viewerId)}/${kind}/?count=${safeCount}`;    return cursor ? `${base}&max_id=${encodeURIComponent(cursor)}` : base;  }  function addFollowBackStatus(following, followers, complete = true) {    const followerIds = new Set(      followers.map((entry) => String(entry && typeof entry === "object" ? entry.id : entry)),    );    return following.map((user) => ({      ...user,      follows_viewer: followerIds.has(String(user.id)) ? true : complete ? false : null,    }));  }  function scanError(kind, message, status) {    const error = new Error(message);    error.kind = kind;    error.status = status;    return error;  }  async function igFetch(url, init = {}) {    let attempt = 0;    while (true) {      if (destroyed || state.scanCancelled) throw scanError("cancelled", "Scan cancelled");      let response;      const controller = new AbortController();      activeRequest = controller;      try {        response = await fetch(url, {          ...init,          credentials: "include",          headers: { ...IG_HEADERS, ...(init.headers || {}) },          signal: controller.signal,        });      } catch (error) {        if (destroyed || state.scanCancelled) throw scanError("cancelled", "Scan cancelled");        if (attempt >= MAX_RETRIES) throw scanError("network", t("networkError"), 0);        await sleepWithCountdown(Math.min(30000, 3000 * Math.pow(2, attempt)), "cooldownIn");        attempt += 1;        continue;      } finally {        if (activeRequest === controller) activeRequest = null;      }      if (destroyed || state.scanCancelled) throw scanError("cancelled", "Scan cancelled");      const body = await response.text();      let json;      try {        json = JSON.parse(body);      } catch {}      const message = json ? String(json.message || json.error_type || "") : body;      if (response.status === 401 || json?.require_login || /login_required/i.test(message)) {        throw scanError("session", t("sessionExpired"), response.status);      }      if (        response.status === 429 ||        /rate_limit|too many requests|please wait a few minutes/i.test(message)      ) {        throw scanError("rate", t("tooManyRequests"), response.status);      }      if (        response.status === 403 ||        json?.spam ||        json?.challenge ||        json?.checkpoint_url ||        json?.feedback_required ||        /feedback_required|checkpoint_required|challenge_required/i.test(message)      ) {        throw scanError("blocked", t("scanBlocked"), response.status);      }      if (response.status >= 500 && response.status < 600) {        if (attempt >= MAX_RETRIES) {          throw scanError("http", t("requestFailed", { status: response.status }), response.status);        }        const retryAfter = parseRetryAfter(response.headers?.get?.("retry-after"));        const wait = retryAfter || Math.min(60000, 5000 * Math.pow(2, attempt));        await sleepWithCountdown(wait, "cooldownIn");        attempt += 1;        continue;      }      if (response.ok) {        if (!json || typeof json !== "object")          throw scanError("session", t("sessionExpired"), response.status);        if (json.status && json.status !== "ok")          throw scanError("http", t("scanFailed"), response.status);        return json;      }      throw scanError("http", t("requestFailed", { status: response.status }), response.status);    }  }  function parseRetryAfter(value) {    if (!value) return 0;    const seconds = Number(value);    if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);    const date = Date.parse(value);    if (!Number.isNaN(date)) return Math.max(0, date - Date.now());    return 0;  }  function evaluateUnfollowResponse(status, bodyText) {    const text = typeof bodyText === "string" ? bodyText : "";    let payload = null;    try {      payload = JSON.parse(text);    } catch {      payload = null;    }    const message = String(payload?.message || "");    const blockedByPayload =      payload?.feedback_required === true ||      payload?.spam === true ||      payload?.require_login === true ||      /feedback_required|checkpoint_required|challenge_required|login_required/i.test(message);    if (status === 401 || status === 403 || status === 429 || blockedByPayload) {      return { ok: false, blocked: true, reason: message || `HTTP ${status}` };    }    if (status >= 500) {      return { ok: false, blocked: false, reason: `HTTP ${status}` };    }    if (status < 200 || status >= 300) {      return { ok: false, blocked: false, reason: message || `HTTP ${status}` };    }    if (payload === null) {      return { ok: false, blocked: false, reason: "unexpected response" };    }    if (payload.status === "ok" || payload.friendship_status !== undefined) {      return { ok: true, blocked: false, reason: "" };    }    return {      ok: false,      blocked: false,      reason: message || String(payload.status || "unfollow rejected"),    };  }  function confirmUnfollow() {    if (destroyed || state.partial || !state.selected.size) return;    const count = state.selected.size;    showDialog({      title: t("unfollowConfirmTitle", { count }),      body: t("unfollowConfirmBody"),      confirmLabel: t("unfollowConfirmBtn", { count }),      destructive: true,      onConfirm: () => startUnfollow(),    });  }  async function startUnfollow() {    if (destroyed || state.partial || state.mode === "scanning" || state.mode === "unfollowing")      return;    const targets = state.users.filter(      (u) => u.follows_viewer === false && state.selected.has(u.id),    );    if (!targets.length) return;    const csrf = getCookie("csrftoken");    if (!csrf) {      toast(t("csrfMissing"));      return;    }    state.mode = "unfollowing";    state.unfollowPaused = false;    state.unfollowCancelled = false;    state.unfollowBlocked = 0;    state.log = [];    state.progress = { current: 0, total: targets.length, label: "unfollowing", note: "" };    renderBody();    let processed = 0;    for (let i = 0; i < targets.length; i += 1) {      await waitWhile(() => state.unfollowPaused && !state.unfollowCancelled);      if (state.unfollowCancelled) break;      const user = targets[i];      let outcome = { ok: false, blocked: false, reason: "" };      try {        outcome = await unfollowUser(user.id, csrf);      } catch (error) {        console.error("[iu] unfollow failed for", user.username, error);        outcome = { ok: false, blocked: false, reason: error?.message || "" };      }      if (destroyed) return;      state.log.push({ user, ok: outcome.ok, reason: outcome.reason });      if (outcome.ok) {        state.selected.delete(user.id);        const userRef = state.users.find((u) => u.id === user.id);        if (userRef) userRef.unfollowed = true;      }      processed += 1;      state.progress = {        current: processed,        total: targets.length,        label: "unfollowing",        note: outcome.ok ? "" : outcome.reason,      };      updateUnfollowDOM();      if (outcome.blocked) {        state.unfollowBlocked = targets.length - processed;        console.warn("[iu] Instagram blocked the unfollow action:", outcome.reason);        break;      }      const isLast = i === targets.length - 1;      if (!isLast) {        await sleepWithCountdown(          randomBetween(state.timings.unfollowDelayMin, state.timings.unfollowDelayMax),          "nextActionIn",        );        if (          state.timings.unfollowPauseEvery > 0 &&          (i + 1) % state.timings.unfollowPauseEvery === 0        ) {          await sleepWithCountdown(state.timings.unfollowPauseMs, "cooldownIn");        }      }    }    state.users = state.users.filter((u) => !u.unfollowed);    state.mode = "unfollowDone";    state.waitUntil = 0;    state.waitReason = "";    renderBody();  }  async function unfollowUser(id, csrf) {    const formHeaders = {      "content-type": "application/x-www-form-urlencoded",      "x-csrftoken": csrf,    };    const attempts = [      { url: `/api/v1/friendships/destroy/${id}/`, headers: { ...IG_HEADERS, ...formHeaders } },      { url: `/web/friendships/${id}/unfollow/`, headers: formHeaders },    ];    let outcome = { ok: false, blocked: false, reason: "" };    for (let i = 0; i < attempts.length; i += 1) {      if (i > 0) await sleep(randomBetween(1200, 2500));      if (destroyed || state.unfollowCancelled)        return { ok: false, blocked: true, reason: "Cancelled" };      let response;      try {        response = await fetch(attempts[i].url, {          method: "POST",          credentials: "include",          headers: attempts[i].headers,        });      } catch (error) {        outcome = { ok: false, blocked: false, reason: error?.message || "network error" };        continue;      }      const text = await response.text().catch(() => "");      outcome = evaluateUnfollowResponse(response.status, text);      if (outcome.ok || outcome.blocked) return outcome;    }    return outcome;  }  async function copyUsernames() {    const display = getDisplayUsers();    const target = state.selected.size ? display.filter((u) => state.selected.has(u.id)) : display;    if (!target.length) return;    const text = target      .map((u) => u.username)      .sort()      .join("\n");    try {      await navigator.clipboard.writeText(text);      toast(t("copiedToast", { count: target.length }));    } catch (error) {      console.error("[iu] copy failed:", error);      const ta = document.createElement("textarea");      ta.value = text;      ta.style.position = "fixed";      ta.style.opacity = "0";      document.body.appendChild(ta);      ta.select();      try {        document.execCommand("copy");        toast(t("copiedToast", { count: target.length }));      } catch {}      ta.remove();    }  }  function showSettings() {    const fields = [      ["scanDelayMin", "minScanDelay", 100, 0],      ["scanDelayMax", "maxScanDelay", 100, 0],      ["scanPauseEveryPages", "scanPauseEvery", 1, 0],      ["scanPauseMs", "scanPauseLength", 1000, 0],      ["usersPerRequest", "usersPerRequest", 1, 1, 200],      ["unfollowDelayMin", "minUnfollowDelay", 1000, 0],      ["unfollowDelayMax", "maxUnfollowDelay", 1000, 0],      ["unfollowPauseEvery", "unfollowPauseEvery", 1, 0],      ["unfollowPauseMs", "unfollowPauseLength", 1000, 0],    ];    const formHTML = fields      .map(        ([key, label, step, min, max]) =>          ` <label class="iu-field"> <span>${escapeHTML(t(label))}</span> <input type="number" min="${min}"${max ? ` max="${max}"` : ""} step="${step}" data-setting="${escapeAttr(key)}" value="${Number(state.timings[key])}"> </label> `,      )      .join("");    showDialog({      title: t("settingsTitle"),      body: t("settingsBody"),      contentHTML: `<div class="iu-form">${formHTML}</div>`,      confirmLabel: t("save"),      extraButton: { label: t("restoreDefaults"), action: "restore" },      onConfirm: (dialog) => {        dialog.querySelectorAll("[data-setting]").forEach((input) => {          const key = input.getAttribute("data-setting");          const val = Number(input.value);          const min = Number(input.min || 0);          const max = input.max ? Number(input.max) : Infinity;          if (Number.isFinite(val) && val >= min && val <= max) state.timings[key] = val;        });        persist();        toast(t("saved"));      },      onExtra: (dialog) => {        Object.assign(state.timings, DEFAULT_TIMINGS);        dialog.querySelectorAll("[data-setting]").forEach((input) => {          const key = input.getAttribute("data-setting");          input.value = state.timings[key];        });      },    });  }  function showDialog({    title,    body,    contentHTML,    confirmLabel,    destructive,    extraButton,    onConfirm,    onExtra,  }) {    dialogCounter += 1;    const titleId = `${APP_ID}-dialog-title-${dialogCounter}`;    const previouslyFocused = document.activeElement;    const overlay = document.createElement("div");    overlay.className = "iu-overlay";    overlay.innerHTML = ` <div class="iu-dialog" role="dialog" aria-modal="true" aria-labelledby="${titleId}" tabindex="-1"> <h3 id="${titleId}">${escapeHTML(title)}</h3> ${body ? `<p>${escapeHTML(body)}</p>` : ""} ${contentHTML || ""} <div class="iu-dialog-actions"> ${extraButton ? `<button type="button" class="iu-btn iu-btn--ghost iu-btn--small" data-extra>${escapeHTML(extraButton.label)}</button>` : ""} <button type="button" class="iu-btn iu-btn--small" data-cancel>${escapeHTML(t("cancel"))}</button> <button type="button" class="iu-btn iu-btn--small ${destructive ? "iu-btn--danger" : "iu-btn--primary"}" data-confirm>${escapeHTML(confirmLabel)}</button> </div> </div> `;    document.getElementById(APP_ID).appendChild(overlay);    const dialog = overlay.querySelector(".iu-dialog");    const close = () => {      document.removeEventListener("keydown", onKeyDown, true);      if (closeActiveDialog === close) closeActiveDialog = null;      overlay.remove();      if (previouslyFocused && document.contains(previouslyFocused)) previouslyFocused.focus();    };    const focusables = () =>      Array.from(        dialog.querySelectorAll(          'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',        ),      ).filter((el) => !el.disabled && el.getClientRects().length > 0);    function onKeyDown(event) {      if (event.key === "Escape") {        event.preventDefault();        event.stopPropagation();        close();        return;      }      if (event.key !== "Tab") return;      const items = focusables();      if (!items.length) return;      const first = items[0];      const last = items[items.length - 1];      if (        event.shiftKey &&        (document.activeElement === first || document.activeElement === dialog)      ) {        event.preventDefault();        last.focus();      } else if (!event.shiftKey && document.activeElement === last) {        event.preventDefault();        first.focus();      }    }    document.addEventListener("keydown", onKeyDown, true);    closeActiveDialog = close;    overlay.addEventListener("click", (event) => {      if (event.target === overlay) close();    });    overlay.querySelector("[data-cancel]").addEventListener("click", close);    overlay.querySelector("[data-confirm]").addEventListener("click", () => {      try {        onConfirm?.(dialog);      } finally {        close();      }    });    if (extraButton && onExtra) {      overlay.querySelector("[data-extra]").addEventListener("click", () => onExtra(dialog));    }    (      dialog.querySelector("input") ||      (destructive ? dialog.querySelector("[data-cancel]") : dialog)    ).focus();  }  function toggleLanguage() {    state.language = state.language === "tr" ? "en" : "tr";    persist();    renderShell();  }  function getDisplayUsers() {    const query = state.search.trim().toLowerCase();    return state.users      .filter((u) => u.follows_viewer === false)      .filter((u) => (state.filters.showHidden ? state.hidden.has(u.id) : !state.hidden.has(u.id)))      .filter((u) => state.filters.verified || !u.is_verified)      .filter((u) => state.filters.private || !u.is_private)      .filter((u) => state.filters.noAvatar || !isDefaultAvatar(u))      .filter(        (u) => !query || (u.username + " " + (u.full_name || "")).toLowerCase().includes(query),      )      .sort((a, b) => a.username.localeCompare(b.username));  }  function pruneSelectionToDisplayed() {    if (!state.selected.size) return;    const visible = new Set(getDisplayUsers().map((u) => u.id));    state.selected.forEach((id) => {      if (!visible.has(id)) state.selected.delete(id);    });  }  function normalizeUser(raw) {    return {      id: String(raw.id || raw.pk || raw.pk_id || ""),      username: String(raw.username || ""),      full_name: String(raw.full_name || ""),      profile_pic_url: String(raw.profile_pic_url || raw.profile_pic_url_hd || ""),      is_verified: Boolean(raw.is_verified),      is_private: Boolean(raw.is_private),      follows_viewer: Boolean(        raw.follows_viewer ?? raw.friendship_status?.followed_by ?? raw.followed_by,      ),    };  }  function dedupe(list) {    const seen = new Set();    return list.filter((u) => {      if (!u.id || seen.has(u.id)) return false;      seen.add(u.id);      return true;    });  }  function isDefaultAvatar(user) {    const url = user.profile_pic_url || "";    return /44884218_345707102882519|464760996_1254146839119862/.test(url);  }  function getCookie(name) {    const match = document.cookie.match(      new RegExp("(^|; )" + name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "=([^;]*)"),    );    return match ? decodeURIComponent(match[2]) : null;  }  function sleep(ms) {    return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));  }  function interruptibleSleep(ms) {    return new Promise((resolve) => {      let timer = null;      let settled = false;      const finish = (expired) => {        if (settled) return;        settled = true;        if (timer) clearTimeout(timer);        if (wakeSleep === finish) wakeSleep = null;        resolve(expired);      };      wakeSleep = finish;      timer = setTimeout(() => finish(true), Math.max(0, ms));    });  }  function wakeUp() {    if (typeof wakeSleep === "function") wakeSleep(false);  }  function visibilityNote() {    return typeof document !== "undefined" && document.hidden ? t("keepTabVisible") : "";  }  function onVisibilityChange() {    if (state.mode !== "scanning") return;    state.progress.note = visibilityNote();    updateProgressDOM();  }  function randomBetween(min, max) {    const lo = Math.min(min, max);    const hi = Math.max(min, max);    return Math.floor(Math.random() * (hi - lo)) + lo;  }  async function waitWhile(predicate, interval = 1000) {    while (predicate()) await interruptibleSleep(interval);  }  async function sleepWithCountdown(ms, reasonKey) {    state.waitReason = reasonKey;    let remaining = Math.max(0, ms);    while (remaining > 0) {      if (state.scanCancelled || state.unfollowCancelled) break;      if (state.scanPaused || state.unfollowPaused) {        state.waitUntil = 0;        updateCountdownDOM();        await waitWhile(          () =>            (state.scanPaused || state.unfollowPaused) &&            !state.scanCancelled &&            !state.unfollowCancelled,        );        continue;      }      state.waitUntil = Date.now() + remaining;      updateCountdownDOM();      const before = Date.now();      const expired = await interruptibleSleep(remaining);      remaining = expired ? 0 : Math.max(0, remaining - (Date.now() - before));    }    state.waitUntil = 0;    state.waitReason = "";    updateCountdownDOM();  }  function startCountdown() {    if (countdownTimer) return;    countdownTimer = setInterval(updateCountdownDOM, 500);  }  function stopCountdown() {    if (countdownTimer) {      clearInterval(countdownTimer);      countdownTimer = null;    }  }  function updateCountdownDOM() {    const node = document.querySelector(`#${APP_ID} [data-countdown]`);    if (!node) return;    if (!state.waitUntil) {      node.textContent = "";      return;    }    const remaining = Math.max(0, Math.ceil((state.waitUntil - Date.now()) / 1000));    const reason = state.waitReason || "nextActionIn";    node.textContent = t(reason, { seconds: remaining });  }  function progressCounter(current, total) {    if (total) return t("ofTotal", { current: formatCount(current), total: formatCount(total) });    return t("ofUnknown", { current: formatCount(current) });  }  function setBar(root, percent, valueText, known) {    const bar = root.querySelector("[data-progress-bar]");    if (bar) bar.style.width = percent + "%";    const box = root.querySelector("[data-bar]");    if (!box) return;    if (known) box.setAttribute("aria-valuenow", String(percent));    else box.removeAttribute("aria-valuenow");    box.setAttribute("aria-valuetext", valueText);  }  function updatePillDOM() {    if (!state.minimized) return;    const pillLabelEl = document.querySelector(`#${APP_ID} [data-pill-label]`);    if (pillLabelEl) pillLabelEl.textContent = pillLabel();  }  function updateProgressDOM() {    const root = document.querySelector(`#${APP_ID} [data-body]`);    if (!root) return;    const { current, total, label, note } = state.progress;    const percent = total ? Math.min(100, Math.round((current / total) * 100)) : 0;    const counter = progressCounter(current, total);    setBar(root, percent, counter, Boolean(total));    const counterEl = root.querySelector("[data-progress-counter]");    if (counterEl) counterEl.textContent = counter;    const labelEl = root.querySelector("[data-progress-label]");    if (labelEl) labelEl.textContent = state.scanPaused ? t("paused") : t(label);    const noteEl = root.querySelector("[data-progress-note]");    if (noteEl) noteEl.textContent = note || "";    updatePillDOM();  }  function updateUnfollowDOM() {    const root = document.querySelector(`#${APP_ID} [data-body]`);    if (!root) return;    const { current, total, note } = state.progress;    const percent = total ? Math.min(100, Math.round((current / total) * 100)) : 0;    const summary = unfollowSummary();    setBar(root, percent, summary, Boolean(total));    const counterEl = root.querySelector("[data-progress-counter]");    if (counterEl) counterEl.textContent = summary;    const labelEl = root.querySelector("[data-progress-label]");    if (labelEl) labelEl.textContent = unfollowTitle();    const noteEl = root.querySelector("[data-progress-note]");    if (noteEl) noteEl.textContent = note || "";    const last = state.log[state.log.length - 1];    const currentEl = root.querySelector("[data-current]");    if (currentEl) {      currentEl.hidden = !last;      if (last) {        const userEl = currentEl.querySelector("[data-current-user]");        if (userEl) userEl.textContent = "@" + last.user.username;        const tagEl = currentEl.querySelector("[data-current-tag]");        if (tagEl) {          tagEl.textContent = last.ok ? "✓" : "✕";          tagEl.title = last.reason || "";          tagEl.classList.toggle("iu-tag--green", last.ok);          tagEl.classList.toggle("iu-tag--red", !last.ok);        }      }    }    updatePillDOM();  }  function toast(message) {    const root = document.getElementById(APP_ID);    if (!root) return;    root.querySelector(".iu-toast")?.remove();    if (toastTimer) {      clearTimeout(toastTimer);      toastTimer = null;    }    const node = document.createElement("div");    node.className = "iu-toast";    node.setAttribute("role", "status");    node.setAttribute("aria-live", "polite");    node.textContent = message;    root.appendChild(node);    toastTimer = setTimeout(() => {      node.remove();      toastTimer = null;    }, 3500);  }  function clamp(value, min, max) {    return Math.min(Math.max(value, min), max);  }  function escapeHTML(value) {    return String(value ?? "").replace(      /[&<>"']/g,      (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[ch],    );  }  function escapeAttr(value) {    return escapeHTML(value);  }  const CSS = ` #${APP_ID}, #${APP_ID} * { box-sizing: border-box; } #${APP_ID} { --iu-bg: #121216; --iu-bg-2: #1c1c23; --iu-bg-3: #272730; --iu-line: rgba(240,240,250,0.10); --iu-line-strong: rgba(240,240,250,0.20); --iu-text: #f4f4f8; --iu-muted: #9d9dab; --iu-accent: #d62976; --iu-accent-hover: #bd1f66; --iu-accent-2: #ff6fa8; --iu-gradient: linear-gradient(135deg, #feda75, #fa7e1e, #d62976, #962fbf, #4f5bd5); --iu-danger: #b3251c; --iu-danger-text: #f06a5d; --iu-success: #34d399; position: fixed; inset: 0; pointer-events: none; z-index: 2147483647; color: var(--iu-text); color-scheme: dark; font: 14px/1.45 Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; } #${APP_ID} .iu-list, #${APP_ID} .iu-form { scrollbar-width: thin; scrollbar-color: var(--iu-line-strong) transparent; } #${APP_ID} .iu-list::-webkit-scrollbar, #${APP_ID} .iu-form::-webkit-scrollbar { width: 8px; } #${APP_ID} .iu-list::-webkit-scrollbar-track, #${APP_ID} .iu-form::-webkit-scrollbar-track { background: transparent; } #${APP_ID} .iu-list::-webkit-scrollbar-thumb, #${APP_ID} .iu-form::-webkit-scrollbar-thumb { background: var(--iu-line-strong); border-radius: 4px; } #${APP_ID} .iu-list::-webkit-scrollbar-thumb:hover, #${APP_ID} .iu-form::-webkit-scrollbar-thumb:hover { background: var(--iu-muted); } #${APP_ID} :focus-visible { outline: 2px solid var(--iu-accent); outline-offset: 2px; } #${APP_ID} > * { pointer-events: auto; } #${APP_ID} button, #${APP_ID} input, #${APP_ID} a { font: inherit; color: inherit; } #${APP_ID} a { text-decoration: none; } #${APP_ID} button { cursor: pointer; } #${APP_ID} [hidden] { display: none !important; } #${APP_ID} .iu-panel { position: absolute; width: ${PANEL_WIDTH}px; max-width: calc(100vw - ${PANEL_MARGIN * 2}px); max-height: calc(100vh - ${PANEL_MARGIN * 2}px); display: flex; flex-direction: column; background: var(--iu-bg); border: 1px solid var(--iu-line); border-radius: 16px; box-shadow: 0 24px 60px rgba(0,0,0,0.5), 0 2px 6px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.04); overflow: hidden; animation: iu-pop 0.18s ease-out; } @keyframes iu-pop { from { opacity: 0; transform: translateY(8px) scale(0.985); } to { opacity: 1; transform: translateY(0) scale(1); } } #${APP_ID} .iu-header { flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 10px 12px 14px; border-bottom: 1px solid var(--iu-line); cursor: grab; user-select: none; background: linear-gradient(180deg, rgba(255,255,255,0.02), transparent); } #${APP_ID} .iu-header.iu-dragging { cursor: grabbing; } #${APP_ID} .iu-brand { display: flex; align-items: center; gap: 10px; min-width: 0; } #${APP_ID} .iu-brand-dot { width: 32px; height: 32px; display: grid; place-items: center; border-radius: 9px; background: var(--iu-gradient); color: #fff; flex-shrink: 0; box-shadow: 0 4px 12px rgba(214,41,118,0.3); } #${APP_ID} .iu-brand-dot svg { width: 18px; height: 18px; } #${APP_ID} .iu-brand-text { min-width: 0; } #${APP_ID} .iu-brand-text strong { display: flex; align-items: center; gap: 6px; font-size: 14px; font-weight: 600; white-space: nowrap; } #${APP_ID} .iu-brand-text [data-subtitle] { display: block; font-size: 11px; color: var(--iu-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } #${APP_ID} .iu-version { padding: 1px 6px; border-radius: 999px; background: var(--iu-bg-3); color: var(--iu-muted); font-size: 10px; font-weight: 600; letter-spacing: 0.02em; } #${APP_ID} .iu-header-actions { display: flex; gap: 4px; flex-shrink: 0; } #${APP_ID} .iu-header-actions button { width: 28px; height: 28px; display: grid; place-items: center; border: none; background: transparent; border-radius: 6px; color: var(--iu-muted); transition: background 0.15s, color 0.15s; font-size: 11px; font-weight: 600; } #${APP_ID} .iu-header-actions button:hover { background: var(--iu-bg-2); color: var(--iu-text); } #${APP_ID} .iu-header-actions svg { display: block; } #${APP_ID} .iu-body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; overflow: hidden; } #${APP_ID} .iu-welcome { padding: 26px 22px 22px; text-align: center; display: flex; flex-direction: column; align-items: center; gap: 10px; } #${APP_ID} .iu-welcome-icon { width: 64px; height: 64px; display: grid; place-items: center; border-radius: 50%; background: linear-gradient(135deg, rgba(250,126,30,0.16), rgba(214,41,118,0.2), rgba(150,47,191,0.2)); color: var(--iu-accent-2); margin-bottom: 4px; } #${APP_ID} .iu-welcome-icon--error { background: rgba(240,106,93,0.15); color: var(--iu-danger-text); } #${APP_ID} .iu-welcome h2 { margin: 0; font-size: 18px; font-weight: 700; letter-spacing: -0.01em; } #${APP_ID} .iu-welcome p.iu-welcome-note { max-width: none; margin-top: 4px; padding: 10px 12px; border: 1px solid var(--iu-line); border-radius: 10px; background: var(--iu-bg-2); text-align: left; font-size: 12px; line-height: 1.5; } #${APP_ID} .iu-welcome > .iu-btn--lg { align-self: stretch; } #${APP_ID} .iu-welcome p { margin: 0; color: var(--iu-muted); font-size: 13px; max-width: 300px; } #${APP_ID} .iu-welcome-actions { display: flex; flex-direction: column; align-items: center; gap: 8px; margin-top: 6px; } #${APP_ID} .iu-notice { margin: 12px 16px 0; padding: 12px 14px; border: 1px solid rgba(240,180,80,0.45); border-radius: 10px; background: rgba(240,180,80,0.10); font-size: 13px; } #${APP_ID} .iu-notice strong { display: block; margin-bottom: 4px; font-weight: 600; } #${APP_ID} .iu-notice p { margin: 0; color: var(--iu-muted); font-size: 12px; line-height: 1.45; } #${APP_ID} .iu-notice-reason { margin-top: 6px !important; color: var(--iu-text) !important; } #${APP_ID} .iu-notice-actions { display: flex; gap: 8px; margin-top: 10px; } #${APP_ID} .iu-btn { border: 1px solid var(--iu-line); background: var(--iu-bg-2); color: var(--iu-text); padding: 8px 14px; border-radius: 8px; font-size: 13px; font-weight: 500; transition: background 0.15s, border-color 0.15s, transform 0.05s; } #${APP_ID} .iu-btn:hover:not(:disabled) { background: var(--iu-bg-3); border-color: var(--iu-line-strong); } #${APP_ID} .iu-btn:active:not(:disabled) { transform: scale(0.98); } #${APP_ID} .iu-btn:disabled { opacity: 0.4; cursor: not-allowed; } #${APP_ID} .iu-btn--primary { background: var(--iu-accent); border-color: var(--iu-accent); color: #fff; box-shadow: 0 6px 18px rgba(214,41,118,0.28); } #${APP_ID} .iu-btn--primary:hover:not(:disabled) { background: var(--iu-accent-hover); border-color: var(--iu-accent-hover); } #${APP_ID} .iu-btn--danger { background: var(--iu-danger); border-color: var(--iu-danger); color: #fff; } #${APP_ID} .iu-btn--danger:hover:not(:disabled) { background: #8f1d16; border-color: #8f1d16; } #${APP_ID} .iu-btn--ghost { background: transparent; } #${APP_ID} .iu-btn--lg { padding: 11px 20px; border-radius: 10px; font-size: 14px; font-weight: 600; margin-top: 8px; } #${APP_ID} .iu-btn--small { padding: 6px 10px; font-size: 12px; } #${APP_ID} .iu-progress { padding: 22px 20px; display: flex; flex-direction: column; gap: 14px; } #${APP_ID} .iu-progress-head h2 { margin: 0; font-size: 15px; font-weight: 600; } #${APP_ID} .iu-progress-head p { margin: 4px 0 0; color: var(--iu-muted); font-size: 12px; min-height: 1em; } #${APP_ID} .iu-bar { height: 6px; border-radius: 3px; background: rgba(255,255,255,0.06); overflow: hidden; } #${APP_ID} .iu-bar > span { display: block; height: 100%; background: var(--iu-gradient); transition: width 0.3s ease; } #${APP_ID} .iu-progress-meta { display: flex; justify-content: space-between; align-items: center; font-size: 12px; } #${APP_ID} .iu-progress-actions { display: flex; gap: 8px; } #${APP_ID} .iu-current { display: flex; align-items: center; gap: 8px; padding: 10px; background: var(--iu-bg-2); border-radius: 8px; font-size: 12px; } #${APP_ID} .iu-current strong { font-weight: 600; } #${APP_ID} .iu-blocked { padding: 10px; border-radius: 8px; font-size: 12px; line-height: 1.5; color: #fecaca; background: rgba(240, 106, 93, 0.12); border: 1px solid rgba(240, 106, 93, 0.35); } #${APP_ID} .iu-results { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; } #${APP_ID} .iu-results-summary { padding: 12px 16px 4px; font-size: 13px; color: var(--iu-muted); } #${APP_ID} .iu-search-row { padding: 8px 16px; } #${APP_ID} .iu-search { width: 100%; height: 36px; padding: 0 12px; border: 1px solid var(--iu-line); border-radius: 8px; background: var(--iu-bg-2); color: var(--iu-text); transition: border-color 0.15s; } #${APP_ID} .iu-search:focus { border-color: var(--iu-accent); } #${APP_ID} .iu-search::-webkit-search-cancel-button { filter: opacity(0.6); cursor: pointer; } #${APP_ID} .iu-filters { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 16px 10px; } #${APP_ID} .iu-chip { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; border: 1px solid var(--iu-line); border-radius: 999px; background: transparent; color: var(--iu-muted); font-size: 12px; transition: border-color 0.15s, color 0.15s, background 0.15s; } #${APP_ID} .iu-chip:hover { border-color: var(--iu-line-strong); color: var(--iu-text); } #${APP_ID} .iu-chip--on { background: rgba(214,41,118,0.12); border-color: rgba(214,41,118,0.5); color: var(--iu-accent-2); } #${APP_ID} .iu-chip-tick { display: inline-grid; place-items: center; width: 12px; height: 12px; } #${APP_ID} .iu-chip-tick svg { width: 12px; height: 12px; } #${APP_ID} .iu-list { flex: 1 1 auto; min-height: 100px; max-height: 50vh; overflow-y: auto; overscroll-behavior: contain; border-top: 1px solid var(--iu-line); border-bottom: 1px solid var(--iu-line); } #${APP_ID} .iu-list-empty { padding: 30px 20px; text-align: center; color: var(--iu-muted); font-size: 13px; } #${APP_ID} .iu-row { display: grid; grid-template-columns: 18px 36px 1fr auto; gap: 10px; padding: 8px 16px; align-items: center; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,0.04); transition: background 0.1s; } #${APP_ID} .iu-row:hover { background: rgba(240,240,250,0.04); } #${APP_ID} .iu-row:focus-visible { outline: 2px solid var(--iu-accent); outline-offset: -2px; } #${APP_ID} .iu-row--selected { background: rgba(214,41,118,0.14); box-shadow: inset 3px 0 0 var(--iu-accent); } #${APP_ID} .iu-row--selected:hover { background: rgba(214,41,118,0.2); } #${APP_ID} .iu-row:last-child { border-bottom: none; } #${APP_ID} .iu-row--hidden { opacity: 0.55; } #${APP_ID} .iu-row-check { width: 16px; height: 16px; accent-color: var(--iu-accent); cursor: pointer; margin: 0; } #${APP_ID} .iu-avatar { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; background: var(--iu-bg-3); } #${APP_ID} .iu-row-text { min-width: 0; } #${APP_ID} .iu-row-name { display: flex; align-items: center; gap: 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 13px; font-weight: 500; } #${APP_ID} .iu-row-name > a { color: var(--iu-text); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } #${APP_ID} .iu-row-name a:hover { color: var(--iu-accent-2); } #${APP_ID} .iu-row-sub { font-size: 12px; color: var(--iu-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } #${APP_ID} .iu-row-actions { display: flex; gap: 4px; align-items: center; } #${APP_ID} .iu-icon-btn { display: inline-grid; place-items: center; width: 28px; height: 28px; border-radius: 6px; border: none; background: transparent; color: var(--iu-muted); transition: background 0.15s, color 0.15s; } #${APP_ID} .iu-icon-btn:hover { background: var(--iu-bg-2); color: var(--iu-text); } #${APP_ID} .iu-text-btn { width: auto; padding: 0 8px; font-size: 11px; font-weight: 500; } #${APP_ID} .iu-tag { display: inline-flex; align-items: center; flex-shrink: 0; padding: 1px 6px; border-radius: 4px; font-size: 10px; font-weight: 600; background: rgba(255,255,255,0.06); color: var(--iu-muted); letter-spacing: 0.02em; text-transform: uppercase; } #${APP_ID} .iu-tag--blue { background: rgba(214,41,118,0.15); color: var(--iu-accent-2); } #${APP_ID} .iu-tag--green { background: rgba(34,197,94,0.15); color: var(--iu-success); } #${APP_ID} .iu-tag--red { background: rgba(240,106,93,0.15); color: var(--iu-danger-text); } #${APP_ID} .iu-actionbar { flex-shrink: 0; display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 10px 16px; background: var(--iu-bg); border-top: 1px solid var(--iu-line); } #${APP_ID} .iu-actionbar-left, #${APP_ID} .iu-actionbar-right { display: flex; align-items: center; gap: 8px; min-width: 0; } #${APP_ID} .iu-muted { color: var(--iu-muted); font-size: 12px; } #${APP_ID} .iu-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.55); display: grid; place-items: center; padding: 20px; z-index: 1; } #${APP_ID} .iu-dialog { width: 100%; max-width: 380px; background: var(--iu-bg); border: 1px solid var(--iu-line); border-radius: 12px; padding: 18px; box-shadow: 0 30px 80px rgba(0,0,0,0.55); } #${APP_ID} .iu-dialog h3 { margin: 0 0 8px; font-size: 16px; } #${APP_ID} .iu-dialog p { margin: 0 0 14px; color: var(--iu-muted); font-size: 13px; } #${APP_ID} .iu-dialog-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; } #${APP_ID} .iu-dialog-actions [data-extra] { margin-right: auto; } #${APP_ID} .iu-form { display: grid; gap: 8px; max-height: 50vh; overflow-y: auto; padding-right: 4px; } #${APP_ID} .iu-field { display: grid; grid-template-columns: 1fr 110px; align-items: center; gap: 10px; font-size: 12px; color: var(--iu-muted); } #${APP_ID} .iu-field input { height: 32px; padding: 0 10px; border: 1px solid var(--iu-line); border-radius: 6px; background: var(--iu-bg-2); color: var(--iu-text); } #${APP_ID} .iu-field input:focus { border-color: var(--iu-accent); } #${APP_ID} .iu-toast { position: absolute; left: 50%; bottom: 12px; transform: translateX(-50%); padding: 8px 14px; background: rgba(28,28,35,0.96); border: 1px solid var(--iu-line); border-radius: 999px; font-size: 12px; box-shadow: 0 6px 20px rgba(0,0,0,0.35); pointer-events: none; animation: iu-pop 0.18s ease-out; } #${APP_ID} .iu-pill { position: absolute; display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px; background: var(--iu-bg); border: 1px solid var(--iu-line); color: var(--iu-text); border-radius: 999px; box-shadow: 0 12px 30px rgba(0,0,0,0.4); font-size: 12px; font-weight: 500; transition: background 0.15s; cursor: pointer; } #${APP_ID} .iu-pill:hover { background: var(--iu-bg-2); } #${APP_ID} .iu-pill-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--iu-muted); } #${APP_ID} .iu-pill-dot--active { background: var(--iu-accent); animation: iu-pulse 1.4s infinite; } #${APP_ID} .iu-pill-dot--error { background: var(--iu-danger-text); } @keyframes iu-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } } @media (max-width: 480px) { #${APP_ID} .iu-panel { width: calc(100vw - 16px) !important; max-height: calc(100vh - 16px) !important; left: 8px !important; right: 8px !important; top: 8px !important; bottom: 8px !important; } #${APP_ID} .iu-list { max-height: none; flex: 1 1 auto; } } @media (prefers-reduced-motion: reduce) { #${APP_ID} *, #${APP_ID} *::before, #${APP_ID} *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } } `;  if (globalThis.__IU_TEST__) {    globalThis.__IU_TEST__({      addFollowBackStatus,      evaluateUnfollowResponse,      fetchFriendshipList,      friendshipListUrl,      igFetch,      createCheckpoint,      loadCheckpoint,      loadTimings,      waitBeforeNextScanPage,      startScan,      showResults,      renderResultsView,      getDisplayUsers,      startUnfollow,      unmount,      state,      interruptibleSleep,      sleepWithCountdown,      unfollowUser,      normalizeUser,      isDefaultAvatar,      parseRetryAfter,      I18N,      t,    });    return;  }  cleanupExisting();  injectStyles();  mount();})(); 

How to use it

Five steps and about two minutes. You need a computer, a browser and an Instagram account — nothing else to install, sign up for or pay for.

  1. 1

    Open Instagram on a computer

    Go to instagram.com in a desktop browser and sign in to the account you want to check.

    • Works in Chrome, Edge, Brave, Opera and Firefox. Safari works too, but you have to enable the Develop menu first: Safari → Settings → Advanced → “Show features for web developers”.
    • It has to be a computer. Mobile browsers on iOS and Android have no JavaScript console, so there is no way to run the script from a phone.
    • If you manage more than one account, switch to the right one before you start. The scan always reads whichever account is currently active.
    • Stay on instagram.com the whole time. The script checks the domain and refuses to run anywhere else.
  2. 2

    Open the developer console

    The console is a panel built into every desktop browser. Open it with the keyboard shortcut for your browser, then click the tab labelled “Console”.

    BrowserWindows / LinuxmacOS
    Chrome, Edge, BraveCtrl + Shift + J⌘ + ⌥ + J
    FirefoxCtrl + Shift + K⌘ + ⌥ + K
    Safari—⌘ + ⌥ + C
    • A panel opens at the bottom or the side of the window. If it lands on “Elements” or “Network”, click across to the “Console” tab.
    • Prefer the mouse? Right-click anywhere on the page, choose “Inspect”, then switch to the Console tab.
    • You will probably see existing yellow and red messages from Instagram itself. That is normal — you can ignore them.

    Heads up: Instagram prints a big red “Stop!” warning in the console. It is a generic warning aimed at scams that talk people into pasting code that hijacks their account. It is genuinely good advice, which is exactly why this script is published in plain, commented, unminified form. Read it before you paste it.

  3. 3

    Paste the code and press Enter

    Click “Copy the code” at the top of this page, click once inside the console so the cursor is blinking next to the > prompt, then paste and press Enter.

    • Paste with Ctrl + V, or ⌘ + V on macOS.
    • Chrome and Edge block the very first paste into the console. If you see “Warning: Don’t paste code you don’t understand”, type allow pasting, press Enter, then paste the script again. You only ever do this once per browser.
    • Press Enter to run it. A panel slides in at the top right corner of the page and the console prints Instagram Unfollowers ready.
    • Nothing appeared? Read the message in the console and check the troubleshooting list below — it usually means you are on the wrong page or signed out.
  4. 4

    Press “Start scan” and let it run

    Click Start scan in the panel. It reads the accounts you follow first, then the accounts that follow you, showing a progress bar for each pass.

    • Instagram returns 50 accounts per request, and the tool waits about a second between them. An account with 1,000 following and 1,000 followers needs roughly 40 requests — about a minute.
    • That pause is deliberate. Hammering the endpoint is what triggers Instagram’s rate limits and temporary action blocks, so the tool stays at a comfortable pace on purpose.
    • Keep the tab open and in the foreground. Browsers throttle timers in background tabs, which makes the scan crawl.
    • If Instagram throttles anyway, the panel shows “Rate limited. Waiting…” and retries automatically with a longer pause each time. You do not need to do anything.
    • You can hit Stop at any point and still see the results from everything scanned so far.
  5. 5

    Work through your results

    When the scan finishes you get the full list of accounts you follow that do not follow you back, newest data first.

    • Type in the search box to filter by username or display name.
    • Click any row to open that profile in a new tab, where you can unfollow it yourself.
    • Use “Copy usernames” to put the whole list on your clipboard, or “Export CSV” to open it in Excel, Numbers or Google Sheets.
    • Unfollow at a human pace — a handful at a time, not hundreds in one sitting. Rapid mass-unfollowing is the single fastest way to get an account temporarily action-blocked.
    • The list is a snapshot. Run the scan again whenever you want fresh numbers.

If something goes wrong

Almost every problem is one of these six, and all of them are fixable in a few seconds.

“Run this on instagram.com”
The script only runs on Instagram itself. Navigate to instagram.com in the same tab and paste it again.
“You are not logged in”
The script identifies you from the session cookie Instagram already set. Sign in, refresh the page, then paste the script again.
Nothing happened after I pressed Enter
Check that you are on the Console tab rather than Elements or Network, and that the whole script was pasted — a partial paste throws a syntax error in red. Copy it again and retry.
It keeps saying “Rate limited. Waiting…”
Instagram is throttling you. The tool backs off and retries on its own. If it does not recover, close the panel and come back in ten to fifteen minutes — this happens most often on very large accounts or after several scans in a row.
“Your session expired”
Reload instagram.com, confirm you are still signed in, and run the script again.
Accounts I already unfollowed are still listed
Results are a snapshot from when the scan ran. Press “Scan again” to refresh the list.

What you get

Everything happens in one panel on top of Instagram.

The full non-follower list

Every account you follow that does not follow you back, with their profile picture, username, and display name. Click any row to open that profile in a new tab.

Search and filter

Type any username or name to narrow a list of hundreds down to the handful you actually care about.

Copy or export

Copy every username to your clipboard in one click, or download the whole list as a CSV to open in Excel, Numbers, or Google Sheets.

No account, no password

There is no sign-up, no email, and nothing to install. You never hand over your Instagram password to anyone — including us.

Privacy & security

Most “unfollowers” apps ask you to log in with your Instagram credentials. That is the one thing you should never do. Here is exactly what this tool does instead.

It never asks for your password
The script uses the session already active in your browser tab. There is no login form anywhere in it.
Nothing is sent to us
The only network requests the script makes are to instagram.com — the same ones the app itself makes when you scroll your followers list. No analytics, no telemetry, no upload. Open the Network tab and watch it run.
It is read-only
The script reads your follower and following lists. It never follows, unfollows, blocks, posts, or changes anything on your account.
You can read the code before you run it
The whole thing is a single readable, commented file — nothing minified or obfuscated. Open it, read it, and only paste it if you are comfortable with what it does.
Our app

Cleaned up your following list? Now grow it.

Unfollowing accounts that never followed you back is the easy half. The hard half is getting real engagement on what you post. That is what we built Boostly for — more likes and views on your Instagram posts, straight from your iPhone.

Download on the
App Store

Frequently asked questions

Is this Instagram unfollowers tool free?

Yes, completely. There is no sign-up, no trial, no credit card and no usage limit. You can run it as often as you like.

Do I have to give you my Instagram password?

No, and you should never give your Instagram password to any third-party unfollowers app. This tool runs inside your own browser and uses the session you are already logged into. It never sees or asks for your password.

Is it safe? Where does my data go?

Nowhere. The script reads your follower and following lists, compares them in your browser’s memory, and shows the result on screen. It makes no requests to likesforinstagram.com or any other server. You can verify this yourself: open the Network tab in DevTools while it runs and watch — the only requests are to instagram.com.

Can I get banned for using it?

The tool only reads data that Instagram already shows you, and it does not unfollow anyone automatically — that is exactly why we left bulk unfollowing out. It also pauses between requests to stay well inside normal usage. That said, very large accounts scanning repeatedly in a short window can hit Instagram’s temporary rate limits, so give it a break between runs.

Does it work on a phone?

Not really. You need a desktop browser with developer tools, so use Chrome, Edge, Firefox, or Safari on a computer. Once you have the list, you can export it as a CSV and work through it on your phone.

Will it unfollow people for me?

No, by design. Automated mass-unfollowing breaks Instagram’s terms of use and is a fast way to get an account restricted. This tool gives you the list and a direct link to each profile so you can unfollow the ones you actually want to, at your own pace.

Why does the scan take a few minutes?

Instagram returns followers 50 at a time, and the tool deliberately waits about a second between pages. An account following 2,000 people needs roughly 40 requests per list. Rushing that is what triggers rate limits, so the wait is intentional.

Read next

Guides on following, unfollowing and what your numbers actually mean.

More follower & unfollower guides

25 more walkthroughs on tracking, checking and cleaning up who you follow.

This tool is not affiliated with, endorsed by, or sponsored by Instagram or Meta. Instagram is a trademark of Meta Platforms, Inc. Use it on your own account and at your own discretion. Looking for more? Browse our free Instagram tools and guides.