1const username = "USER_NAME_HERE";
2
312
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