From fc192ce51705655e3f96ae2405a870887c4500ee Mon Sep 17 00:00:00 2001 From: s-FunIA Date: Sun, 18 Jan 2026 16:41:55 +0100 Subject: [PATCH] ci: auto-label issues from Type/State --- .gitea/workflows/auto-label-issues.yml | 110 +++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 .gitea/workflows/auto-label-issues.yml diff --git a/.gitea/workflows/auto-label-issues.yml b/.gitea/workflows/auto-label-issues.yml new file mode 100644 index 0000000..a0f8a30 --- /dev/null +++ b/.gitea/workflows/auto-label-issues.yml @@ -0,0 +1,110 @@ +name: Auto-label issues + +on: + issues: + types: [opened, edited] + +jobs: + label: + runs-on: ubuntu-latest + steps: + - name: Apply labels from Type/State + env: + FORGE_BASE: ${{ vars.FORGE_BASE }} + FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }} + REPO_FULL: ${{ gitea.repository }} + EVENT_PATH: ${{ github.event_path }} + run: | + python3 - <<'PY' + import json, os, re, urllib.request, urllib.error + + forge = os.environ["FORGE_BASE"].rstrip("/") + token = os.environ["FORGE_TOKEN"] + owner, repo = os.environ["REPO_FULL"].split("/", 1) + event_path = os.environ["EVENT_PATH"] + + with open(event_path, "r", encoding="utf-8") as f: + ev = json.load(f) + + issue = ev.get("issue") or {} + title = issue.get("title") or "" + body = issue.get("body") or "" + number = issue.get("number") or issue.get("index") + if not number: + raise SystemExit("No issue number/index in event payload") + + def pick_line(key): + m = re.search(rf"^{re.escape(key)}:\s*([^\n\r]+)", body, flags=re.M) + return m.group(1).strip() if m else "" + + desired = set() + + t = pick_line("Type") + s = pick_line("State") + + # 1) Type / State explicit (depuis le site) + if t: desired.add(t) + if s: desired.add(s) + + # 2) Fallback depuis le titre si besoin + if not t: + if title.startswith("[Correction]"): + desired.add("type/correction") + elif title.startswith("[Fact-check]") or title.startswith("[Vérification]"): + desired.add("type/fact-check") + + if not s: + # état par défaut si absent + if "type/fact-check" in desired: + desired.add("state/a-sourcer") + elif "type/correction" in desired: + desired.add("state/recevable") + + if not desired: + print("No labels to apply.") + raise SystemExit(0) + + api = forge + "/api/v1" + headers = { + "Authorization": f"token {token}", + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": "archicratie-auto-label/1.0", + } + + def jreq(method, url, payload=None): + data = None if payload is None else json.dumps(payload).encode("utf-8") + req = urllib.request.Request(url, data=data, headers=headers, method=method) + try: + with urllib.request.urlopen(req) as r: + b = r.read() + return json.loads(b.decode("utf-8")) if b else None + except urllib.error.HTTPError as e: + b = e.read().decode("utf-8", errors="replace") + raise RuntimeError(f"HTTP {e.code} {method} {url}\n{b}") from e + + # labels repo + labels = jreq("GET", f"{api}/repos/{owner}/{repo}/labels?limit=1000") or [] + name_to_id = {x.get("name"): x.get("id") for x in labels} + + missing = [x for x in desired if x not in name_to_id] + if missing: + raise SystemExit("Missing labels in repo: " + ", ".join(sorted(missing))) + + wanted_ids = [name_to_id[x] for x in desired] + + # labels actuels de l'issue + current = jreq("GET", f"{api}/repos/{owner}/{repo}/issues/{number}/labels") or [] + current_ids = {x.get("id") for x in current if x.get("id") is not None} + + final_ids = sorted(current_ids.union(wanted_ids)) + + # set labels = union (n'enlève rien) + url = f"{api}/repos/{owner}/{repo}/issues/{number}/labels" + try: + jreq("PUT", url, {"labels": final_ids}) + except Exception: + jreq("PUT", url, final_ids) + + print(f"OK labels #{number}: {sorted(desired)}") + PY