ENFRKOantony langlois
work

Instagram Unfollower Scanner

visitchrome extensionChrome ExtensionJavaScript

Chrome extension that finds who doesn't follow you back · no backend, no credential handoff · rides the user's own Instagram session through an injected content script

Architecture

vanilla JS
Frontend · action popup
Popup UI
popup · popup.js
Restores the last scan on open, renders category lists, confirm-to-unfollow flow
popup thread
Analysis · influencer scoring
Profile categorizer
popup.js · categorizeUsers
Scores accounts (verified, follower ratio, business) to split influencers from regular users
runs on instagram.com
Core · injected API client
Content script
content · content.js
Injected into the Instagram page so every fetch carries the user's own session cookies
cursor pagination429 kill switch300 ms pacingCSRF from cookiesfollow / unfollow POSTprofile hydration
5 fallbacks
Identity · login detection
Session detector
content.js · isUserLoggedIn
Finds login state and user id across 40+ UI languages via alt-text, nav heuristics, cookies
per-account keys
Storage · local
chrome.storage.local
instagramAnalysisResults_<id>
Last scan with timestamp per account, 7-day profile cache, recently-unfollowed list
api/v1
External · private API
Instagram web API
friendships · web_profile_info
Undocumented endpoints, authenticated by session cookies plus the web app id header

You open the popup on any instagram.com tab. Popup UI first restores the previous scan from chrome.storage.local and checks the 24 h cooldown. On Analyze it messages the Content script, which asks the Session detector for the logged-in user id (cookies first, profile fetch as fallback). The content script then pages through /friendships/&lt;id&gt;/followers (25 per page) and /following (200 per page) against the Instagram web API with 300 ms pacing between requests. Back in the popup, the diff yields the not-following-back set, the Profile categorizer hydrates each account in batches of 5 to separate influencers from regular users, and results plus caches are written back to storage.

Deployed: Extension → Chrome · Manifest V3, loaded unpacked or via Web Store

Outcomes

  • 0 · servers · everything runs inside the user's browser
  • 40+ · UI languages the login detection survives
  • 1st 429 · trips a kill switch, scan degrades instead of failing
  • 24 h · scan cooldown per account, results restored meanwhile

Skills demonstrated: private API reverse-engineering · rate-limit-aware pagination · cache invalidation (7-day TTL) · Chrome message passing · CSRF token handling

Problems and solutions

One constraint drives everything: Instagram has no public API for follower lists, so the extension must reuse the user's own session without ever holding credentials or tripping rate limits.

█ Ride the user's session, not a backend

Problem: There is no official API to list followers. A server-side scraper would need the user's password or stolen cookies, plus proxies to survive blocks. Both are dealbreakers for trust and cost.

A content script injected into instagram.com calls the same private endpoints the web app uses. Requests go out with credentials: include, the browser's own user agent, and the web app id header, so to Instagram they look like the user browsing.

  • Rejected a scraping backend: zero credential handling beats full control of the pipeline
  • Rejected DOM-scraping the followers modal: brittle against markup changes and far slower than JSON endpoints
  • Accepted trade-off: undocumented endpoints can change without notice, so the code treats every response as optional

▒ Rate limits: pace, detect, degrade

Problem: Follower pagination plus one profile fetch per account can mean hundreds of requests. Hammering private endpoints earns a 429 and can flag the user's real account.

300 ms pauses between pages, profile hydration in batches of 5, and a global kill switch: the first 429 stops all further profile fetches for the session and the scan continues with basic data only.

  • Rejected retry-with-backoff: retrying against an anti-abuse system digs the hole deeper
  • Degrading beats failing: users still get the full not-following-back list, just without influencer scores
  • The kill switch is a one-way flag, deliberately conservative to protect the user's account

▚ 24-hour scan cooldown

Problem: The full scan is the heaviest operation. Users clicking Analyze repeatedly multiply the block risk for zero new information.

Each scan is stamped per account in chrome.storage.local. Within 24 h the button is disabled with a live countdown and the saved results are shown instead.

  • Rejected unlimited scans: protecting the user's account outranks freshness
  • Accepted trade-off: data can be up to a day stale
  • Follow / unfollow actions update the saved results in place, so the cached view stays correct between scans

▞ Login detection in 40+ languages

Problem: The first version detected login by the English alt text 'profile picture'. Any non-English Instagram UI made the extension think the user was logged out.

Five layered fallbacks: a localized alt-text table covering 40+ languages, a geometry heuristic (small square image inside the nav), Instagram's own data objects, the logout link, and session cookies.

  • Rejected a single 'reliable' selector: Instagram's obfuscated class names change too often to pin
  • Heuristics can false-positive individually, so five independent signals cross-check each other
  • Cookie check is the last resort because cookies can outlive a real session

▓ Cache aggressively, key by account

Problem: Profile hydration costs one request per analyzed user. Rescans and account switches would re-pay that cost and re-risk rate limits.

Per-username analysis results live in a 7-day cache; scan results are keyed by account id, so people managing several accounts keep separate histories that survive switching.

  • Rejected no-cache simplicity: repeat requests are the scarcest resource here
  • 7 days balances staleness against request budget, follower counts move slowly
  • When the 429 kill switch fires, cached profiles still fill in scores for free

▤ Unfollow without losing history

Problem: Unfollowing someone while reviewing the list mutates the data underneath: the user disappears, and a rescan to reflect it would burn the daily scan.

Unfollowed accounts move to a persisted recently-unfollowed list instead of vanishing. Refollowing restores them to the regular list, and every change edits the saved results in place.

  • Rejected rescan-after-action: one unfollow should not cost a full scan
  • Rejected silent removal: users want an undo path and a record of who they dropped
  • State transitions live in storage, so closing the popup mid-cleanup loses nothing