{"id":29427494,"url":"https://github.com/volkansah/github-stats-auto-update","last_synced_at":"2026-04-10T16:34:19.279Z","repository":{"id":302779299,"uuid":"1013596263","full_name":"VolkanSah/GitHub-Stats-Auto-Update","owner":"VolkanSah","description":"Automatically update your GitHub stats in the README.md using GitHub Actions.","archived":false,"fork":false,"pushed_at":"2025-07-04T07:36:21.000Z","size":28,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-11T08:06:15.584Z","etag":null,"topics":["actions","follow","github","github-actions","github-stars","github-stats","repository","stats","toolset","workflows"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/VolkanSah.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","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":"2025-07-04T06:48:39.000Z","updated_at":"2025-07-04T07:36:24.000Z","dependencies_parsed_at":"2025-07-04T07:47:07.942Z","dependency_job_id":null,"html_url":"https://github.com/VolkanSah/GitHub-Stats-Auto-Update","commit_stats":null,"previous_names":["volkansah/github-stats-auto-update"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/VolkanSah/GitHub-Stats-Auto-Update","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VolkanSah%2FGitHub-Stats-Auto-Update","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VolkanSah%2FGitHub-Stats-Auto-Update/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VolkanSah%2FGitHub-Stats-Auto-Update/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VolkanSah%2FGitHub-Stats-Auto-Update/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/VolkanSah","download_url":"https://codeload.github.com/VolkanSah/GitHub-Stats-Auto-Update/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VolkanSah%2FGitHub-Stats-Auto-Update/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264995709,"owners_count":23695015,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["actions","follow","github","github-actions","github-stars","github-stats","repository","stats","toolset","workflows"],"created_at":"2025-07-12T13:08:58.846Z","updated_at":"2026-04-10T16:34:19.258Z","avatar_url":"https://github.com/VolkanSah.png","language":"Python","readme":"# 📊 GitHub Stats Auto-Update\n\nAutomatically update your GitHub stats in the README.md using GitHub Actions. [Demo](https://github.com/VolkanSah/)\n\n##  What does the script do?\n\nThe script displays **only public** GitHub stats with detailed breakdown:\n\n* Public repositories (active + archived)\n* Total stars separated by:\n  - ⭐ Active repository stars\n  - 💎 Archived repository stars\n  - 🌟 Total stars per category\n* Separate stats for own repos vs. forked repos\n* 🎯 Grand total of all stars\n\n\u003e **Note:** Private repositories and their stars are not counted. The script uses only publicly available API data via GraphQL for accurate pagination.\n\n##  Features\n\n- ✅ **Complete pagination** - fetches ALL repos (not just first 100)\n- ✅ **Archive-aware** - separates active from archived repository stars\n- ✅ **Fork separation** - distinguishes between own and forked repos\n- ✅ **Detailed breakdown** - shows active, archived, and total stats\n- ✅ **Top 10 list** - displays your most starred repositories\n- ✅ **Full repo list** - complete overview of all repos with stars\n\n## Setup\n\n### 1. Create the files\n\nCreate these two files in your profile repository:\n\n**`.github/workflows/update-stats.yml`**\n\n```yaml\nname: Update Stats\n\non:\n  schedule:\n    - cron: '0 6 * * *'  # Every day at 6 AM\n  workflow_dispatch:  # Manual trigger option\n\njobs:\n  update-stats:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write  # Required for push\n    steps:\n      - name: Checkout Repo\n        uses: actions/checkout@v4\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n      \n      - name: Setup Python\n        uses: actions/setup-python@v4\n        with:\n          python-version: '3.x'\n      \n      - name: Install Dependencies\n        run: pip install requests\n      \n      - name: Run Stats Script\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: python update_stats.py\n      \n      - name: Commit \u0026 Push\n        run: |\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n          git add README.md\n          git commit -m \"🤖 Automatic GitHub Stats Update\" || exit 0\n          git push\n```\n\n**`update_stats.py`**\n\n```python\nimport requests\nimport os\nimport re\n\n# GitHub Username - ENTER YOUR USERNAME HERE!\nUSERNAME = \"YOUR_USERNAME_HERE\"\n\nTOKEN = os.getenv(\"GITHUB_TOKEN\")\nif not TOKEN:\n    print(\"❌ GITHUB_TOKEN not found!\")\n    exit(1)\n\nHEADERS = {\"Authorization\": f\"Bearer {TOKEN}\"}\n\ndef fetch_all_repos(is_fork):\n    \"\"\"Fetch ALL repos with pagination + extended info\"\"\"\n    all_repos = []\n    has_next = True\n    cursor = None\n    \n    while has_next:\n        query = \"\"\"\n        {\n          user(login: \"%s\") {\n            repositories(first: 100, privacy: PUBLIC, isFork: %s, ownerAffiliations: OWNER%s) {\n              nodes {\n                name\n                stargazerCount\n                isArchived\n                isDisabled\n                isLocked\n                owner {\n                  login\n                }\n              }\n              pageInfo {\n                hasNextPage\n                endCursor\n              }\n            }\n          }\n        }\n        \"\"\" % (USERNAME, str(is_fork).lower(), f', after: \"{cursor}\"' if cursor else '')\n        \n        try:\n            response = requests.post(\n                \"https://api.github.com/graphql\",\n                json={\"query\": query},\n                headers=HEADERS\n            )\n            response.raise_for_status()\n            data = response.json()\n            \n            if \"errors\" in data:\n                print(f\"❌ API Error: {data['errors']}\")\n                exit(1)\n            \n            repos = data[\"data\"][\"user\"][\"repositories\"]\n            all_repos.extend(repos[\"nodes\"])\n            \n            page_info = repos[\"pageInfo\"]\n            has_next = page_info[\"hasNextPage\"]\n            cursor = page_info[\"endCursor\"]\n            \n            print(f\"  📦 Fetched {len(repos['nodes'])} repos (Total: {len(all_repos)})\")\n            \n        except requests.exceptions.RequestException as e:\n            print(f\"❌ API Error: {e}\")\n            exit(1)\n    \n    return all_repos\n\ndef calculate_stats(repos, repo_type):\n    \"\"\"Calculate stats with filtering\"\"\"\n    # Filter: Only active, non-archived repos\n    active_repos = [\n        r for r in repos \n        if not r.get(\"isArchived\", False) \n        and not r.get(\"isDisabled\", False)\n        and not r.get(\"isLocked\", False)\n        and r.get(\"owner\", {}).get(\"login\") == USERNAME\n    ]\n    \n    # Archived repos separately\n    archived_repos = [\n        r for r in repos \n        if (r.get(\"isArchived\", False) or r.get(\"isDisabled\", False) or r.get(\"isLocked\", False))\n        and r.get(\"owner\", {}).get(\"login\") == USERNAME\n    ]\n    \n    archived_count = len(archived_repos)\n    archived_stars = sum(repo.get(\"stargazerCount\", 0) for repo in archived_repos)\n    \n    active_stars = sum(repo.get(\"stargazerCount\", 0) for repo in active_repos)\n    active_count = len(active_repos)\n    \n    print(f\"\\n📊 {repo_type.capitalize()} Repositories:\")\n    print(f\"  ✅ Active: {active_count}\")\n    if archived_count \u003e 0:\n        print(f\"  🗄️  Archived/Disabled: {archived_count} (with {archived_stars} ⭐)\")\n    print(f\"⭐ {repo_type.capitalize()} Stars:\")\n    print(f\"  Active: {active_stars}\")\n    if archived_stars \u003e 0:\n        print(f\"  Archive: {archived_stars} 💎\")\n    print(f\"  Total: {active_stars + archived_stars}\")\n    \n    # Top 10 repos with most stars\n    print(f\"\\n🏆 Top 10 {repo_type} Repos:\")\n    top_repos = sorted(active_repos, key=lambda x: x.get(\"stargazerCount\", 0), reverse=True)[:10]\n    for i, repo in enumerate(top_repos, 1):\n        print(f\"  {i:2}. {repo['name']:40} {repo.get('stargazerCount', 0):4} ⭐\")\n    \n    return active_count, active_stars, archived_count, archived_stars\n\ndef update_readme(own_repos, own_stars, own_archived_stars, forked_repos, forked_stars, forked_archived_stars):\n    \"\"\"Update the README\"\"\"\n    stats_md = f\"\"\"\u003c!-- STATS-START --\u003e\n## 📊 GitHub Stats\n- **Own Public Repositories:** {own_repos}\n  - ⭐ Active Stars: {own_stars}\n  - 💎 Archived Stars: {own_archived_stars}\n  - 🌟 Total Own Stars: {own_stars + own_archived_stars}\n- **Forked Public Repositories:** {forked_repos}\n  - ⭐ Active Stars: {forked_stars}\n  - 💎 Archived Stars: {forked_archived_stars}\n  - 🌟 Total Fork Stars: {forked_stars + forked_archived_stars}\n- **🎯 Grand Total Stars:** {own_stars + own_archived_stars + forked_stars + forked_archived_stars}\n\n*Last updated automatically via GitHub Actions.*\n\u003c!-- STATS-END --\u003e\"\"\"\n    \n    try:\n        with open(\"README.md\", \"r\", encoding=\"utf-8\") as f:\n            readme_content = f.read()\n    except FileNotFoundError:\n        print(\"❌ README.md not found!\")\n        exit(1)\n    \n    pattern = r\"\u003c!-- STATS-START --\u003e.*?\u003c!-- STATS-END --\u003e\"\n    if re.search(pattern, readme_content, re.DOTALL):\n        new_readme = re.sub(pattern, stats_md, readme_content, flags=re.DOTALL)\n        print(\"\\n✅ Stats section updated.\")\n    else:\n        new_readme = readme_content.strip() + \"\\n\\n\" + stats_md\n        print(\"\\n✅ Stats section added.\")\n    \n    with open(\"README.md\", \"w\", encoding=\"utf-8\") as f:\n        f.write(new_readme)\n    \n    print(\"🎉 Done!\")\n\nif __name__ == \"__main__\":\n    print(\"🔍 Fetching own repositories...\")\n    own_repos_data = fetch_all_repos(False)\n    own_repos, own_stars, own_archived, own_archived_stars = calculate_stats(own_repos_data, \"own\")\n    \n    print(\"\\n\" + \"=\"*80)\n    print(\"🔍 Fetching forked repositories...\")\n    forked_repos_data = fetch_all_repos(True)\n    forked_repos, forked_stars, forked_archived, forked_archived_stars = calculate_stats(forked_repos_data, \"forked\")\n    \n    print(\"\\n\" + \"=\"*80)\n    print(f\"📈 TOTAL:\")\n    print(f\"  Active Repos: {own_repos + forked_repos}\")\n    print(f\"  Archived Repos: {own_archived + forked_archived}\")\n    print(f\"  ⭐ Active Stars: {own_stars + forked_stars}\")\n    print(f\"  💎 Archive Stars: {own_archived_stars + forked_archived_stars}\")\n    print(f\"  🌟 GRAND TOTAL: {own_stars + own_archived_stars + forked_stars + forked_archived_stars} ⭐\")\n    \n    update_readme(own_repos, own_stars, own_archived_stars, forked_repos, forked_stars, forked_archived_stars)\n```\n\n### 2. Prepare your README.md\n\nAdd these markers in your README.md where the stats should appear:\n\n```markdown\n\u003c!-- STATS-START --\u003e\n\u003c!-- STATS-END --\u003e\n```\n\n### 3. Adjust your username\n\n**Important:** Change the following line in `update_stats.py`:\n\n```python\nUSERNAME = \"YOUR_USERNAME_HERE\"\n```\n\n### 4. Test the Action\n\n* Go to your repository\n* Click \"Actions\"\n* Select \"Update Stats\"\n* Click \"Run workflow\"\n\n## 📊 Stats Breakdown\n\nThe script shows:\n\n### Own Repositories\n- **Active Stars**: Stars from currently maintained projects\n- **Archived Stars**: Stars from archived/legacy projects\n- **Total Own Stars**: Complete star history\n\n### Forked Repositories  \n- Same breakdown for your forked repositories\n\n### Grand Total\n- Your complete GitHub star collection across all public repositories\n\n## ⚙️ Configuration\n\n### Change the schedule\n\n```yaml\n- cron: '0 6 * * *'  # Daily at 6 AM\n- cron: '0 12 * * 1'  # Mondays at 12 PM  \n- cron: '0 0 1 * *'   # First day of each month\n```\n\n### Manual trigger\n\nIn the repository under \"Actions\" → \"Update Stats\" → \"Run workflow\"\n\n## 🔒 Security\n\n* Uses the default `GITHUB_TOKEN` (no extra secrets needed)\n* Displays only public data\n* No private repository information\n* Uses GraphQL API for efficient data fetching\n\n## 📝 Notes\n\n* **Public stats only:** Private repos are not included\n* **Complete pagination:** Fetches ALL repositories (no 100-repo limit)\n* **Archive-aware:** Distinguishes between active and archived projects\n* **Error handling:** Aborts on errors, no broken updates\n* **Top 10 list:** Shows your most popular repositories in console output\n\n##  Customization\n\nYou can adjust the Markdown output in the `stats_md` variable in the `update_readme()` function.\n\nExample for a compact single-line format:\n\n```python\nstats_md = f\"\"\"\u003c!-- STATS-START --\u003e\n**📁 Repos:** {own_repos} | **⭐ Active Stars:** {own_stars} | **💎 Archive Stars:** {own_archived_stars} | **🎯 Total:** {own_stars + own_archived_stars + forked_stars + forked_archived_stars}\n\u003c!-- STATS-END --\u003e\"\"\"\n```\n\n## 🛠️ Troubleshooting\n\n**Action fails:**\n* Check if your username is correct\n* Look at the Action logs for details\n* Verify `GITHUB_TOKEN` permissions\n\n**Stats don't show:**\n* Make sure `\u003c!-- STATS-START --\u003e` and `\u003c!-- STATS-END --\u003e` are in your README.md\n* Check that markers are on separate lines\n\n**Numbers seem wrong:**\n* The script only counts public repository stats\n* Private repos are not included\n* Check console output for detailed breakdown\n\n**GraphQL API errors:**\n* GitHub API has rate limits\n* Daily updates should work fine\n* Manual runs might hit limits if run too frequently\n\n##  Why GraphQL?\n\nThis script uses GitHub's GraphQL API instead of REST because:\n- ✅ **Complete pagination** - no 100-repo limit issues\n- ✅ **Single request** per page - more efficient\n- ✅ **Precise filtering** - `ownerAffiliations: OWNER` ensures only your repos\n- ✅ **Archive detection** - knows which repos are archived\n- ✅ **Better rate limits** - GraphQL is more efficient\n\n---\n\n**Enjoy your automatic GitHub stats with full transparency! 🎉**\n\n### Copyright\nVolkan Kücükbudak\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvolkansah%2Fgithub-stats-auto-update","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvolkansah%2Fgithub-stats-auto-update","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvolkansah%2Fgithub-stats-auto-update/lists"}