From bd21c6ad68f45cad94ca01179b994fc59e3a7a3d Mon Sep 17 00:00:00 2001 From: alexander Date: Sun, 25 Jan 2026 11:38:55 +0100 Subject: [PATCH] Added codeberg --- src/inventory.py | 4 ++ src/sources/__init__.py | 13 +++- src/sources/codeberg.py | 146 ++++++++++++++++++++++++++++++++++++++++ src/sources/gitea.py | 8 +-- static/css/main.css | 7 ++ static/css/main.css.map | 2 +- styles/main.scss | 7 ++ templates/index.html.j2 | 13 +++- templates/navbar.j2 | 2 +- 9 files changed, 193 insertions(+), 9 deletions(-) create mode 100644 src/sources/codeberg.py diff --git a/src/inventory.py b/src/inventory.py index 1497ede..5f1e5a0 100644 --- a/src/inventory.py +++ b/src/inventory.py @@ -4,6 +4,7 @@ import traceback from sources import CSInventoryConfig, CSItem, CheatsheetSourceType from sources.plain import process_plain_url from sources.gitea import process_gitea +from sources.codeberg import process_codeberg from logger import get_worker_thread_logger def load_cheatsheet_inventory(file: str) -> CSInventoryConfig: @@ -38,6 +39,9 @@ def prepare_cheatsheets(config: CSInventoryConfig, outdir: str) -> list[CSItem]: case CheatsheetSourceType.PLAIN_URL: new_items.append(process_plain_url(item, outdir)) + case CheatsheetSourceType.CODEBERG_SOURCE: + new_items += process_codeberg(item, outdir) + case _: logger.warning("Unknown Source Type: %s", item.source.type) except: diff --git a/src/sources/__init__.py b/src/sources/__init__.py index 641c237..6f1bc7d 100644 --- a/src/sources/__init__.py +++ b/src/sources/__init__.py @@ -7,8 +7,10 @@ from uuid import uuid4 # Configuration class CheatsheetSourceType(str, Enum): GITEA_SOURCE = "gitea" + CODEBERG_SOURCE = "codeberg" PLAIN_URL = "url" + class CSSourceBase(BaseModel): type: CheatsheetSourceType @@ -21,12 +23,21 @@ class CSSourceGitea(CSSourceBase): tag: str hide_repo: bool = False +class CSSourceCodeberg(CSSourceBase): + type: Literal[CheatsheetSourceType.CODEBERG_SOURCE] + base_url: str + fetch_url: str | None = Field(default=None) + repo: str + owner: str + tag: str + hide_repo: bool = False + class CSSourcePlainURL(CSSourceBase): type: Literal[CheatsheetSourceType.PLAIN_URL] url: str repo_url: str -CSSourceType = CSSourcePlainURL | CSSourceGitea +CSSourceType = CSSourcePlainURL | CSSourceGitea | CSSourceCodeberg class CSInventoryItem(BaseModel): source: CSSourceType diff --git a/src/sources/codeberg.py b/src/sources/codeberg.py new file mode 100644 index 0000000..176561e --- /dev/null +++ b/src/sources/codeberg.py @@ -0,0 +1,146 @@ + +from datetime import datetime +from sources import CSSourceGitea, CSItem, CSInventoryItem +from sources.util import cache_cheatsheet, get_datestring +import httpx +from pathlib import Path +from logger import get_worker_thread_logger + + +def process_codeberg(item: CSInventoryItem, outdir: str) -> list[CSItem] | None: + logger = get_worker_thread_logger() + logger.info(f"Processing Gitea cheatsheet: {item.source.owner}/{item.source.repo}@{item.source.tag}") + source: CSSourceGitea = item.source + commit_hash, tag_id = get_codeberg_release_commit_sha(source.fetch_url or source.base_url, source.owner, source.repo, source.tag) + assets = list_codeberg_release_assets(source.fetch_url or source.base_url, source.owner, source.repo, tag_id) + + assets = list(filter(lambda a: a[1].endswith(".pdf"), assets)) + print(assets) + assets_urls = list(map(lambda a: ( + f"{source.fetch_url or source.base_url}/{source.owner}/{source.repo}/releases/download/{source.tag}/{a[1]}", + f"{source.base_url}/{source.owner}/{source.repo}/releases/download/{source.tag}/{a[1]}" + ), assets), + ) + + logger.info(f"Found {len(assets_urls)} PDF assets in Gitea release {source.owner}/{source.repo}@{source.tag}") + + res = [] + + for fetch_url, real_url in assets_urls: + + if item.cache: + cache_url = cache_cheatsheet(fetch_url, outdir) + if cache_url: + real_url = cache_url + else: + continue + + name = Path(real_url).stem + + res.append(CSItem( + url = real_url, + date=get_datestring(), + commit=commit_hash[:10] if commit_hash else "", + author=item.author if item.author else source.owner, + title=f"{name}", + id=item.id, + git_repo=f"{source.base_url}/{source.owner}/{source.repo}" if not source.hide_repo else "", + git_repo_type="Codeberg" + )) + + return res + +def get_codeberg_release_commit_sha(base_url, owner, repo, tag_name, token=None): + """ + Resolve the commit SHA for a Codeberg release tag. + + :param base_url: e.g. "https://codeberg.org" + :param owner: repo owner + :param repo: repository name + :param tag_name: release tag (e.g. "v1.2.3") + :param token: optional API token + :return: commit SHA (str) + """ + logger = get_worker_thread_logger() + + + with httpx.Client() as client: + headers = {} + if token: + headers["Authorization"] = f"token {token}" + + # 1) List tags and find the matching tag + tags_url = f"{base_url}/api/v1/repos/{owner}/{repo}/tags" + resp = client.get(tags_url, headers=headers) + resp.raise_for_status() + tags = resp.json() + + + sorted_tags = list(sorted(tags, key=lambda t: datetime.fromisoformat(t["commit"]["created"]), reverse=True) ) + + tag = sorted_tags[0] if sorted_tags else None + if not tag: + logger.warning(f"Tag '{tag_name}' not found") + return None, "" + + # Lightweight tags usually already contain the commit SHA + commit_sha = tag.get("commit", {}).get("sha") + tag_obj_sha = tag.get("id") + + # If commit.sha looks valid, return it + if commit_sha: + return commit_sha, tag.get("name") + + # 2) Annotated tag: dereference via /git/tags/{sha} + if not tag_obj_sha: + raise RuntimeError("Tag object SHA missing; cannot dereference annotated tag") + + git_tag_url = f"{base_url}/api/v1/repos/{owner}/{repo}/git/tags/{tag_obj_sha}" + resp = client.get(git_tag_url, headers=headers) + resp.raise_for_status() + annotated = resp.json() + + # The object pointed to by the tag (usually a commit) + target = annotated.get("object", {}) + if target.get("type") != "commit": + raise RuntimeError(f"Tag points to a {target.get('type')} instead of a commit") + + return target.get("sha"), tag.get("name") + + +def list_codeberg_release_assets(base_url, owner, repo, tag, token=None): + """ + Return a list of (download_url, filename) for all assets of a Codeberg release. + + :param base_url: Codeberg host URL, e.g. "https://codeberg.org" + :param owner: repository owner + :param repo: repository name + :param tag: release tag name + :param token: optional API token + :returns: list of (download_url, filename) tuples + """ + + with httpx.Client() as client: + headers = {} + if token: + headers["Authorization"] = f"token {token}" + + # 1) Get release by tag + rel_url = f"{base_url}/api/v1/repos/{owner}/{repo}/releases/tags/{tag}" + rel_resp = client.get(rel_url, headers=headers) + rel_resp.raise_for_status() + release: dict = rel_resp.json() + + assets = release.get("assets", []) + result = [] + + for asset in assets: + # Codeberg asset info usually contains: + # - "browser_download_url" → direct URL + # - "name" → filename + download_url = asset.get("browser_download_url") + filename = asset.get("name") + if download_url and filename: + result.append((download_url, filename)) + + return result \ No newline at end of file diff --git a/src/sources/gitea.py b/src/sources/gitea.py index 2ef397a..4e0bc5c 100644 --- a/src/sources/gitea.py +++ b/src/sources/gitea.py @@ -10,8 +10,8 @@ def process_gitea(item: CSInventoryItem, outdir: str) -> list[CSItem] | None: logger = get_worker_thread_logger() logger.info(f"Processing Gitea cheatsheet: {item.source.owner}/{item.source.repo}@{item.source.tag}") source: CSSourceGitea = item.source - commit_hash = get_release_commit_sha(source.fetch_url or source.base_url, source.owner, source.repo, source.tag) - assets = list_release_assets(source.fetch_url or source.base_url, source.owner, source.repo, source.tag) + commit_hash = get_gitea_release_commit_sha(source.fetch_url or source.base_url, source.owner, source.repo, source.tag) + assets = list_gitea_release_assets(source.fetch_url or source.base_url, source.owner, source.repo, source.tag) assets = list(filter(lambda a: a[1].endswith(".pdf"), assets)) print(assets) @@ -49,7 +49,7 @@ def process_gitea(item: CSInventoryItem, outdir: str) -> list[CSItem] | None: return res -def get_release_commit_sha(base_url, owner, repo, tag_name, token=None): +def get_gitea_release_commit_sha(base_url, owner, repo, tag_name, token=None): """ Resolve the commit SHA for a Gitea release tag. @@ -102,7 +102,7 @@ def get_release_commit_sha(base_url, owner, repo, tag_name, token=None): return target.get("sha") -def list_release_assets(base_url, owner, repo, tag, token=None): +def list_gitea_release_assets(base_url, owner, repo, tag, token=None): """ Return a list of (download_url, filename) for all assets of a Gitea release. diff --git a/static/css/main.css b/static/css/main.css index fb69368..9302980 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -61,6 +61,13 @@ h1 { padding-bottom: 10px; } +h3 { + color: #333; + text-align: center; + font-weight: normal; + margin-top: 5px; +} + table { width: calc(100% - 40px); border-collapse: collapse; diff --git a/static/css/main.css.map b/static/css/main.css.map index 1ec0b49..23b8b3d 100644 --- a/static/css/main.css.map +++ b/static/css/main.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../styles/main.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAAA;AAKA;EACI;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;;;AAGJ;AACA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;;;AAGJ;AACA;AAAA;EAEI;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;;;AAGJ;EACI;EACA;;;AAGJ;EACI;;;AAGJ;AACA;EACI;EACA;EACA;EACA;EACA;EACA","file":"main.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../styles/main.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAAA;AAKA;EACI;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;;;AAGJ;AACA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;;;AAGJ;AACA;AAAA;EAEI;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;;;AAGJ;EACI;EACA;;;AAGJ;EACI;;;AAGJ;AACA;EACI;EACA;EACA;EACA;EACA;EACA","file":"main.css"} \ No newline at end of file diff --git a/styles/main.scss b/styles/main.scss index e222248..e203879 100644 --- a/styles/main.scss +++ b/styles/main.scss @@ -64,6 +64,13 @@ h1 { padding-bottom: 10px; } +h3 { + color: #333; + text-align: center; + font-weight: normal; + margin-top: 5px; +} + table { width: calc(100% - 40px); border-collapse: collapse; diff --git a/templates/index.html.j2 b/templates/index.html.j2 index 10d4ba2..3fd3a67 100644 --- a/templates/index.html.j2 +++ b/templates/index.html.j2 @@ -4,12 +4,21 @@ - FS² + typst4ei {% include "navbar.j2" %} -

Formel(sammlung)²

+

typst4ei

+

Eine Sammlung von Formelsammlung für/von EI Stundenten der TUM

+ +

+ Disclaimer: Die Richtigkeit des Materials kann nicht garantiert werden. + Wir wissen auch nicht was wir tun. Nutzt die Formelsammlungen auf eigene Gefahr. + + Aber Feedback und Korrekturen sind immer willkommen! +

+ diff --git a/templates/navbar.j2 b/templates/navbar.j2 index d1af675..c8dc8d2 100644 --- a/templates/navbar.j2 +++ b/templates/navbar.j2 @@ -4,6 +4,6 @@ HomeGitea - Impressum + License