From 13c1fcad7a3373f4386e62800c51af20125e3053 Mon Sep 17 00:00:00 2001 From: saksham2001 Date: Sat, 11 Jul 2026 03:52:13 +0000 Subject: [PATCH 1/2] Add automated contributor recognition in README and docs --- .github/workflows/contributors.yml | 36 +++++++ README.md | 22 +++- docs/project/contributors.md | 26 +++++ mkdocs.yml | 1 + scripts/update_contributors.py | 158 +++++++++++++++++++++++++++++ 5 files changed, 240 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/contributors.yml create mode 100644 docs/project/contributors.md create mode 100644 scripts/update_contributors.py diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml new file mode 100644 index 0000000..aa7536c --- /dev/null +++ b/.github/workflows/contributors.yml @@ -0,0 +1,36 @@ +name: Contributors + +on: + workflow_dispatch: + schedule: + - cron: "0 6 * * 1" # Mondays 06:00 UTC + +permissions: + contents: write + +jobs: + update: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Regenerate contributor grids + run: python scripts/update_contributors.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Commit if changed + run: | + if [ -n "$(git status --porcelain)" ]; then + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add README.md docs/project/contributors.md + git commit -m "chore: refresh contributors" + git push + else + echo "No contributor changes." + fi diff --git a/README.md b/README.md index bb890c0..e9c15d5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ Forks Issues PRs - CI Platform Swift @@ -194,8 +193,25 @@ place to start — ping us in Discord and we'll point you at the right files. - [@foureight84](https://github.com/foureight84) for the [Android port](https://github.com/foureight84/PulseLoopAndroid). -- Everyone who's filed an issue, opened a PR, or reported a device — see the - [contributors graph](https://github.com/saksham2001/PulseLoopiOS/graphs/contributors). + +### Contributors + +Thanks to everyone who's contributed to the iOS app 💜 + + + + + + + + + + +
@saksham2001
@saksham2001
@rgvxsthi
@rgvxsthi
@hoveeman
@hoveeman
@radxp
@radxp
+ + +Everyone who's filed an issue, opened a PR, or reported a device shows up in the +[contributors graph](https://github.com/saksham2001/PulseLoopiOS/graphs/contributors). ## License diff --git a/docs/project/contributors.md b/docs/project/contributors.md new file mode 100644 index 0000000..ac3fe2e --- /dev/null +++ b/docs/project/contributors.md @@ -0,0 +1,26 @@ +--- +title: Contributors +description: The people building PulseLoop on iOS and Android. +--- + +# Contributors + +PulseLoop is built by a community across **iOS** and **Android**. This page is +generated automatically from both repositories. + + + + + + + + + + + + +
@saksham2001
@saksham2001
@foureight84
@foureight84
@rgvxsthi
@rgvxsthi
@hoveeman
@hoveeman
@radxp
@radxp
@robisaks
@robisaks
+ + +!!! tip "Want to join them?" + See the [Contributing guide](contributing.md). diff --git a/mkdocs.yml b/mkdocs.yml index 24b4f05..3b0cba7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -120,4 +120,5 @@ nav: - Roadmap: project/roadmap.md - Architecture: project/architecture.md - Contributing: project/contributing.md + - Contributors: project/contributors.md - Privacy: project/privacy.md diff --git a/scripts/update_contributors.py b/scripts/update_contributors.py new file mode 100644 index 0000000..d79e166 --- /dev/null +++ b/scripts/update_contributors.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +"""Generate contributor avatar grids from the GitHub API. + +Fetches contributors for the iOS repo (and, for the docs page, the Android repo +too), renders an HTML avatar grid, and injects it between marker comments in +README.md and docs/project/contributors.md. + +Stdlib only — no third-party dependencies. Set GITHUB_TOKEN to raise the API +rate limit (reading public-repo contributors works without it). + +Usage: + python scripts/update_contributors.py +""" + +import json +import os +import re +import sys +import urllib.error +import urllib.request + +# Repos to pull from. +IOS_REPO = ("saksham2001", "PulseLoopiOS") +ANDROID_REPO = ("foureight84", "PulseLoopAndroid") + +# Paths, relative to the repo root (this script lives in scripts/). +REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +README_PATH = os.path.join(REPO_ROOT, "README.md") +DOCS_PATH = os.path.join(REPO_ROOT, "docs", "project", "contributors.md") + +# Bots to exclude, in addition to accounts whose login ends with "[bot]". +BOT_LOGINS = {"dependabot", "dependabot[bot]", "github-actions", "github-actions[bot]"} + +COLUMNS = 6 +AVATAR_SIZE = 80 # rendered px +MARKER_START = "" +MARKER_END = "" +GENERATED_NOTE = "" + + +def _is_bot(contributor): + login = contributor.get("login", "") + return ( + contributor.get("type") != "User" + or login in BOT_LOGINS + or login.endswith("[bot]") + ) + + +def fetch_contributors(owner, repo): + """Return a list of human contributors for owner/repo, sorted by contributions.""" + contributors = [] + page = 1 + token = os.environ.get("GITHUB_TOKEN") + while True: + url = ( + f"https://api.github.com/repos/{owner}/{repo}/contributors" + f"?per_page=100&anon=0&page={page}" + ) + req = urllib.request.Request(url) + req.add_header("Accept", "application/vnd.github+json") + req.add_header("X-GitHub-Api-Version", "2022-11-28") + req.add_header("User-Agent", "pulseloop-contributors-script") + if token: + req.add_header("Authorization", f"Bearer {token}") + try: + with urllib.request.urlopen(req) as resp: + batch = json.loads(resp.read().decode("utf-8")) + except urllib.error.HTTPError as err: + print(f"error: GitHub API {err.code} for {owner}/{repo}: {err.reason}", file=sys.stderr) + raise + if not batch: + break + contributors.extend(batch) + if len(batch) < 100: + break + page += 1 + + humans = [c for c in contributors if not _is_bot(c)] + humans.sort(key=lambda c: c.get("contributions", 0), reverse=True) + return humans + + +def merge_dedup(*lists): + """Merge contributor lists, keyed by lowercased login, summing contributions.""" + merged = {} + for contributors in lists: + for c in contributors: + key = c.get("login", "").lower() + if not key: + continue + if key in merged: + merged[key]["contributions"] += c.get("contributions", 0) + else: + merged[key] = dict(c) + return sorted(merged.values(), key=lambda c: c.get("contributions", 0), reverse=True) + + +def render_grid(contributors, columns=COLUMNS): + """Render contributors as an HTML table of avatar + username cells.""" + if not contributors: + return "_No contributors found._" + + rows = [] + for i in range(0, len(contributors), columns): + cells = [] + for c in contributors[i : i + columns]: + login = c["login"] + avatar = f"{c['avatar_url']}&s=100" + cell = ( + f'' + f'
' + f"@{login}
" + ) + cells.append(cell) + rows.append(" \n " + "\n ".join(cells) + "\n ") + + return "\n" + "\n".join(rows) + "\n
" + + +def inject(path, grid): + """Replace the content between the marker comments in `path`. Returns True if changed.""" + with open(path, "r", encoding="utf-8") as fh: + original = fh.read() + + block = f"{MARKER_START}\n{GENERATED_NOTE}\n{grid}\n{MARKER_END}" + pattern = re.compile( + re.escape(MARKER_START) + r".*?" + re.escape(MARKER_END), + re.DOTALL, + ) + if not pattern.search(original): + print(f"error: markers not found in {path}", file=sys.stderr) + raise SystemExit(1) + + updated = pattern.sub(lambda _: block, original) + if updated == original: + print(f"unchanged: {path}") + return False + + with open(path, "w", encoding="utf-8") as fh: + fh.write(updated) + print(f"updated: {path}") + return True + + +def main(): + ios = fetch_contributors(*IOS_REPO) + android = fetch_contributors(*ANDROID_REPO) + + print(f"iOS contributors: {len(ios)}; Android contributors: {len(android)}") + + inject(README_PATH, render_grid(ios)) + inject(DOCS_PATH, render_grid(merge_dedup(ios, android))) + + +if __name__ == "__main__": + main() From 0b9c05c56bc2db4dd4cb24661172c2ae6d51cd08 Mon Sep 17 00:00:00 2001 From: saksham2001 Date: Sat, 11 Jul 2026 04:03:58 +0000 Subject: [PATCH 2/2] Remove Android port acknowledgement from iOS README --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index e9c15d5..7de2137 100644 --- a/README.md +++ b/README.md @@ -191,9 +191,6 @@ place to start — ping us in Discord and we'll point you at the right files. ## Acknowledgements -- [@foureight84](https://github.com/foureight84) for the - [Android port](https://github.com/foureight84/PulseLoopAndroid). - ### Contributors Thanks to everyone who's contributed to the iOS app 💜