followers!

1const username = "USER_NAME_HERE";
2
3/**
4 * Run in browser console on instagram.com while logged in.
5 * Live status refreshes every 2s.
6 * Final output: 2 labeled tables.
7 *
8 * Enrichment behavior:
9 * - Medium speed (concurrency 3 + 250ms delay).
10 * - If any enrichment request errors (especially 429), enrichment bails out immediately.
11 * - Tables still return, with follower-count columns as "NA"/null for skipped rows.
12 */
13
14let followers = [];
15let followings = [];
16let dontFollowMeBack = [];
17let iDontFollowBack = [];
18
19const profileCache = new Map();
20const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
21
22function formatCompact(n) {
23  if (typeof n !== "number" || Number.isNaN(n)) return "NA";
24  if (n < 1000) return String(n);
25
26  const units = [
27    { v: 1e9, s: "b" },
28    { v: 1e6, s: "m" },
29    { v: 1e3, s: "k" },
30  ];
31
32  for (const u of units) {
33    if (n >= u.v) {
34      const raw = n / u.v;
35      const rounded = raw >= 100 ? Math.round(raw) : Math.round(raw * 10) / 10;
36      return `${String(rounded).replace(/\.0$/, "")}${u.s}`;
37    }
38  }
39  return String(n);
40}
41
42function pct(done, total) {
43  if (!total || total <= 0) return "0%";
44  return `${Math.min(100, Math.floor((done / total) * 100))}%`;
45}
46
47function withCountFields(u, raw) {
48  return {
49    username: u.username,
50    full_name: u.full_name,
51    followers_count_compact: formatCompact(raw),
52    followers_count_raw: raw,
53  };
54}
55
56async function fetchJsonWithRetry(url, options, label, state, markDirty, maxRetries = 12) {
57  let attempt = 0;
58
59  while (true) {
60    try {
61      const res = await fetch(url, options);
62      if (!res.ok) {
63        const err = new Error(`HTTP ${res.status}`);
64        err.status = res.status;
65        throw err;
66      }
67      return await res.json();
68    } catch (err) {
69      attempt += 1;
70      const statusText = err?.status ? `HTTP ${err.status}` : (err?.message || "Unknown error");
71      state.notice = `Hit an error (${statusText}) on ${label}. Pausing 15s before retry...`;
72      markDirty();
73
74      if (attempt >= maxRetries) throw err;
75
76      await sleep(15000);
77      state.notice = "";
78      markDirty();
79    }
80  }
81}
82
83async function fetchProfileCountOnce(handle) {
84  const res = await fetch(
85    `https://www.instagram.com/api/v1/users/web_profile_info/?username=${encodeURIComponent(handle)}`,
86    { headers: { "x-ig-app-id": "936619743392459" } }
87  );
88  if (!res.ok) {
89    const err = new Error(`HTTP ${res.status}`);
90    err.status = res.status;
91    throw err;
92  }
93  const json = await res.json();
94  return json?.data?.user?.edge_followed_by?.count ?? null;
95}
96
97async function getFollowersCountByUsername(handle) {
98  if (profileCache.has(handle)) return profileCache.get(handle);
99  const count = await fetchProfileCountOnce(handle);
100  profileCache.set(handle, count);
101  return count;
102}
103
104function applyDone(state, type, done, total) {
105  if (type === "followers_star") {
106    state.followersStarDone = done;
107    state.followersStarTotal = total;
108  } else {
109    state.followingStarDone = done;
110    state.followingStarTotal = total;
111  }
112}
113
114async function enrichWithFollowersCount(
115  users,
116  type,
117  state,
118  markDirty,
119  { concurrency = 3, delayMs = 250 } = {}
120) {
121  const out = users.map((u) => ({ ...u }));
122  const total = out.length;
123  let idx = 0;
124  let done = 0;
125
126  applyDone(state, type, done, total);
127  markDirty();
128
129  if (state.enrichmentAborted) {
130    for (let i = 0; i < total; i++) out[i] = withCountFields(out[i], null);
131    applyDone(state, type, total, total);
132    markDirty();
133    return out;
134  }
135
136  async function worker() {
137    while (idx < total) {
138      if (state.enrichmentAborted) break;
139
140      const current = idx++;
141      const user = out[current];
142
143      try {
144        const raw = await getFollowersCountByUsername(user.username);
145        out[current] = withCountFields(user, raw);
146      } catch (err) {
147        state.enrichmentAborted = true;
148        const status = err?.status ? `HTTP ${err.status}` : (err?.message || "Unknown error");
149        state.notice = `Enrichment hit ${status}. Stopping enrichment and returning base tables without follower counts.`;
150        out[current] = withCountFields(user, null);
151      }
152
153      done += 1;
154      applyDone(state, type, done, total);
155      markDirty();
156
157      if (!state.enrichmentAborted && delayMs) {
158        const jitter = Math.floor(Math.random() * 250);
159        await sleep(delayMs + jitter);
160      }
161    }
162  }
163
164  const workers = Array.from(
165    { length: Math.min(concurrency, Math.max(total, 1)) },
166    () => worker()
167  );
168  await Promise.all(workers);
169
170  for (let i = 0; i < total; i++) {
171    if (!("followers_count_raw" in out[i])) out[i] = withCountFields(out[i], null);
172  }
173
174  applyDone(state, type, total, total);
175  markDirty();
176
177  return out;
178}
179
180(async () => {
181  const state = {
182    followersLoaded: 0,
183    followersTotal: 0,
184    followingsLoaded: 0,
185    followingsTotal: 0,
186    followersStarDone: 0,
187    followersStarTotal: 0,
188    followingStarDone: 0,
189    followingStarTotal: 0,
190    enrichmentAborted: false,
191    notice: "",
192  };
193
194  let dirty = true;
195  const markDirty = () => {
196    dirty = true;
197  };
198
199  const render = () => {
200    if (!dirty) return;
201    dirty = false;
202
203    console.clear();
204    console.log(
205      `Followers: ${state.followersLoaded} out of ${state.followersTotal} (${pct(
206        state.followersLoaded,
207        state.followersTotal
208      )})`
209    );
210    console.log(
211      `Following: ${state.followingsLoaded} out of ${state.followingsTotal} (${pct(
212        state.followingsLoaded,
213        state.followingsTotal
214      )})`
215    );
216    console.log(
217      `Followers ⭐: ${state.followersStarDone} out of ${state.followersStarTotal} (${pct(
218        state.followersStarDone,
219        state.followersStarTotal
220      )})`
221    );
222    console.log(
223      `Following ⭐: ${state.followingStarDone} out of ${state.followingStarTotal} (${pct(
224        state.followingStarDone,
225        state.followingStarTotal
226      )})`
227    );
228
229    if (state.notice) {
230      console.log("");
231      console.log(state.notice);
232    }
233  };
234
235  const timer = setInterval(render, 2000);
236  render();
237
238  try {
239    const userQueryJson = await fetchJsonWithRetry(
240      `https://www.instagram.com/web/search/topsearch/?query=${encodeURIComponent(username)}`,
241      {},
242      "user lookup",
243      state,
244      markDirty
245    );
246
247    const userId = userQueryJson.users
248      .map((u) => u.user)
249      .find((u) => u.username === username)?.pk;
250
251    if (!userId) throw new Error(`Could not find user id for "${username}"`);
252
253    let after = null;
254    let has_next = true;
255    while (has_next) {
256      const query_hash = "c76146de99bb02f6415203be841dd25a";
257      const json = await fetchJsonWithRetry(
258        `https://www.instagram.com/graphql/query/?query_hash=${query_hash}&variables=` +
259          encodeURIComponent(
260            JSON.stringify({
261              id: userId,
262              include_reel: true,
263              fetch_mutual: true,
264              first: 50,
265              after,
266            })
267          ),
268        {},
269        "followers list",
270        state,
271        markDirty
272      );
273
274      const edge = json?.data?.user?.edge_followed_by;
275      if (!state.followersTotal) state.followersTotal = edge?.count ?? 0;
276      has_next = Boolean(edge?.page_info?.has_next_page);
277      after = edge?.page_info?.end_cursor ?? null;
278
279      followers = followers.concat(
280        (edge?.edges ?? []).map(({ node }) => ({
281          username: node.username,
282          full_name: node.full_name,
283        }))
284      );
285
286      state.followersLoaded = followers.length;
287      markDirty();
288    }
289
290    after = null;
291    has_next = true;
292    while (has_next) {
293      const query_hash = "d04b0a864b4b54837c0d870b0e77e076";
294      const json = await fetchJsonWithRetry(
295        `https://www.instagram.com/graphql/query/?query_hash=${query_hash}&variables=` +
296          encodeURIComponent(
297            JSON.stringify({
298              id: userId,
299              include_reel: true,
300              fetch_mutual: true,
301              first: 50,
302              after,
303            })
304          ),
305        {},
306        "followings list",
307        state,
308        markDirty
309      );
310
311      const edge = json?.data?.user?.edge_follow;
312      if (!state.followingsTotal) state.followingsTotal = edge?.count ?? 0;
313      has_next = Boolean(edge?.page_info?.has_next_page);
314      after = edge?.page_info?.end_cursor ?? null;
315
316      followings = followings.concat(
317        (edge?.edges ?? []).map(({ node }) => ({
318          username: node.username,
319          full_name: node.full_name,
320        }))
321      );
322
323      state.followingsLoaded = followings.length;
324      markDirty();
325    }
326
327    const followerSet = new Set(followers.map((u) => u.username));
328    const followingSet = new Set(followings.map((u) => u.username));
329
330    iDontFollowBack = followers.filter((u) => !followingSet.has(u.username));
331    dontFollowMeBack = followings.filter((u) => !followerSet.has(u.username));
332
333    iDontFollowBack = await enrichWithFollowersCount(
334      iDontFollowBack,
335      "followers_star",
336      state,
337      markDirty
338    );
339
340    dontFollowMeBack = await enrichWithFollowersCount(
341      dontFollowMeBack,
342      "following_star",
343      state,
344      markDirty
345    );
346
347    clearInterval(timer);
348    console.clear();
349
350    console.log("%c(Followers) People who follow you that you don't follow back", "font-weight:700;");
351    console.table(iDontFollowBack);
352
353    console.log("%c(Following) People you follow who don't follow you back", "font-weight:700;");
354    console.table(dontFollowMeBack);
355  } catch (err) {
356    clearInterval(timer);
357    console.clear();
358    console.error("Script failed:", err);
359  }
360})();
361