diff --git a/.github/workflows/metrics.yml b/.github/workflows/metrics.yml index 15ff6da..776a8a6 100644 --- a/.github/workflows/metrics.yml +++ b/.github/workflows/metrics.yml @@ -1,67 +1,44 @@ -# Visit https://github.com/lowlighter/metrics/blob/master/action.yml for full reference -name: Metrics +name: Update README on: - schedule: [{cron: "0 4 * * *"}] - # Lines below let you run workflow manually and on each commit + schedule: [{ cron: '0 4,16 * * *' }] workflow_dispatch: jobs: - metrics: + update_readme: runs-on: ubuntu-latest - permissions: - contents: write + steps: - - - name: Left Panel - if: ${{ success() || failure() }} - uses: lowlighter/metrics@latest + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 with: - filename: left-panel.svg - token: ${{ secrets.METRICS_TOKEN }} - base: header, activity, community, repositories, metadata - config_order: base.header, base.repositories, base.activity+community, languages, followup - output_action: gist - committer_gist: ${{ secrets.GIST }} - template: classic - config_timezone: Europe/Berlin - plugin_followup: yes - plugin_followup_sections: user, repositories - plugin_languages: yes - plugin_languages_analysis_timeout: 15 - plugin_languages_categories: markup, programming - plugin_languages_colors: github - plugin_languages_limit: 8 - plugin_languages_recent_categories: markup, programming, data, prose - plugin_languages_recent_days: 14 - plugin_languages_recent_load: 300 - plugin_languages_sections: most-used - plugin_languages_threshold: 0% - - - name: Right Panel - if: ${{ success() || failure() }} - uses: lowlighter/metrics@latest + python-version: '3.12' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Create .env file + run: | + echo "GHOST_URL=${{ secrets.GHOST_URL }}" > .env + echo "GHOST_KEY=${{ secrets.GHOST_KEY }}" > .env + echo "WAKAPI_URL=${{ secrets.WAKAPI_URL }}" > .env + echo "WAKAPI_KEY=${{ secrets.WAKAPI_KEY }}" > .env + echo "DUOLINGO_URL=${{ secrets.DUOLINGO_URL }}" > .env + + - name: Run README updater + run: python readme_updater.py + + - name: Commit changes + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add README.md + git commit -m "Auto-update README [skip ci]" + + - name: Push changes + uses: ad-m/github-push-action@master with: - filename: right-panel.svg - token: ${{ secrets.METRICS_TOKEN }} - base: "" - config_order: wakatime, isocalendar, activity - output_action: gist - committer_gist: ${{ secrets.GIST }} - template: classic - config_timezone: Europe/Berlin - plugin_activity: yes - plugin_activity_days: 14 - plugin_activity_filter: all - plugin_activity_limit: 5 - plugin_activity_load: 300 - plugin_activity_visibility: all - plugin_activity_skipped: marvinscham/marvinscham - plugin_isocalendar: yes - plugin_isocalendar_duration: half-year - plugin_wakatime: yes - plugin_wakatime_token: ${{ secrets.WAKA_TOKEN }} - plugin_wakatime_days: "30" - plugin_wakatime_sections: time, projects-graphs, languages-graphs, editors - plugin_wakatime_user: marvinscham - plugin_wakatime_url: https://wakapi.ms-ds.org - - + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/README.md b/README.md index ead5ea7..f526bf4 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,106 @@
-

Hi there πŸ‘‹

+

Welcome!

-I'm Marvin.
-CSS enjoyer, pianist, creator of Mastery Chart. +I'm Marvin – M.Sc. student, fullstack developer and creator of Mastery Chart.
+Located in Baden-WΓΌrttemberg, Germany .

- - -
- +![PHP](https://img.shields.io/badge/-PHP-777BB4?style=flat-square&logo=php&logoColor=white) +![Plausible](https://img.shields.io/badge/-Plausible-5850EC?style=flat-square&logo=plausibleanalytics&logoColor=white) +![Sentry](https://img.shields.io/badge/-Sentry-362D59?style=flat-square&logo=sentry&logoColor=white) +![OpenAI](https://img.shields.io/badge/-OpenAI-412991?style=flat-square&logo=openai&logoColor=white) +![Obsidian](https://img.shields.io/badge/-Obsidian-7C3AED?style=flat-square&logo=obsidian&logoColor=white) +![Sass](https://img.shields.io/badge/-Sass-CC6699?style=flat-square&logo=sass&logoColor=white) +![Chart.js](https://img.shields.io/badge/-Chart.js-FF6384?style=flat-square&logo=chartdotjs&logoColor=white) +![Riot API](https://img.shields.io/badge/-Riot_API-EB0029?style=flat-square&logo=riotgames&logoColor=white) +![Ruby](https://img.shields.io/badge/-Ruby-CC342D?style=flat-square&logo=ruby&logoColor=white) +![Git](https://img.shields.io/badge/-Git-F05032?style=flat-square&logo=git&logoColor=white) +![HTML5](https://img.shields.io/badge/-HTML5-E34F26?style=flat-square&logo=html5&logoColor=white) +![GitLab CI](https://img.shields.io/badge/-GitLab_CI-FC6D26?style=flat-square&logo=gitlab&logoColor=white) +![Cloudflare](https://img.shields.io/badge/-Cloudflare-F38020?style=flat-square&logo=cloudflare&logoColor=white) +![D3.js](https://img.shields.io/badge/-D3.js-F9A03C?style=flat-square&logo=d3dotjs&logoColor=white) +![JavaScript](https://img.shields.io/badge/-JavaScript-F7DF1E?style=flat-square&logo=javascript&logoColor=white) +![Bash](https://img.shields.io/badge/-Bash-4EAA25?style=flat-square&logo=gnubash&logoColor=white) +![NGINX](https://img.shields.io/badge/-NGINX-009639?style=flat-square&logo=nginx&logoColor=white) +![Weblate](https://img.shields.io/badge/-Weblate-2ECCAA?style=flat-square&logo=weblate&logoColor=white) +![SonarQube](https://img.shields.io/badge/-SonarQube-4E9BCD?style=flat-square&logo=sonarqube&logoColor=white) +![VS Code](https://img.shields.io/badge/-VS_Code-007ACC?style=flat-square&logo=visual-studio-code&logoColor=white) +![Docker](https://img.shields.io/badge/-Docker-2496ED?style=flat-square&logo=Docker&logoColor=white) +![GitHub Actions](https://img.shields.io/badge/-GitHub_Actions-2496ED?style=flat-square&logo=githubactions&logoColor=white) +![Python](https://img.shields.io/badge/-Python-3776AB?style=flat-square&logo=python&logoColor=white) +![MariaDB](https://img.shields.io/badge/-MariaDB-1F305F?style=flat-square&logo=mariadb&logoColor=white) +![Markdown](https://img.shields.io/badge/-Markdown-000000?style=flat-square&logo=markdown&logoColor=white) +![Ghost](https://img.shields.io/badge/-Ghost-15171A?style=flat-square&logo=ghost&logoColor=white) +## 🎁 Open source + + + + + + + + + + + + + + + + + + +
DisenchanterStarsIssuesPull Requests
Duolingo API DockerizedStarsIssuesPull Requests
BibTeX OSSStarsIssuesPull Requests
+ +## πŸ“Š 30-day breakdown + +``` +wiha-tool 17:48:31 β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 46 % +masterychart 11:56:10 β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 31 % +marvinscham 5:08:39 β–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 13 % +disenchanter 2:05:48 β–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 5 % +bibtex-oss 1:05:34 β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 2 % +``` + +``` +JavaScript 8:47:04 β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 22 % +Python 7:45:10 β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 20 % +PHP 5:44:46 β–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 14 % +HTML 3:53:25 β–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 10 % +YAML 3:50:46 β–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 10 % +``` + +## πŸ““ Latest blog posts + + +- [![](https://gitlab.ms-ds.org/msds/icons/-/raw/main/icons-small/ghost.png) Splitting MIDI Controller Input](https://blog.marvinscham.de/splitting-midi/) + +- [![](https://gitlab.ms-ds.org/msds/icons/-/raw/main/icons-small/ghost.png) (DE) DKIM mit Strato SMTP und Cloudflare DNS](https://blog.marvinscham.de/dkim-strato-cloudflare/) + +- [![](https://gitlab.ms-ds.org/msds/icons/-/raw/main/icons-small/ghost.png) About Mastery Chart](https://blog.marvinscham.de/about-mastery-chart/) + + +## πŸ¦‰ Duolingo progress + +- πŸ”₯ Streak: 909 +- ⚑ 75964 XP +- πŸ‘¨β€πŸŽ“ Currently learning: Esperanto + +## More + +Homepage +GitHub +LinkedIn +Duolingo + + +------------ + +

Last update: Friday, 3 May 06:49 CEST

\ No newline at end of file diff --git a/readme_updater.py b/readme_updater.py new file mode 100644 index 0000000..d3f6526 --- /dev/null +++ b/readme_updater.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +import base64 +import datetime +import json +from dotenv import load_dotenv +from jinja2 import Environment, FileSystemLoader +import matplotlib.colors as mcolors +import os + +import pytz +import requests + +load_dotenv() + +# Constants for the progress bar +MAX_BAR_LENGTH = 30 +BAR_CHAR = 'β–ˆ' +EMPTY_BAR_CHAR = 'β–‘' + + +def hex_to_rgb(hex_color): + # Helper function to convert hex to RGB + return tuple(int(hex_color[i:i+2], 16) / 255.0 for i in (1, 3, 5)) + + +def shift_hue(obj, hue_shift): + # Shift hue to determine rainbow start + hue = mcolors.rgb_to_hsv(hex_to_rgb(obj['color']))[0] + hue_shift + if hue > 1: + hue -= 1.0 + return hue + + +def calc_darkness_bias(obj, threshold): + # Threshold 1: No bias + brightness = mcolors.rgb_to_hsv(hex_to_rgb(obj['color']))[2] + if brightness < threshold: + return 2 - brightness + else: + return 0 + + +resource_dir = os.path.join(os.path.dirname( + os.path.abspath(__file__)), "resources") +env = Environment(loader=FileSystemLoader(resource_dir)) + +# Load template +template = env.get_template('README.md.jinja') + +# Load metadata files +with open(os.path.join(resource_dir, "technologies.json")) as f: + technologies = json.load(f) +with open(os.path.join(resource_dir, "projects.json")) as f: + projects = json.load(f) +with open(os.path.join(resource_dir, "socials.json")) as f: + socials = json.load(f) + +# Sort to build rainbow +hue_shift = 0.37 +darkness_bias = 0.2 + +technologies = sorted( + technologies, + key=lambda obj: shift_hue(obj, hue_shift) + + calc_darkness_bias(obj, darkness_bias) +) + +blog_entries = {} +try: + response = requests.get( + f"{os.getenv("GHOST_URL")}/ghost/api/content/posts/?key={os.getenv("GHOST_KEY")}") + blog_entries = response.json()['posts'][:3] +except Exception as e: + print(e) + pass + +waka_projects = "" +waka_langs = "" +try: + waka_token = base64.b64encode( + os.getenv("WAKAPI_KEY").encode("ascii")).decode("ascii") + response = requests.get( + f"{os.getenv("WAKAPI_URL")}/api/summary?interval=30_days", + headers={'Authorization': f"Basic {waka_token}"} + ) + waka_info = response.json() + + total_duration = sum(item["total"] for item in waka_info["machines"]) + + project_list = waka_info["projects"][:5] + lang_list = waka_info["languages"][:5] + + max_name_len = max(len(entry["key"]) for entry in project_list) + max_lang_len = max(len(entry["key"]) for entry in lang_list) + max_key_len = max(max_name_len, max_lang_len) + + max_proj_time_len = max(len(str(datetime.timedelta(seconds=entry["total"]))) for entry in project_list) + max_lang_time_len = max(len(str(datetime.timedelta(seconds=entry["total"]))) for entry in lang_list) + max_total_len = max(max_proj_time_len, max_lang_time_len) + + waka_projects += "```\n" + for project in project_list: + filled_length = int((project["total"] / total_duration) * MAX_BAR_LENGTH) + progress_bar = BAR_CHAR * filled_length + EMPTY_BAR_CHAR * (MAX_BAR_LENGTH - filled_length) + percentage_str = str(int((project["total"] / total_duration * 100))) + " %" + + waka_projects += f"{project['key']:<{max_key_len}} " + waka_projects += f"{str(datetime.timedelta(seconds=project["total"])):>{max_total_len}} " + waka_projects += f"{progress_bar} " + waka_projects += f"{percentage_str:>4}\n" + waka_projects += "```" + + waka_langs += "```\n" + for lang in lang_list: + filled_length = int((lang["total"] / total_duration) * MAX_BAR_LENGTH) + progress_bar = BAR_CHAR * filled_length + EMPTY_BAR_CHAR * (MAX_BAR_LENGTH - filled_length) + percentage_str = str(int((lang["total"] / total_duration * 100))) + " %" + + waka_langs += f"{lang['key']:<{max_key_len}} " + waka_langs += f"{str(datetime.timedelta(seconds=lang["total"])):>{max_total_len}} " + waka_langs += f"{progress_bar} " + waka_langs += f"{percentage_str:>4}\n" + waka_langs += "```" +except Exception as e: + waka_projects = "" + waka_langs = "" + print(e) + pass + +duolingo_stats = {} +try: + response = requests.get(os.getenv("DUOLINGO_URL")) + duolingo_stats = response.json() + + for lang in duolingo_stats["lang_data"]: + if duolingo_stats["lang_data"][lang]["learningLanguage"] == duolingo_stats["learning_language"]: + current_lang = duolingo_stats["lang_data"][lang]["learningLanguageFull"] + + duolingo_stats["current_lang"] = current_lang +except Exception as e: + print(e) + pass + +berlin_timezone = pytz.timezone('Europe/Berlin') +berlin_time = datetime.datetime.now(berlin_timezone) +last_update = berlin_time.strftime('%A, %e %B %H:%M %Z') + +# Variables to pass to the template +data = { + "technologies": technologies, + "projects": projects, + "blog_entries": blog_entries, + "waka_projects": waka_projects, + "waka_langs": waka_langs, + "duolingo_stats": duolingo_stats, + "socials": socials, + "last_update": last_update +} + +# Render the template with data +output = template.render(data) + +# Write the output to README.md +with open('README.md', 'w', encoding='utf-8') as f: + f.write(output) + +print("README.md generated successfully.") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7324bf4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +jinja2 +matplotlib +requests +python-dotenv +pytz diff --git a/resources/README.md.jinja b/resources/README.md.jinja new file mode 100644 index 0000000..66b0228 --- /dev/null +++ b/resources/README.md.jinja @@ -0,0 +1,60 @@ +
+ +

Welcome!

+

+I'm Marvin – M.Sc. student, fullstack developer and creator of Mastery Chart.
+Located in Baden-WΓΌrttemberg, Germany . +

+ +
+ +[//]: # "Derived from https://github.com/thmsgbrt/thmsgbrt" + +## πŸ›  Tools I use + +{% for item in technologies -%} +![{{ item['name'] }}](https://img.shields.io/badge/-{{ item["name"] | replace(" ", "_") }}-{{ item['color'] | replace("#", "") }}?style=flat-square&logo={{ item['logo'] or item['name'] | replace(" ", "_") }}&logoColor=white) +{% endfor -%} + +## 🎁 Open source + + + +{%- for project in projects -%} + + + + + + +{%- endfor -%} + +
{{ project['name'] }}StarsIssuesPull Requests
+ +## πŸ“Š 30-day breakdown + +{{ waka_projects }} + +{{ waka_langs }} + +## πŸ““ Latest blog posts + +{% for entry in blog_entries %} +- [![](https://gitlab.ms-ds.org/msds/icons/-/raw/main/icons-small/ghost.png) {{ entry['title'] }}]({{ entry['url'] }}) +{% endfor %} + +## πŸ¦‰ Duolingo progress + +- πŸ”₯ Streak: {{ "%d"|format(duolingo_stats["streak"]|float) }} +- ⚑ {{ "%d"|format(duolingo_stats["xp"]|float) }} XP +- πŸ‘¨β€πŸŽ“ Currently learning: {{ duolingo_stats['current_lang'] }} + +## More + +{% for social in socials -%} +{{ social['name'] }} +{% endfor %} + +------------ + +

Last update: {{ last_update }}

diff --git a/resources/projects.json b/resources/projects.json new file mode 100644 index 0000000..1ad4734 --- /dev/null +++ b/resources/projects.json @@ -0,0 +1,8 @@ +[ + { "name": "Disenchanter", "repository": "marvinscham/disenchanter" }, + { + "name": "Duolingo API Dockerized", + "repository": "marvinscham/duolingo-api-dockerized" + }, + { "name": "BibTeX OSS", "repository": "marvinscham/bibtex-oss" } +] diff --git a/resources/socials.json b/resources/socials.json new file mode 100644 index 0000000..8183af5 --- /dev/null +++ b/resources/socials.json @@ -0,0 +1,26 @@ +[ + { + "name": "Homepage", + "url": "https://marvinscham.de", + "color": "#0d254c", + "logo": "googlechrome" + }, + { + "name": "GitHub", + "url": "https://github.vom/marvinscham", + "color": "#181717", + "logo": "github" + }, + { + "name": "LinkedIn", + "url": "https://www.linkedin.com/in/marvin-scham-58576216b/", + "color": "#0A66C2", + "logo": "linkedin" + }, + { + "name": "Duolingo", + "url": "https://duolingo.com/profile/marvinscham", + "color": "#58CC02", + "logo": "duolingo" + } +] diff --git a/resources/technologies.json b/resources/technologies.json new file mode 100644 index 0000000..bdc290d --- /dev/null +++ b/resources/technologies.json @@ -0,0 +1,28 @@ +[ + { "name": "Bash", "logo": "gnubash", "color": "#4EAA25" }, + { "name": "Chart.js", "logo": "chartdotjs", "color": "#FF6384" }, + { "name": "Cloudflare", "logo": "cloudflare", "color": "#F38020" }, + { "name": "D3.js", "logo": "d3dotjs", "color": "#F9A03C" }, + { "name": "Docker", "logo": "Docker", "color": "#2496ED" }, + { "name": "Ghost", "logo": "ghost", "color": "#15171A" }, + { "name": "Git", "logo": "git", "color": "#F05032" }, + { "name": "GitHub Actions", "logo": "githubactions", "color": "#2496ED" }, + { "name": "GitLab CI", "logo": "gitlab", "color": "#FC6D26" }, + { "name": "HTML5", "logo": "html5", "color": "#E34F26" }, + { "name": "JavaScript", "logo": "javascript", "color": "#F7DF1E" }, + { "name": "MariaDB", "logo": "mariadb", "color": "#1F305F" }, + { "name": "Markdown", "logo": "markdown", "color": "#000000" }, + { "name": "NGINX", "logo": "nginx", "color": "#009639" }, + { "name": "Obsidian", "logo": "obsidian", "color": "#7C3AED" }, + { "name": "OpenAI", "logo": "openai", "color": "#412991" }, + { "name": "PHP", "logo": "php", "color": "#777BB4" }, + { "name": "Plausible", "logo": "plausibleanalytics", "color": "#5850EC" }, + { "name": "Python", "logo": "python", "color": "#3776AB" }, + { "name": "Riot API", "logo": "riotgames", "color": "#EB0029" }, + { "name": "Ruby", "logo": "ruby", "color": "#CC342D" }, + { "name": "Sass", "logo": "sass", "color": "#CC6699" }, + { "name": "Sentry", "logo": "sentry", "color": "#362D59" }, + { "name": "SonarQube", "logo": "sonarqube", "color": "#4E9BCD" }, + { "name": "VS Code", "logo": "visual-studio-code", "color": "#007ACC" }, + { "name": "Weblate", "logo": "weblate", "color": "#2ECCAA" } +] diff --git a/resources/wave.gif b/resources/wave.gif new file mode 100644 index 0000000..22feb4c Binary files /dev/null and b/resources/wave.gif differ