{"id":21477549,"url":"https://github.com/5ht2/twitter-follower-rank","last_synced_at":"2026-02-14T16:33:09.967Z","repository":{"id":208421891,"uuid":"721593114","full_name":"5HT2/twitter-follower-rank","owner":"5HT2","description":"A no-API-access way to get stats about someone's followers","archived":false,"fork":false,"pushed_at":"2024-12-13T03:02:05.000Z","size":1123,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-12T02:51:03.697Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/5HT2.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-11-21T11:34:09.000Z","updated_at":"2025-01-04T02:22:13.000Z","dependencies_parsed_at":null,"dependency_job_id":"50b42c39-817c-44f3-8bd7-fd41ffbe7276","html_url":"https://github.com/5HT2/twitter-follower-rank","commit_stats":null,"previous_names":["5ht2c/twitter-follower-rank","5ht2/twitter-follower-rank"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/5HT2/twitter-follower-rank","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5HT2%2Ftwitter-follower-rank","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5HT2%2Ftwitter-follower-rank/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5HT2%2Ftwitter-follower-rank/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5HT2%2Ftwitter-follower-rank/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/5HT2","download_url":"https://codeload.github.com/5HT2/twitter-follower-rank/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5HT2%2Ftwitter-follower-rank/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29449370,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-14T15:52:44.973Z","status":"ssl_error","status_checked_at":"2026-02-14T15:52:11.208Z","response_time":53,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-23T11:14:14.796Z","updated_at":"2026-02-14T16:33:09.952Z","avatar_url":"https://github.com/5HT2.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# twitter-follower-rank\n\n#### A simple Go program to print a Twitter User's list of followers, sorted by number of followers.\n\n\u003e [!CAUTION]\n\u003e This does **not** use or require API access.\n\u003e Please read [disclaimer(s) section](#disclaimers) first.\n\n![](./.github/IMAGES/preview.png)\n\n\u003e [!IMPORTANT]\n\u003e #### Pros\n\u003e - No $$$ API key\n\u003e - It just works\n\u003e\n\u003e#### Cons\n\u003e - Come up with some way to scroll down. I did it by hand.\n\u003e - After ~2k followers I got rate-limited with a 429 response. Go make some tea and come back.\n\n\n## Usage\n\n```bash\n# Get repo \u0026\u0026 cd\ngit clone https://github.com/5HT2/twitter-follower-rank \u0026\u0026 cd twitter-follower-rank\n```\n```bash\n# Build\nmake\n# Once you have a data.json, run:\n./twitter-follower-rank\n# That's it!\n```\n\n\u003c!-- GENERATED FROM MAKEFILE --\u003e\n```\nUsage of ./twitter-follower-rank:\n  -f string\n        Data file to read (default \"data.json\" or matching \"data-[0-9]{4}-[0-9]{2}-[0-9]{2}(-following)?.json\")\n  -following\n        Invert mutuals detection mode to the following tab instead of the followers tab\n  -maxFollowers int\n        Filter to \u003c= this many followers\n  -maxFollowing int\n        Filter to \u003c= this many following\n  -minFollowers int\n        Filter to \u003e= this many followers\n  -minFollowing int\n        Filter to \u003e= this many following\n  -ratio\n        Only display followers with a following:follower ratio of \u003e= -ratioBuf\n  -ratioBuf float\n        Buffer for ranked. e.g. If set to 0.9, it will display if (followers / following \u003e= 0.9) (default 0.9)\n```\n\u003c!-- GENERATED FROM MAKEFILE --\u003e\n\n### Auto select `data.json` file\n\n\u003cdetails\u003e\u003csummary\u003eAn explanation of how automatically selecting a data file works:\u003c/summary\u003e\n\n---\n\nThe dates given here are **examples**, to show how the sort order works. It will use the first valid file that it finds.\n- The flag `-f` is always prioritized as first, and is only ignored if the file cannot be read / does not exist.\n- The `data.json` value is always **last** as a final fallback.\n- The rest of the files are found from the existing files in the current working directory.\n\nBy default, the data file will by selected in this order:\n```\nmy-custom-file.json (optionally set by -f)\ndata-2024-04-20.json\ndata-2024-03-01.json\ndata-2023-11-30.json\ndata.json\n```\n\nIf you have the `-following` flag set it would instead look like this:\n```\nmy-custom-file.json (optionally set by -f)\ndata-2024-04-20-following.json\ndata-2024-03-01-following.json\ndata.json\n```\n\n---\n\n\u003c/details\u003e\n\n## What is a `data.json`\n\nThe way that this works is by leveraging Twitter's own Followers tab, and simply grabbing an object of all the requests.\n\nIn the future, I'll probably add a way to also do this via Twitter's GDPR data request, assuming it includes enough info to not have to scrape.\n\n\u003e [!TIP]\n\u003e TLDR:\n\u003e The `data.json` is created from the requests\n\u003e that your browser sends when loading a `/followers` or `/following` tab for a user profile.\n\n## How to get your very own `data.json`\n\n1. To do so, open the Chrome Dev Tools with \u003ckbd\u003eCtrl\u003c/kbd\u003e \u003ckbd\u003eShift\u003c/kbd\u003e \u003ckbd\u003eI\u003c/kbd\u003e, go to the network tab.\n2. In the network tab, search the text `Followers?`. This will filter it to only the requests we want.\n3. Now, open the followers menu on Twitter, or visit https://[domain]/username/followers.\n4. Scroll all the way to the bottom, just keep in mind that going too fast will rate-limit you. If you hit the limit, just wait 30min - 1h.\n5. Once at the bottom, open a _new_ Dev Tools window for your existing Dev Tools window with \u003ckbd\u003eCtrl\u003c/kbd\u003e \u003ckbd\u003eShift\u003c/kbd\u003e \u003ckbd\u003eI\u003c/kbd\u003e (yes, debug inception).\n6. In the new Dev Tools window, open the console tab (taken from StackOverflow[^1][^2]), works on Chrome 118 or newer) and run the following:\n\u003e [!IMPORTANT]\n\u003e If you encounter an error complaining about `r.contentData()`, use the newer ≥ 118 version.[^3]\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eChrome / Edge ≥ 118\u003c/b\u003e\u003c/summary\u003e\n\n```javascript\nlet followers = await (async () =\u003e {\n  const getContent = r =\u003e r.url() \u0026\u0026 !r.url().startsWith('data:') \u0026\u0026 r.contentData();\n  const nodes = UI.panels.network.networkLogView.dataGrid.rootNode().flatChildren();\n  const requests = nodes.map(n =\u003e n.request());\n  const contents = await Promise.all(requests.map(getContent));\n  return contents.map((data, i) =\u003e {\n    const r = requests[i];\n    const url = r.url();\n    const body = data?.content;\n    const content = !data ? url :\n        r.contentType().isTextType() ? data :\n            typeof body !== 'string' ? body :\n                `data:${r.mimeType}${data.encoded ? ';base64' : ''},${body}`;\n    return { url, content };\n  });\n})();\n```\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cb\u003eChrome / Edge ≥ 111 ≤ 117\u003c/b\u003e\u003c/summary\u003e\n\n  ```javascript\n  let followers = await (async () =\u003e {\n  const getContent = r =\u003e r.url() \u0026\u0026 !r.url().startsWith('data:') \u0026\u0026 r.contentData();\n  const nodes = UI.panels.network.networkLogView.dataGrid.rootNode().flatChildren();\n  const requests = nodes.map(n =\u003e n.request());\n  const contents = await Promise.all(requests.map(getContent));\n  return contents.map((data, i) =\u003e {\n  const r = requests[i];\n  const url = r.url();\n  const body = data?.content;\n  const content = !data ? url :\n  r.contentType().isTextType() ? data :\n  typeof body !== 'string' ? body :\n  `data:${r.mimeType}${data.encoded ? ';base64' : ''},${body}`;\n  return { url, content };\n  });\n  })();\n  ```\n\u003c/details\u003e\n7. Once you've scrolled all the way to the bottom, \u003ckbd\u003eRight Click\u003c/kbd\u003e → \u003ckbd\u003e\"Copy Object\"\u003c/kbd\u003e on the output of the last console command.\n\u003cimg src=\"./.github/IMAGES/chrome.png\" alt=\"Screenshot of Chrome Devtools, the user is right-clicking the object output\"/\u003e\n8. Congrats! Now you can create a file called `data.json` inside this project, and paste the JSON object into it.\n\n## Using the `data.json`\n\u003e [!TIP]\n\u003e The program itself handles all the parsing and such from that point.\n\u003e Please see [the usage section](#usage) for a list of program flags, the simplest arguments are as follows:\n\u003e ```shell\n\u003e # You can even omit the -f flag, as it will auto-select the file for you.\n\u003e ./twitter-follower-rank -f data.json\n\u003e ```\n\n---\n\n### Manual patch workaround\n\u003e [!TIP]\n\u003e **The patch is currently __unnecessary__!!**\n\u003e\n\u003e As of [fbf4d30](https://github.com/5HT2/twitter-follower-rank/commit/fbf4d3024a2f20509e40baf95ae79693c4fbd499) it is automagically supported.\n\n\u003e [!NOTE]\n\u003e Twitter attempted to patch this (multiple times).\n\u003e - As of 2024/03, Twitter switched `followers[n].content.content` → `followers[n].content.#g`\n\u003e - As of 2024/04, Twitter switched `followers[n].content.#g` → `followers[n].content.#e`\n\u003e - As of 2024/12, Twitter switched `followers[n].content.#e` → `followers[n].content`\n\n\u003cdetails\u003e\u003csummary\u003ePatch workaround (currently unnecessary)\u003c/summary\u003e\n\nThis is annoying, as it means the Chrome Dev tools don't include the final `.content` when doing \"Copy Object\", *but* there is a workaround.\nThere's a `fix-chrome-private-field.applescript` script which you can run with `osascript fix-chrome-private-field.applescript [number of items]`.\n\nThe script essentially just types out `followers[n].content.content = followers[n].content.#e` incrementally and increases `n`. You can't do this in a for loop in Chrome for some reason, as it will give you a private field access error.\n\nIf you'd like a Linux version of the script, or know how to get around Chrome not allowing accessing private fields in a for loop, feel free to open an issue or message me (my contact info is listed on [my profile](https://github.com/5HT2)).\n\n\u003c/details\u003e\n\n---\n\n## Disclaimer(s)\n\n1. I have no idea if Twitter will care if you scroll down to the bottom of the followers tab, I'd advise you to tread with caution though.\n2. This code was incredibly hastily written, it's kind of awful, easy to improve but it works, I don't care enough to bother. It does what I need it to.\n3. This might break if the response format changes. Unlikely, but feel free to make a Github issue about it.\n4. This data appears to be kind of cached on the server, if someone changes their username the old one is still sent in a response (multiple weeks after the username change). There's no error checking for empty profiles either.\nFor the most part, it's functional, with the rare 1 or 2 duplicate accounts, the follower counts might also be a lil outdated, this is just Twitter's caching / optimization stuff on their end.\n5. Idk this is really janky, if it breaks or there's something you want me to fix, feel free to ask lol\n\n[^1]: https://stackoverflow.com/a/57782978 (Chrome JS code \u003e= 111 \u003c= 117)\n[^2]: https://stackoverflow.com/a/78999261 (Chrome JS code \u003e= 118)\n[^3]: [Chromium `third_party/devtools-frontend` API change for `requestData()`](https://source.chromium.org/chromium/_/chromium/devtools/devtools-frontend/+/0d87db62760f96b8efe54bb6a97c3450b372bb74:front_end/core/sdk/NetworkRequest.ts;dlc=37ae2d25449c0b76cb5559f60460707fee5e01b5)\n  ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F5ht2%2Ftwitter-follower-rank","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F5ht2%2Ftwitter-follower-rank","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F5ht2%2Ftwitter-follower-rank/lists"}