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 @@

-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
.
| Disenchanter | +|||
| Duolingo API Dockerized | +|||
| BibTeX OSS | +
Esperanto
+
+## More
+
+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 @@ +
+I'm Marvin β M.Sc. student, fullstack developer and creator of Mastery Chart.
+Located in Baden-WΓΌrttemberg, Germany
.
+
| {{ project['name'] }} | +
{{ duolingo_stats['current_lang'] }}
+
+## More
+
+{% for social in socials -%}
+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