export interface PrWebhookEvent { kind: 'pr' issueNumber: number | undefined action: string pr: string branch: string htmlUrl: string title: string body: string raw: unknown } export interface IssueWebhookEvent { kind: 'issue' action: string issueNumber: number title: string body: string htmlUrl: string raw: unknown } export interface IssueCommentWebhookEvent { kind: 'issue_comment' action: string issueNumber: number prNumber: number | undefined commentBody: string commentAuthor: string commentHtmlUrl: string raw: unknown } export interface PushWebhookEvent { kind: 'push' branch: string raw: unknown } export type WebhookEvent = PrWebhookEvent | IssueWebhookEvent | IssueCommentWebhookEvent | PushWebhookEvent export function parseEvent( eventHeader: string, issueNumber: number | undefined, raw: unknown, ): WebhookEvent | null { if (!raw || typeof raw !== 'object') return null const obj = raw as { action?: unknown pull_request?: unknown issue?: unknown comment?: unknown ref?: unknown sender?: unknown } if (eventHeader === 'push') { // gitea push 事件无 `action`,顶层带 `ref: "refs/heads/"`。 if (typeof obj.ref !== 'string') return null const branch = obj.ref.replace(/^refs\/heads\//, '') if (!branch) return null return { kind: 'push', branch, raw } } if (typeof obj.action !== 'string') return null if (eventHeader === 'issue_comment') { const issue = obj.issue const comment = obj.comment if (!issue || typeof issue !== 'object') return null if (!comment || typeof comment !== 'object') return null const issueObj = issue as { number?: unknown pull_request?: unknown } const commentObj = comment as { body?: unknown html_url?: unknown user?: unknown } const num = typeof issueObj.number === 'number' ? issueObj.number : Number(issueObj.number) if (!Number.isFinite(num)) return null // gitea sets issue.pull_request (non-null object) when the issue is a PR. const prNumber = issueObj.pull_request && typeof issueObj.pull_request === 'object' ? num : undefined const commentBody = typeof commentObj.body === 'string' ? commentObj.body : '' const commentHtmlUrl = typeof commentObj.html_url === 'string' ? commentObj.html_url : '' const commentUser = commentObj.user as { login?: unknown, username?: unknown } | undefined const sender = obj.sender as { login?: unknown, username?: unknown } | undefined const commentAuthor = (typeof commentUser?.login === 'string' && commentUser.login) || (typeof commentUser?.username === 'string' && commentUser.username) || (typeof sender?.login === 'string' && sender.login) || (typeof sender?.username === 'string' && sender.username) || '' return { kind: 'issue_comment', action: obj.action, issueNumber: num, prNumber, commentAuthor, commentBody, commentHtmlUrl, raw, } } if (eventHeader === 'issues') { const issue = obj.issue if (!issue || typeof issue !== 'object') return null const issueObj = issue as { number?: unknown html_url?: unknown title?: unknown body?: unknown } const num = typeof issueObj.number === 'number' ? issueObj.number : Number(issueObj.number) if (!Number.isFinite(num)) return null const htmlUrl = typeof issueObj.html_url === 'string' ? issueObj.html_url : '' const title = typeof issueObj.title === 'string' ? issueObj.title : '' const body = typeof issueObj.body === 'string' ? issueObj.body : '' return { kind: 'issue', action: obj.action, issueNumber: num, title, body, htmlUrl, raw, } } // Default path: pull_request payload (preserves legacy behaviour for any // event header value, since gitea historically may not set the header). const pr = obj.pull_request if (!pr || typeof pr !== 'object') return null const prObj = pr as { number?: unknown html_url?: unknown head?: unknown title?: unknown body?: unknown } const num = typeof prObj.number === 'number' ? prObj.number : Number(prObj.number) if (!Number.isFinite(num)) return null const htmlUrl = typeof prObj.html_url === 'string' ? prObj.html_url : '' const head = prObj.head as { ref?: unknown } | undefined const branch = head && typeof head.ref === 'string' ? head.ref : '' const title = typeof prObj.title === 'string' ? prObj.title : '' const body = typeof prObj.body === 'string' ? prObj.body : '' if (!branch || !htmlUrl) return null return { kind: 'pr', issueNumber, action: obj.action, pr: String(num), branch, htmlUrl, title, body, raw, } }