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