Skip to content

Instantly share code, notes, and snippets.

@istiyakamin
Created October 21, 2025 12:33
Show Gist options
  • Select an option

  • Save istiyakamin/5439de6c6bc858cbd3a3493d56ad455b to your computer and use it in GitHub Desktop.

Select an option

Save istiyakamin/5439de6c6bc858cbd3a3493d56ad455b to your computer and use it in GitHub Desktop.
GitHub Contributor Auto-Follow Script
#!/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()
@istiyakamin
Copy link
Author

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

  • Contributor Discovery: Fetches all human contributors of any owner/repo using the GitHub API.
  • Bot Filtering: Automatically skips bots and organization service accounts.
  • Custom Filters: Set minimum commit thresholds, daily follow caps, or dry-run previews.
  • Rate Limit Management: Smart sleep logic to comply with GitHub’s API limits.
  • CLI Ready: Simple one-line command — just provide your repository name and token.
  • Secure & Private: Works with your own personal access token; no external dependencies.

Use Cases

  • Build your professional GitHub network based on meaningful contribution activity.
  • Discover potential collaborators or hire open-source developers.
  • Automate community engagement for your organization’s repositories.

Example Command

python follow_contributors.py --repo vercel/next.js --limit 25 --min-commits 3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment