GNOME Nepal - Top contributors calculator
This system automatically aggregates contributor data from all organization repositories using the GitHub API, updates a sorted JSON file every hour, and triggers a webhook whenever changes occur. It ensures contributor information remains accurate, version-controlled, and easily integrated into GNOME Website and Discord
One of the repetitive tasks in any open-source organization is keeping contributor information up to date. Contributors change, new people join, old ones go quiet — and keeping a static list is both tedious and error-prone.
To solve this, I built a small automation pipeline around GitHub Actions and the GitHub API. It does two things:
- Poll all repositories under the GNOME-Nepal organization every hour, update the contributor list, and commit changes to the repo.
- Broadcast changes through a webhook whenever
contributors.jsonis updated.
This post breaks down how the algorithm works and how the two workflows tie together.
The Python Script: Building the Contributor Index
The heart of the system is a Python script that queries GitHub’s REST API and aggregates contributor data across all repositories in the organization.
Step 1: Fetch All Repositories
repos_url = f"{BASE_URL}/orgs/{ORG_NAME}/repos"
repos_response = requests.get(repos_url, headers=headers)
This hits the
/orgs/:org/repos endpoint to list every repo in the organization.Step 2: Collect Contributor Counts
For each repository, the script calls the
/repos/:org/:repo/contributors endpoint, which returns contributors and their contribution counts.The script filters out non-user accounts (like bots or the
actions-user) and accumulates contributions into a dictionary keyed by username:if username not in contributor_contribution_dict:
contributor_contribution_dict[username] = 0
contributor_contribution_dict[username] += repo_contributor["contributions"]
Step 3: Enrich Contributor Profiles
Once raw counts are collected, the script calls
/users/:username to fetch profile details for each contributor. This way, the final dataset includes not only contribution numbers but also user metadata such as profile URLs, avatars, etc.user_information_url = f"{BASE_URL}/users/{username}"
response = requests.get(user_information_url, headers=headers)
Step 4: Sort and Persist
The final list is sorted by contributions (highest first) and dumped into
contributors.json:with open("contributors.json", "w") as c:
json.dump(full_information, c, indent=2, ensure_ascii=False)
At this point, the JSON file represents a live, ranked leaderboard of contributors across the entire org.

Workflow 1: Hourly Data Refresh
The first GitHub Actions workflow (
Run Python Script and Commit Changes) ensures contributors.json is always current.- It runs every hour via a cron trigger.
- Checks out the repository.
- Sets up Python and installs dependencies.
- Executes
script.pywith a GitHub token (MAINTAINERS_POLLING_TOKEN). - Commits and pushes the updated
contributors.jsonback tomainwith a timestamped commit message.
This effectively turns GitHub Actions into a scheduled ETL job — extract from GitHub’s API, transform into a sorted list, load into version control.
Workflow 2: Broadcasting Updates
The second workflow (
Update Contributors Webhook) activates whenever contributors.json changes.- Reads the updated JSON with
jq. - Iterates through each contributor, extracting their username and profile URL.
- Constructs an embed payload.
- Sends it via a PATCH request to a configured webhook URL.
This is how updates get pushed outward — whether to a Discord channel, Slack, or another external system.
Why This Works Well
- Automated polling ensures data freshness without manual intervention.
- Version-controlled JSON gives a full history of contributor changes over time.
- Event-driven webhooks allow the data to be propagated wherever needed.
- Simple and transparent — the whole system is just Python + YAML + GitHub Actions.
Closing Thoughts
This setup gives me an always-up-to-date snapshot of organizational contributors, enriched with profile data, and automatically propagated to wherever it needs to be consumed. It’s not complex infrastructure — just a few hundred lines of Python and two YAML workflows — but it reliably handles what used to be a manual and messy task.
The result: a continuously refreshed, versioned, and broadcast contributor index for the entire org.