"""Parse Gitea webhook payloads into typed `ForgeEvent`s. Only the fields the orchestrator acts on are extracted; unknown payloads and event types return None so the webhook layer can ignore them. Gitea sends the event kind in the `X-Gitea-Event` header and the payload as JSON. The relevant kinds: - `issues` with `action == "assigned"` -> IssueAssigned - `issue_comment` with `action == "created"` -> CommentCreated - `pull_request` with `action == "closed"` -> PullRequestClosed """ from __future__ import annotations from typing import Any from .model import CommentCreated, ForgeEvent, IssueAssigned, PullRequestClosed def _repo_owner(payload: dict[str, Any]) -> tuple[str, str]: repo = payload.get("repository") or {} owner = (repo.get("owner") or {}).get("login", "") return str(owner), str(repo.get("name", "")) def parse_event(event_kind: str, payload: dict[str, Any]) -> ForgeEvent | None: """Map (X-Gitea-Event, payload) to a `ForgeEvent`, or None to ignore.""" if event_kind == "issues": return _parse_issue(payload) if event_kind == "issue_comment": return _parse_comment(payload) if event_kind == "pull_request": return _parse_pull_request(payload) return None def _parse_issue(payload: dict[str, Any]) -> IssueAssigned | None: if payload.get("action") != "assigned": return None owner, repo = _repo_owner(payload) issue = payload.get("issue") or {} assignees = tuple( str(a.get("login", "")) for a in (issue.get("assignees") or []) ) labels = tuple(str(l.get("name", "")) for l in (issue.get("labels") or [])) return IssueAssigned( owner=owner, repo=repo, issue_number=int(issue.get("number", 0)), title=str(issue.get("title", "")), body=str(issue.get("body", "") or ""), assignees=assignees, labels=labels, ) def _parse_comment(payload: dict[str, Any]) -> CommentCreated | None: if payload.get("action") != "created": return None owner, repo = _repo_owner(payload) issue = payload.get("issue") or {} comment = payload.get("comment") or {} return CommentCreated( owner=owner, repo=repo, issue_number=int(issue.get("number", 0)), comment_id=int(comment.get("id", 0)), author=str((comment.get("user") or {}).get("login", "")), body=str(comment.get("body", "") or ""), is_pull=bool(issue.get("pull_request")), ) def _parse_pull_request(payload: dict[str, Any]) -> PullRequestClosed | None: if payload.get("action") != "closed": return None owner, repo = _repo_owner(payload) pr = payload.get("pull_request") or {} return PullRequestClosed( owner=owner, repo=repo, pr_number=int(pr.get("number", 0)), merged=bool(pr.get("merged", False)), )