[READ-ONLY] Mirror of https://github.com/jmrplens/jmrplens. Profile jmrp.io/
0

Configure Feed

Select the types of activity you want to include in your feed.

๐Ÿš€ Implement dynamic GitHub statistics

- Create Python script to fetch real GitHub API data
- Generate custom SVG with:
- Total repos, stars, commits (estimate), and lines of code
- Top 6 languages with visual bars and percentages
- Dark/light mode support
- Add GitHub workflow for daily automatic updates
- Add Matrix Synapse and Meshtastic to tech stack
- Remove Support My Work section
- Redesign Connect With Me buttons (flat-square style)
- Replace static language icons with dynamic stats

Josรฉ M. Requena Plens (Dec 28, 2025, 3:02 AM +0100) 74cbfb95 b14ce8a0

+366 -23
+57
.github/workflows/generate-stats.yml
··· 1 + name: Generate GitHub Stats 2 + 3 + on: 4 + push: 5 + branches: [ main ] 6 + schedule: 7 + # Run daily at 00:00 UTC 8 + - cron: '0 0 * * *' 9 + workflow_dispatch: 10 + 11 + permissions: 12 + contents: write 13 + 14 + jobs: 15 + generate-stats: 16 + runs-on: ubuntu-latest 17 + 18 + steps: 19 + - name: Checkout repository 20 + uses: actions/checkout@v4 21 + 22 + - name: Set up Python 23 + uses: actions/setup-python@v5 24 + with: 25 + python-version: '3.11' 26 + cache: 'pip' 27 + 28 + - name: Install dependencies 29 + run: | 30 + python -m pip install --upgrade pip 31 + pip install -r requirements.txt 32 + 33 + - name: Generate stats SVG 34 + env: 35 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 + run: | 37 + python generate_stats.py 38 + 39 + - name: Commit and push if changed 40 + run: | 41 + git config --global user.name "stats-bot" 42 + git config --global user.email "stats-bot@jmrplens.github.io" 43 + git add generated/github-stats.svg 44 + git diff --quiet && git diff --staged --quiet || git commit -m "๐Ÿ“Š Update GitHub stats" 45 + git push 46 + 47 + # Mirror to Gitea/Forgejo 48 + - name: Checkout for mirroring 49 + uses: actions/checkout@v4 50 + with: 51 + fetch-depth: 0 52 + 53 + - name: Mirror to Gitea 54 + uses: pixta-dev/repository-mirroring-action@v1 55 + with: 56 + target_repo_url: ssh://git@git.jmrp.dev:${{ secrets.PORT }}/${{ github.repository }}.git 57 + ssh_private_key: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
+18 -23
README.md
··· 34 34 <p align="center"> 35 35 <a href="https://mikrotik.com/"><img src="https://cdn.simpleicons.org/mikrotik/293239" width="48" height="48" alt="MikroTik"/></a> 36 36 <a href="https://matrix.org/"><img src="https://skillicons.dev/icons?i=matrix" alt="Matrix"/></a> 37 + <a href="https://element-hq.github.io/synapse/"><img src="https://cdn.simpleicons.org/matrix/000000" width="48" height="48" alt="Matrix Synapse"/></a> 37 38 <a href="https://joinmastodon.org/"><img src="https://skillicons.dev/icons?i=mastodon" alt="Mastodon"/></a> 38 39 <a href="https://www.home-assistant.io/"><img src="https://skillicons.dev/icons?i=homeassistant" alt="Home Assistant"/></a> 40 + <a href="https://meshtastic.org/"><img src="https://cdn.simpleicons.org/meshtastic/67EA94" width="48" height="48" alt="Meshtastic"/></a> 39 41 </p> 40 42 41 43 ## ๐Ÿ“ Latest Blog Posts ··· 75 77 </a> 76 78 </p> 77 79 78 - ## ๐Ÿ’ป Most Used Languages 80 + ## ๐Ÿ“Š GitHub Statistics 79 81 80 82 <div align="center"> 81 - <img src="https://skillicons.dev/icons?i=cpp,c,python,matlab,js,bash&theme=dark" alt="Top Languages" /> 83 + <img src="generated/github-stats.svg" alt="GitHub Statistics" width="100%"/> 82 84 </div> 83 85 84 86 ## ๐Ÿ”— Connect With Me 85 87 86 - <div align="center"> 87 - 88 - [![Website](https://img.shields.io/badge/๐ŸŒ_Website-667eea?style=for-the-badge)](https://jmrp.io) 89 - [![LinkedIn](https://img.shields.io/badge/LinkedIn-0A66C2?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/jmrplens/) 90 - [![Mastodon](https://img.shields.io/badge/Mastodon-6364FF?style=for-the-badge&logo=mastodon&logoColor=white)](https://mstdn.jmrp.io/@jmrplens) 88 + <p align="center"> 89 + <a href="https://jmrp.io"> 90 + <img src="https://img.shields.io/badge/-Website-667eea?style=flat-square&logo=googlechrome&logoColor=white" alt="Website"/> 91 + </a> 92 + <a href="https://www.linkedin.com/in/jmrplens/"> 93 + <img src="https://img.shields.io/badge/-LinkedIn-0A66C2?style=flat-square&logo=linkedin&logoColor=white" alt="LinkedIn"/> 94 + </a> 95 + <a href="https://mstdn.jmrp.io/@jmrplens"> 96 + <img src="https://img.shields.io/badge/-Mastodon-6364FF?style=flat-square&logo=mastodon&logoColor=white" alt="Mastodon"/> 97 + </a> 98 + </p> 91 99 92 - </div> 93 - 94 - --- 95 - 96 - <div align="center"> 97 - 98 - ### ๐Ÿ’ Support My Work 99 - 100 - [![Ko-fi](https://img.shields.io/badge/Ko--fi-FF5E5B?style=for-the-badge&logo=ko-fi&logoColor=white)](https://ko-fi.com/X8X147IR3) 101 - [![Liberapay](https://img.shields.io/badge/Liberapay-F6C915?style=for-the-badge&logo=liberapay&logoColor=black)](https://liberapay.com/jmrplens/donate) 102 - 103 - </div> 104 - 105 - <div align="center"> 106 - <img src="https://komarev.com/ghpvc/?username=jmrplens&style=for-the-badge&color=667eea" alt="Profile Views"/> 107 - </div> 100 + <p align="center"> 101 + <img src="https://komarev.com/ghpvc/?username=jmrplens&style=flat-square&color=667eea" alt="Profile Views"/> 102 + </p>
+187
generate_stats.py
··· 1 + #!/usr/bin/env python3 2 + """ 3 + Generate GitHub statistics SVG 4 + Fetches real data from GitHub API and creates a custom SVG visualization 5 + """ 6 + 7 + import os 8 + import requests 9 + from datetime import datetime 10 + 11 + # GitHub API configuration 12 + GITHUB_TOKEN = os.getenv('GITHUB_TOKEN', '') 13 + USERNAME = 'jmrplens' 14 + HEADERS = {'Authorization': f'token {GITHUB_TOKEN}'} if GITHUB_TOKEN else {} 15 + 16 + def fetch_github_stats(): 17 + """Fetch comprehensive GitHub statistics""" 18 + 19 + # Get user data 20 + user_url = f'https://api.github.com/users/{USERNAME}' 21 + user_data = requests.get(user_url, headers=HEADERS).json() 22 + 23 + # Get all repos (including contributions) 24 + repos_url = f'https://api.github.com/users/{USERNAME}/repos?per_page=100&type=all' 25 + repos = requests.get(repos_url, headers=HEADERS).json() 26 + 27 + # Calculate statistics 28 + total_stars = sum(repo['stargazers_count'] for repo in repos if not repo['fork']) 29 + total_repos = len([repo for repo in repos if not repo['fork']]) 30 + 31 + # Get languages statistics 32 + languages_bytes = {} 33 + for repo in repos: 34 + if repo['fork']: 35 + continue 36 + lang_url = repo['languages_url'] 37 + lang_data = requests.get(lang_url, headers=HEADERS).json() 38 + for lang, bytes_count in lang_data.items(): 39 + languages_bytes[lang] = languages_bytes.get(lang, 0) + bytes_count 40 + 41 + # Sort and get top 6 languages 42 + sorted_langs = sorted(languages_bytes.items(), key=lambda x: x[1], reverse=True)[:6] 43 + total_bytes = sum(languages_bytes.values()) 44 + 45 + # Get contribution data (approximate from events) 46 + events_url = f'https://api.github.com/users/{USERNAME}/events/public?per_page=100' 47 + events = requests.get(events_url, headers=HEADERS).json() 48 + recent_commits = len([e for e in events if e['type'] == 'PushEvent']) 49 + 50 + return { 51 + 'total_stars': total_stars, 52 + 'total_repos': total_repos, 53 + 'public_repos': user_data.get('public_repos', 0), 54 + 'total_commits': recent_commits * 10, # Rough estimate 55 + 'languages': [(lang, bytes_count, (bytes_count / total_bytes * 100)) for lang, bytes_count in sorted_langs], 56 + 'total_lines': total_bytes // 50, # Rough estimate: 50 bytes per line average 57 + } 58 + 59 + # Language colors (official GitHub colors) 60 + LANG_COLORS = { 61 + 'C++': '#f34b7d', 62 + 'C': '#555555', 63 + 'Python': '#3572A5', 64 + 'MATLAB': '#e16737', 65 + 'JavaScript': '#f1e05a', 66 + 'Shell': '#89e051', 67 + 'TypeScript': '#2b7489', 68 + 'HTML': '#e34c26', 69 + 'CSS': '#563d7c', 70 + 'Makefile': '#427819', 71 + } 72 + 73 + def generate_svg(stats): 74 + """Generate SVG with GitHub statistics""" 75 + 76 + width = 800 77 + height = 400 78 + 79 + svg = f'''<svg width="{width}" height="{height}" viewBox="0 0 {width} {height}" xmlns="http://www.w3.org/2000/svg"> 80 + <defs> 81 + <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%"> 82 + <stop offset="0%" style="stop-color:#667eea;stop-opacity:1" /> 83 + <stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" /> 84 + </linearGradient> 85 + <style> 86 + .title {{ font: 600 18px 'Segoe UI', Ubuntu, sans-serif; }} 87 + .stat-label {{ font: 400 14px 'Segoe UI', Ubuntu, sans-serif; }} 88 + .stat-value {{ font: 700 24px 'Segoe UI', Ubuntu, sans-serif; }} 89 + .lang-name {{ font: 400 12px 'Segoe UI', Ubuntu, sans-serif; }} 90 + .lang-percent {{ font: 600 12px 'Segoe UI', Ubuntu, sans-serif; }} 91 + 92 + @media (prefers-color-scheme: light) {{ 93 + .bg {{ fill: #ffffff; stroke: #e1e4e8; }} 94 + .title {{ fill: #24292e; }} 95 + .stat-label {{ fill: #586069; }} 96 + .stat-value {{ fill: #24292e; }} 97 + .lang-name {{ fill: #586069; }} 98 + .lang-percent {{ fill: #24292e; }} 99 + .divider {{ stroke: #e1e4e8; }} 100 + }} 101 + 102 + @media (prefers-color-scheme: dark) {{ 103 + .bg {{ fill: #0d1117; stroke: #30363d; }} 104 + .title {{ fill: #c9d1d9; }} 105 + .stat-label {{ fill: #8b949e; }} 106 + .stat-value {{ fill: #c9d1d9; }} 107 + .lang-name {{ fill: #8b949e; }} 108 + .lang-percent {{ fill: #c9d1d9; }} 109 + .divider {{ stroke: #30363d; }} 110 + }} 111 + </style> 112 + </defs> 113 + 114 + <!-- Background --> 115 + <rect class="bg" width="{width}" height="{height}" rx="10" stroke-width="1" fill-opacity="0.8"/> 116 + 117 + <!-- Title --> 118 + <text class="title" x="25" y="35">๐Ÿ“Š GitHub Statistics</text> 119 + 120 + <!-- Stats Grid --> 121 + <g transform="translate(25, 60)"> 122 + <!-- Total Repos --> 123 + <text class="stat-label" x="0" y="0">Total Repositories</text> 124 + <text class="stat-value" x="0" y="28">{stats['total_repos']}</text> 125 + 126 + <!-- Total Stars --> 127 + <text class="stat-label" x="190" y="0">Total Stars</text> 128 + <text class="stat-value" x="190" y="28">{stats['total_stars']}</text> 129 + 130 + <!-- Total Commits (estimate) --> 131 + <text class="stat-label" x="380" y="0">Commits (est.)</text> 132 + <text class="stat-value" x="380" y="28">{stats['total_commits']:,}</text> 133 + 134 + <!-- Lines of Code (estimate) --> 135 + <text class="stat-label" x="570" y="0">Lines of Code (est.)</text> 136 + <text class="stat-value" x="570" y="28">{stats['total_lines']:,}</text> 137 + </g> 138 + 139 + <!-- Divider --> 140 + <line class="divider" x1="25" y1="130" x2="{width-25}" y2="130" stroke-width="1"/> 141 + 142 + <!-- Languages Title --> 143 + <text class="title" x="25" y="165">๐Ÿ’ป Most Used Languages</text> 144 + 145 + <!-- Languages List --> 146 + <g transform="translate(25, 180)"> 147 + ''' 148 + 149 + y_offset = 0 150 + for i, (lang, bytes_count, percent) in enumerate(stats['languages']): 151 + color = LANG_COLORS.get(lang, '#858585') 152 + bar_width = (percent / 100) * 550 153 + 154 + svg += f''' <!-- {lang} --> 155 + <circle cx="0" cy="{y_offset + 8}" r="5" fill="{color}"/> 156 + <text class="lang-name" x="15" y="{y_offset + 12}">{lang}</text> 157 + <rect x="120" y="{y_offset + 2}" width="{bar_width}" height="12" rx="6" fill="{color}" opacity="0.8"/> 158 + <text class="lang-percent" x="{120 + bar_width + 10}" y="{y_offset + 12}">{percent:.1f}%</text> 159 + ''' 160 + y_offset += 30 161 + 162 + svg += f''' </g> 163 + 164 + <!-- Footer --> 165 + <text class="stat-label" x="{width//2}" y="{height-15}" text-anchor="middle">Generated on {datetime.now().strftime('%Y-%m-%d %H:%M UTC')}</text> 166 + </svg>''' 167 + 168 + return svg 169 + 170 + if __name__ == '__main__': 171 + print("Fetching GitHub statistics...") 172 + stats = fetch_github_stats() 173 + 174 + print(f" - Repos: {stats['total_repos']}") 175 + print(f" - Stars: {stats['total_stars']}") 176 + print(f" - Top Languages: {[lang for lang, _, _ in stats['languages']]}") 177 + 178 + print("Generating SVG...") 179 + svg_content = generate_svg(stats) 180 + 181 + output_path = 'generated/github-stats.svg' 182 + os.makedirs('generated', exist_ok=True) 183 + 184 + with open(output_path, 'w') as f: 185 + f.write(svg_content) 186 + 187 + print(f"โœ… SVG generated: {output_path}")
+103
generated/github-stats.svg
··· 1 + <svg width="800" height="400" viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg"> 2 + <defs> 3 + <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%"> 4 + <stop offset="0%" style="stop-color:#667eea;stop-opacity:1" /> 5 + <stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" /> 6 + </linearGradient> 7 + <style> 8 + .title { font: 600 18px 'Segoe UI', Ubuntu, sans-serif; } 9 + .stat-label { font: 400 14px 'Segoe UI', Ubuntu, sans-serif; } 10 + .stat-value { font: 700 24px 'Segoe UI', Ubuntu, sans-serif; } 11 + .lang-name { font: 400 12px 'Segoe UI', Ubuntu, sans-serif; } 12 + .lang-percent { font: 600 12px 'Segoe UI', Ubuntu, sans-serif; } 13 + 14 + @media (prefers-color-scheme: light) { 15 + .bg { fill: #ffffff; stroke: #e1e4e8; } 16 + .title { fill: #24292e; } 17 + .stat-label { fill: #586069; } 18 + .stat-value { fill: #24292e; } 19 + .lang-name { fill: #586069; } 20 + .lang-percent { fill: #24292e; } 21 + .divider { stroke: #e1e4e8; } 22 + } 23 + 24 + @media (prefers-color-scheme: dark) { 25 + .bg { fill: #0d1117; stroke: #30363d; } 26 + .title { fill: #c9d1d9; } 27 + .stat-label { fill: #8b949e; } 28 + .stat-value { fill: #c9d1d9; } 29 + .lang-name { fill: #8b949e; } 30 + .lang-percent { fill: #c9d1d9; } 31 + .divider { stroke: #30363d; } 32 + } 33 + </style> 34 + </defs> 35 + 36 + <!-- Background --> 37 + <rect class="bg" width="800" height="400" rx="10" stroke-width="1" fill-opacity="0.8"/> 38 + 39 + <!-- Title --> 40 + <text class="title" x="25" y="35">๐Ÿ“Š GitHub Statistics</text> 41 + 42 + <!-- Stats Grid --> 43 + <g transform="translate(25, 60)"> 44 + <!-- Total Repos --> 45 + <text class="stat-label" x="0" y="0">Total Repositories</text> 46 + <text class="stat-value" x="0" y="28">21</text> 47 + 48 + <!-- Total Stars --> 49 + <text class="stat-label" x="190" y="0">Total Stars</text> 50 + <text class="stat-value" x="190" y="28">247</text> 51 + 52 + <!-- Total Commits (estimate) --> 53 + <text class="stat-label" x="380" y="0">Commits (est.)</text> 54 + <text class="stat-value" x="380" y="28">700</text> 55 + 56 + <!-- Lines of Code (estimate) --> 57 + <text class="stat-label" x="570" y="0">Lines of Code (est.)</text> 58 + <text class="stat-value" x="570" y="28">102,351</text> 59 + </g> 60 + 61 + <!-- Divider --> 62 + <line class="divider" x1="25" y1="130" x2="775" y2="130" stroke-width="1"/> 63 + 64 + <!-- Languages Title --> 65 + <text class="title" x="25" y="165">๐Ÿ’ป Most Used Languages</text> 66 + 67 + <!-- Languages List --> 68 + <g transform="translate(25, 180)"> 69 + <!-- MATLAB --> 70 + <circle cx="0" cy="8" r="5" fill="#e16737"/> 71 + <text class="lang-name" x="15" y="12">MATLAB</text> 72 + <rect x="120" y="2" width="245.8715210003754" height="12" rx="6" fill="#e16737" opacity="0.8"/> 73 + <text class="lang-percent" x="375.8715210003754" y="12">44.7%</text> 74 + <!-- C --> 75 + <circle cx="0" cy="38" r="5" fill="#555555"/> 76 + <text class="lang-name" x="15" y="42">C</text> 77 + <rect x="120" y="32" width="107.50999638697755" height="12" rx="6" fill="#555555" opacity="0.8"/> 78 + <text class="lang-percent" x="237.50999638697755" y="42">19.5%</text> 79 + <!-- HTML --> 80 + <circle cx="0" cy="68" r="5" fill="#e34c26"/> 81 + <text class="lang-name" x="15" y="72">HTML</text> 82 + <rect x="120" y="62" width="64.00801430514584" height="12" rx="6" fill="#e34c26" opacity="0.8"/> 83 + <text class="lang-percent" x="194.00801430514582" y="72">11.6%</text> 84 + <!-- Pascal --> 85 + <circle cx="0" cy="98" r="5" fill="#858585"/> 86 + <text class="lang-name" x="15" y="102">Pascal</text> 87 + <rect x="120" y="92" width="36.76228833091456" height="12" rx="6" fill="#858585" opacity="0.8"/> 88 + <text class="lang-percent" x="166.76228833091454" y="102">6.7%</text> 89 + <!-- TeX --> 90 + <circle cx="0" cy="128" r="5" fill="#858585"/> 91 + <text class="lang-name" x="15" y="132">TeX</text> 92 + <rect x="120" y="122" width="27.12524955550445" height="12" rx="6" fill="#858585" opacity="0.8"/> 93 + <text class="lang-percent" x="157.12524955550444" y="132">4.9%</text> 94 + <!-- Astro --> 95 + <circle cx="0" cy="158" r="5" fill="#858585"/> 96 + <text class="lang-name" x="15" y="162">Astro</text> 97 + <rect x="120" y="152" width="20.4532633369672" height="12" rx="6" fill="#858585" opacity="0.8"/> 98 + <text class="lang-percent" x="150.4532633369672" y="162">3.7%</text> 99 + </g> 100 + 101 + <!-- Footer --> 102 + <text class="stat-label" x="400" y="385" text-anchor="middle">Generated on 2025-12-28 03:01 UTC</text> 103 + </svg>
+1
requirements.txt
··· 1 + requests==2.31.0