f19b271642
Rebuild the Gitea Codex review bot from the product contract with a Go HTTP service, durable SQL queue, typed Gitea client, isolated runner, and deployment updates. Co-Authored-By: Claude <noreply@anthropic.com>
128 lines
3.4 KiB
Go
128 lines
3.4 KiB
Go
package webhook
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gitea-codex-bot/internal/domain"
|
|
)
|
|
|
|
func VerifySignature(body []byte, secret, supplied string) bool {
|
|
supplied = strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(supplied), "sha256="))
|
|
if supplied == "" {
|
|
return false
|
|
}
|
|
mac := hmac.New(sha256.New, []byte(secret))
|
|
_, _ = mac.Write(body)
|
|
expected := hex.EncodeToString(mac.Sum(nil))
|
|
return hmac.Equal([]byte(strings.ToLower(expected)), []byte(strings.ToLower(supplied)))
|
|
}
|
|
|
|
func Digest(body []byte) string { sum := sha256.Sum256(body); return hex.EncodeToString(sum[:]) }
|
|
|
|
func ParseEvent(eventName, delivery string, body []byte) (domain.WebhookEvent, error) {
|
|
if eventName != "issue_comment" && eventName != "pull_request_comment" {
|
|
return domain.WebhookEvent{}, errors.New("event ignored")
|
|
}
|
|
var raw map[string]any
|
|
if err := json.Unmarshal(body, &raw); err != nil {
|
|
return domain.WebhookEvent{}, errors.New("invalid JSON payload")
|
|
}
|
|
repo := nestedString(raw, "repository", "full_name")
|
|
commentID := nestedInt64(raw, "comment", "id")
|
|
sender := nestedString(raw, "sender", "username")
|
|
commentBody := nestedString(raw, "comment", "body")
|
|
if repo == "" || commentID <= 0 {
|
|
return domain.WebhookEvent{}, errors.New("not a pull request comment")
|
|
}
|
|
prNumber, headSHA := 0, ""
|
|
if eventName == "issue_comment" {
|
|
if _, ok := raw["pull_request"]; !ok || raw["pull_request"] == nil {
|
|
return domain.WebhookEvent{}, errors.New("not a pull request comment")
|
|
}
|
|
issue, ok := raw["issue"].(map[string]any)
|
|
if !ok || !truthy(issue["pull_request"]) {
|
|
return domain.WebhookEvent{}, errors.New("not a pull request comment")
|
|
}
|
|
prNumber = number(issue["number"])
|
|
headSHA = nestedString(raw, "pull_request", "head", "sha")
|
|
} else {
|
|
pr, ok := raw["pull_request"].(map[string]any)
|
|
if !ok || pr == nil {
|
|
return domain.WebhookEvent{}, errors.New("not a pull request comment")
|
|
}
|
|
prNumber = number(pr["number"])
|
|
headSHA = nestedString(raw, "pull_request", "head", "sha")
|
|
}
|
|
if prNumber <= 0 {
|
|
return domain.WebhookEvent{}, errors.New("not a pull request comment")
|
|
}
|
|
if headSHA == "" {
|
|
headSHA = "unknown"
|
|
}
|
|
return domain.WebhookEvent{EventName: eventName, DeliveryID: delivery, Repo: repo, PRNumber: prNumber, HeadSHA: headSHA, CommentID: commentID, CommentBody: strings.TrimSpace(commentBody), Sender: sender, PayloadSHA256: Digest(body)}, nil
|
|
}
|
|
|
|
func nestedString(raw map[string]any, path ...string) string {
|
|
var current any = raw
|
|
for _, key := range path {
|
|
obj, ok := current.(map[string]any)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
current = obj[key]
|
|
}
|
|
if value, ok := current.(string); ok {
|
|
return value
|
|
}
|
|
return ""
|
|
}
|
|
func nestedInt64(raw map[string]any, path ...string) int64 {
|
|
var current any = raw
|
|
for _, key := range path {
|
|
obj, ok := current.(map[string]any)
|
|
if !ok {
|
|
return 0
|
|
}
|
|
current = obj[key]
|
|
}
|
|
return int64(number(current))
|
|
}
|
|
func number(value any) int {
|
|
switch v := value.(type) {
|
|
case float64:
|
|
return int(v)
|
|
case json.Number:
|
|
n, _ := strconv.Atoi(string(v))
|
|
return n
|
|
case int:
|
|
return v
|
|
case int64:
|
|
return int(v)
|
|
case string:
|
|
n, _ := strconv.Atoi(v)
|
|
return n
|
|
}
|
|
return 0
|
|
}
|
|
func truthy(value any) bool {
|
|
switch v := value.(type) {
|
|
case bool:
|
|
return v
|
|
case map[string]any:
|
|
return len(v) > 0
|
|
case string:
|
|
return strings.TrimSpace(v) != ""
|
|
default:
|
|
return value != nil
|
|
}
|
|
}
|
|
|
|
var _ = fmt.Sprintf
|