Created
October 21, 2025 12:33
-
-
Save istiyakamin/5439de6c6bc858cbd3a3493d56ad455b to your computer and use it in GitHub Desktop.
GitHub Contributor Auto-Follow Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| import os, sys, time, argparse, requests | |
| from urllib.parse import urlparse, parse_qs | |
| API = "https://api.github.com" | |
| def gh_headers(token): | |
| return { | |
| "Authorization": f"Bearer {token}", | |
| "Accept": "application/vnd.github+json", | |
| "X-GitHub-Api-Version": "2022-11-28", | |
| "User-Agent": "follow-contributors-script" | |
| } | |
| def paged_get(url, headers, params=None): | |
| params = params or {} | |
| while url: | |
| r = requests.get(url, headers=headers, params=params) | |
| if r.status_code == 403 and r.headers.get("X-RateLimit-Remaining") == "0": | |
| reset = int(r.headers.get("X-RateLimit-Reset", "0")) | |
| sleep_for = max(0, reset - int(time.time()) + 2) | |
| print(f"[rate-limit] sleeping {sleep_for}s until reset…", flush=True) | |
| time.sleep(sleep_for) | |
| continue | |
| r.raise_for_status() | |
| yield r.json() | |
| # pagination | |
| link = r.headers.get("Link", "") | |
| next_url = None | |
| for part in link.split(","): | |
| if 'rel="next"' in part: | |
| next_url = part[part.find("<")+1:part.find(">")] | |
| break | |
| url, params = next_url, None # after first page, GitHub encodes params in the URL | |
| def list_contributors(owner, repo, headers): | |
| url = f"{API}/repos/{owner}/{repo}/contributors" | |
| for page in paged_get(url, headers, params={"per_page": 100, "anon": "false"}): | |
| for c in page: | |
| yield c | |
| def list_i_follow(headers): | |
| url = f"{API}/user/following" | |
| following = set() | |
| for page in paged_get(url, headers, params={"per_page": 100}): | |
| for u in page: | |
| following.add(u["login"]) | |
| return following | |
| def is_bot(login, user_type): | |
| return user_type == "Bot" or login.endswith("[bot]") or login.lower().endswith("-bot") | |
| def follow_user(login, headers, dry_run=False): | |
| if dry_run: | |
| return True, "dry-run" | |
| r = requests.put(f"{API}/user/following/{login}", headers=headers) | |
| if r.status_code in (204, 304): | |
| return True, r.status_code | |
| return False, f"{r.status_code}: {r.text}" | |
| def main(): | |
| ap = argparse.ArgumentParser(description="Follow all contributors of a GitHub repo (safely).") | |
| ap.add_argument("--repo", required=True, help="Format: owner/repo") | |
| ap.add_argument("--min-commits", type=int, default=1, help="Only follow contributors with >= this many commits") | |
| ap.add_argument("--include-bots", action="store_true", help="Include bot accounts (default: skip)") | |
| ap.add_argument("--limit", type=int, default=50, help="Max follows to perform this run") | |
| ap.add_argument("--dry-run", action="store_true", help="Print who would be followed without doing it") | |
| args = ap.parse_args() | |
| token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | |
| if not token: | |
| print("ERROR: set GITHUB_TOKEN with scope user:follow", file=sys.stderr) | |
| sys.exit(1) | |
| headers = gh_headers(token) | |
| try: | |
| owner, repo = args.repo.split("/", 1) | |
| except ValueError: | |
| print("ERROR: --repo must be like owner/repo", file=sys.stderr) | |
| sys.exit(1) | |
| print(f"[info] fetching contributors for {owner}/{repo}…") | |
| existing = list_i_follow(headers) | |
| to_follow = [] | |
| for c in list_contributors(owner, repo, headers): | |
| login = c.get("login") | |
| if not login: | |
| continue | |
| if not args.include_bots and is_bot(login, c.get("type", "")): | |
| continue | |
| if c.get("contributions", 0) < args.min_commits: | |
| continue | |
| if login in existing: | |
| continue | |
| to_follow.append((login, c.get("contributions", 0))) | |
| # Sort by contributions (desc) to prioritize meaningful accounts | |
| to_follow.sort(key=lambda x: x[1], reverse=True) | |
| if args.limit: | |
| to_follow = to_follow[: args.limit] | |
| print(f"[plan] {len(to_follow)} accounts to follow (limit applied).") | |
| success, fail = 0, 0 | |
| for i, (login, contribs) in enumerate(to_follow, 1): | |
| ok, detail = follow_user(login, headers, args.dry_run) | |
| status = "OK" if ok else "FAIL" | |
| print(f"[{i}/{len(to_follow)}] {status} follow @{login} (commits={contribs}) -> {detail}") | |
| if ok: success += 1 | |
| else: fail += 1 | |
| # Gentle pacing to avoid secondary abuse detection | |
| time.sleep(0.4 if not args.dry_run else 0) | |
| print(f"[done] success={success} fail={fail} dry_run={args.dry_run}") | |
| if __name__ == "__main__": | |
| main() |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
GitHub Contributor Auto-Follow Script
Description
A smart automation script that lets you instantly follow all contributors of any GitHub repository — safely, efficiently, and within API rate limits.
Designed for developers, recruiters, and open-source enthusiasts who want to connect with active contributors in their field.
🚀 Key Features
owner/repousing the GitHub API.Use Cases
Example Command