mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-06 08:00:29 +01:00
Refine string formatting in Bark webhook handler for repo events, handle cases with nil repository in package payloads, and update related test assertions for clarity.
355 lines
10 KiB
Go
355 lines
10 KiB
Go
// Copyright 2025 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package webhook
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
webhook_model "code.gitea.io/gitea/models/webhook"
|
|
"code.gitea.io/gitea/modules/base"
|
|
"code.gitea.io/gitea/modules/git"
|
|
"code.gitea.io/gitea/modules/json"
|
|
"code.gitea.io/gitea/modules/log"
|
|
api "code.gitea.io/gitea/modules/structs"
|
|
"code.gitea.io/gitea/modules/util"
|
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
|
)
|
|
|
|
type (
|
|
// BarkPayload represents the payload for Bark notifications
|
|
BarkPayload struct {
|
|
Title string `json:"title"`
|
|
Body string `json:"body"`
|
|
URL string `json:"url,omitempty"`
|
|
Group string `json:"group,omitempty"`
|
|
Sound string `json:"sound,omitempty"`
|
|
Icon string `json:"icon,omitempty"`
|
|
}
|
|
|
|
// BarkMeta contains the metadata for the webhook
|
|
BarkMeta struct {
|
|
Sound string `json:"sound"`
|
|
Group string `json:"group"`
|
|
}
|
|
|
|
barkConvertor struct {
|
|
Sound string
|
|
Group string
|
|
}
|
|
)
|
|
|
|
// GetBarkHook returns bark metadata
|
|
func GetBarkHook(w *webhook_model.Webhook) *BarkMeta {
|
|
s := &BarkMeta{}
|
|
if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
|
|
log.Error("webhook.GetBarkHook(%d): %v", w.ID, err)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (bc barkConvertor) getGroup(defaultGroup string) string {
|
|
if bc.Group != "" {
|
|
return bc.Group
|
|
}
|
|
return defaultGroup
|
|
}
|
|
|
|
// Create implements PayloadConvertor Create method
|
|
func (bc barkConvertor) Create(p *api.CreatePayload) (BarkPayload, error) {
|
|
refName := git.RefName(p.Ref).ShortName()
|
|
title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName)
|
|
body := fmt.Sprintf("%s created %s %s", p.Sender.UserName, p.RefType, refName)
|
|
return BarkPayload{
|
|
Title: title,
|
|
Body: body,
|
|
URL: p.Repo.HTMLURL + "/src/" + util.PathEscapeSegments(refName),
|
|
Group: bc.getGroup(p.Repo.FullName),
|
|
Sound: bc.Sound,
|
|
}, nil
|
|
}
|
|
|
|
// Delete implements PayloadConvertor Delete method
|
|
func (bc barkConvertor) Delete(p *api.DeletePayload) (BarkPayload, error) {
|
|
refName := git.RefName(p.Ref).ShortName()
|
|
title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName)
|
|
body := fmt.Sprintf("%s deleted %s %s", p.Sender.UserName, p.RefType, refName)
|
|
return BarkPayload{
|
|
Title: title,
|
|
Body: body,
|
|
URL: p.Repo.HTMLURL,
|
|
Group: bc.getGroup(p.Repo.FullName),
|
|
Sound: bc.Sound,
|
|
}, nil
|
|
}
|
|
|
|
// Fork implements PayloadConvertor Fork method
|
|
func (bc barkConvertor) Fork(p *api.ForkPayload) (BarkPayload, error) {
|
|
title := fmt.Sprintf("[%s] Repository forked", p.Forkee.FullName)
|
|
body := fmt.Sprintf("%s forked %s to %s", p.Sender.UserName, p.Forkee.FullName, p.Repo.FullName)
|
|
return BarkPayload{
|
|
Title: title,
|
|
Body: body,
|
|
URL: p.Repo.HTMLURL,
|
|
Group: bc.getGroup(p.Forkee.FullName),
|
|
Sound: bc.Sound,
|
|
}, nil
|
|
}
|
|
|
|
// Push implements PayloadConvertor Push method
|
|
func (bc barkConvertor) Push(p *api.PushPayload) (BarkPayload, error) {
|
|
branchName := git.RefName(p.Ref).ShortName()
|
|
|
|
var titleLink string
|
|
if p.TotalCommits == 1 {
|
|
titleLink = p.Commits[0].URL
|
|
} else {
|
|
titleLink = p.CompareURL
|
|
}
|
|
if titleLink == "" {
|
|
titleLink = p.Repo.HTMLURL + "/src/" + util.PathEscapeSegments(branchName)
|
|
}
|
|
|
|
title := fmt.Sprintf("[%s:%s] %d new commit(s)", p.Repo.FullName, branchName, p.TotalCommits)
|
|
|
|
var body strings.Builder
|
|
body.WriteString(fmt.Sprintf("%s pushed to %s\n", p.Pusher.UserName, branchName))
|
|
for i, commit := range p.Commits {
|
|
body.WriteString(fmt.Sprintf("%s: %s", commit.ID[:7], strings.TrimRight(commit.Message, "\r\n")))
|
|
if commit.Author != nil {
|
|
body.WriteString(" - " + commit.Author.Name)
|
|
}
|
|
if i < len(p.Commits)-1 {
|
|
body.WriteString("\n")
|
|
}
|
|
}
|
|
|
|
return BarkPayload{
|
|
Title: title,
|
|
Body: body.String(),
|
|
URL: titleLink,
|
|
Group: bc.getGroup(p.Repo.FullName),
|
|
Sound: bc.Sound,
|
|
}, nil
|
|
}
|
|
|
|
// Issue implements PayloadConvertor Issue method
|
|
func (bc barkConvertor) Issue(p *api.IssuePayload) (BarkPayload, error) {
|
|
title := fmt.Sprintf("[%s] Issue #%d: %s", p.Repository.FullName, p.Index, p.Action)
|
|
body := fmt.Sprintf("%s %s issue #%d: %s", p.Sender.UserName, p.Action, p.Index, p.Issue.Title)
|
|
|
|
return BarkPayload{
|
|
Title: title,
|
|
Body: body,
|
|
URL: p.Issue.HTMLURL,
|
|
Group: bc.getGroup(p.Repository.FullName),
|
|
Sound: bc.Sound,
|
|
}, nil
|
|
}
|
|
|
|
// Wiki implements PayloadConvertor Wiki method
|
|
func (bc barkConvertor) Wiki(p *api.WikiPayload) (BarkPayload, error) {
|
|
title := fmt.Sprintf("[%s] Wiki %s", p.Repository.FullName, p.Action)
|
|
body := fmt.Sprintf("%s %s wiki page: %s", p.Sender.UserName, p.Action, p.Page)
|
|
wikiURL := p.Repository.HTMLURL + "/wiki/" + url.PathEscape(p.Page)
|
|
|
|
return BarkPayload{
|
|
Title: title,
|
|
Body: body,
|
|
URL: wikiURL,
|
|
Group: bc.getGroup(p.Repository.FullName),
|
|
Sound: bc.Sound,
|
|
}, nil
|
|
}
|
|
|
|
// IssueComment implements PayloadConvertor IssueComment method
|
|
func (bc barkConvertor) IssueComment(p *api.IssueCommentPayload) (BarkPayload, error) {
|
|
title := fmt.Sprintf("[%s] New comment on #%d", p.Repository.FullName, p.Issue.Index)
|
|
body := fmt.Sprintf("%s commented on issue #%d: %s\n%s",
|
|
p.Sender.UserName, p.Issue.Index, p.Issue.Title,
|
|
truncateString(p.Comment.Body, 100))
|
|
|
|
return BarkPayload{
|
|
Title: title,
|
|
Body: body,
|
|
URL: p.Comment.HTMLURL,
|
|
Group: bc.getGroup(p.Repository.FullName),
|
|
Sound: bc.Sound,
|
|
}, nil
|
|
}
|
|
|
|
// PullRequest implements PayloadConvertor PullRequest method
|
|
func (bc barkConvertor) PullRequest(p *api.PullRequestPayload) (BarkPayload, error) {
|
|
title := fmt.Sprintf("[%s] PR #%d: %s", p.Repository.FullName, p.Index, p.Action)
|
|
body := fmt.Sprintf("%s %s pull request #%d: %s",
|
|
p.Sender.UserName, p.Action, p.Index, p.PullRequest.Title)
|
|
|
|
return BarkPayload{
|
|
Title: title,
|
|
Body: body,
|
|
URL: p.PullRequest.HTMLURL,
|
|
Group: bc.getGroup(p.Repository.FullName),
|
|
Sound: bc.Sound,
|
|
}, nil
|
|
}
|
|
|
|
// Review implements PayloadConvertor Review method
|
|
func (bc barkConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (BarkPayload, error) {
|
|
var action string
|
|
switch p.Action {
|
|
case api.HookIssueReviewed:
|
|
var err error
|
|
action, err = parseHookPullRequestEventType(event)
|
|
if err != nil {
|
|
return BarkPayload{}, err
|
|
}
|
|
}
|
|
|
|
title := fmt.Sprintf("[%s] PR #%d review %s", p.Repository.FullName, p.Index, action)
|
|
body := fmt.Sprintf("PR #%d: %s", p.Index, p.PullRequest.Title)
|
|
if p.Review != nil && p.Review.Content != "" {
|
|
body += "\n" + truncateString(p.Review.Content, 100)
|
|
}
|
|
|
|
return BarkPayload{
|
|
Title: title,
|
|
Body: body,
|
|
URL: p.PullRequest.HTMLURL,
|
|
Group: bc.getGroup(p.Repository.FullName),
|
|
Sound: bc.Sound,
|
|
}, nil
|
|
}
|
|
|
|
// Repository implements PayloadConvertor Repository method
|
|
func (bc barkConvertor) Repository(p *api.RepositoryPayload) (BarkPayload, error) {
|
|
var title, body string
|
|
switch p.Action {
|
|
case api.HookRepoCreated:
|
|
title = fmt.Sprintf("[%s] Repository created", p.Repository.FullName)
|
|
body = p.Sender.UserName + " created repository"
|
|
case api.HookRepoDeleted:
|
|
title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName)
|
|
body = p.Sender.UserName + " deleted repository"
|
|
default:
|
|
return BarkPayload{}, nil
|
|
}
|
|
|
|
return BarkPayload{
|
|
Title: title,
|
|
Body: body,
|
|
URL: p.Repository.HTMLURL,
|
|
Group: bc.getGroup(p.Repository.FullName),
|
|
Sound: bc.Sound,
|
|
}, nil
|
|
}
|
|
|
|
// Release implements PayloadConvertor Release method
|
|
func (bc barkConvertor) Release(p *api.ReleasePayload) (BarkPayload, error) {
|
|
title := fmt.Sprintf("[%s] Release %s", p.Repository.FullName, p.Action)
|
|
body := fmt.Sprintf("%s %s release %s", p.Sender.UserName, p.Action, p.Release.TagName)
|
|
if p.Release.Title != "" {
|
|
body += ": " + p.Release.Title
|
|
}
|
|
|
|
return BarkPayload{
|
|
Title: title,
|
|
Body: body,
|
|
URL: p.Release.HTMLURL,
|
|
Group: bc.getGroup(p.Repository.FullName),
|
|
Sound: bc.Sound,
|
|
}, nil
|
|
}
|
|
|
|
// Package implements PayloadConvertor Package method
|
|
func (bc barkConvertor) Package(p *api.PackagePayload) (BarkPayload, error) {
|
|
repoFullName := ""
|
|
if p.Repository != nil {
|
|
repoFullName = p.Repository.FullName
|
|
}
|
|
|
|
title := fmt.Sprintf("[%s] Package %s", repoFullName, p.Action)
|
|
body := fmt.Sprintf("%s %s package %s:%s",
|
|
p.Sender.UserName, p.Action, p.Package.Name, p.Package.Version)
|
|
|
|
return BarkPayload{
|
|
Title: title,
|
|
Body: body,
|
|
URL: p.Package.HTMLURL,
|
|
Group: bc.getGroup(repoFullName),
|
|
Sound: bc.Sound,
|
|
}, nil
|
|
}
|
|
|
|
// Status implements PayloadConvertor Status method
|
|
func (bc barkConvertor) Status(p *api.CommitStatusPayload) (BarkPayload, error) {
|
|
title := fmt.Sprintf("[%s] Commit status: %s", p.Repo.FullName, p.State)
|
|
body := fmt.Sprintf("Commit %s: %s", base.ShortSha(p.SHA), p.Description)
|
|
if p.Context != "" {
|
|
body = fmt.Sprintf("%s (%s)", body, p.Context)
|
|
}
|
|
|
|
return BarkPayload{
|
|
Title: title,
|
|
Body: body,
|
|
URL: p.TargetURL,
|
|
Group: bc.getGroup(p.Repo.FullName),
|
|
Sound: bc.Sound,
|
|
}, nil
|
|
}
|
|
|
|
// WorkflowRun implements PayloadConvertor WorkflowRun method
|
|
func (bc barkConvertor) WorkflowRun(p *api.WorkflowRunPayload) (BarkPayload, error) {
|
|
title := fmt.Sprintf("[%s] Workflow %s", p.Repo.FullName, p.WorkflowRun.Status)
|
|
body := fmt.Sprintf("Workflow '%s' %s", p.WorkflowRun.DisplayTitle, p.WorkflowRun.Status)
|
|
|
|
return BarkPayload{
|
|
Title: title,
|
|
Body: body,
|
|
URL: p.WorkflowRun.HTMLURL,
|
|
Group: bc.getGroup(p.Repo.FullName),
|
|
Sound: bc.Sound,
|
|
}, nil
|
|
}
|
|
|
|
// WorkflowJob implements PayloadConvertor WorkflowJob method
|
|
func (bc barkConvertor) WorkflowJob(p *api.WorkflowJobPayload) (BarkPayload, error) {
|
|
title := fmt.Sprintf("[%s] Job %s", p.Repo.FullName, p.WorkflowJob.Status)
|
|
body := fmt.Sprintf("Job '%s' %s", p.WorkflowJob.Name, p.WorkflowJob.Status)
|
|
|
|
return BarkPayload{
|
|
Title: title,
|
|
Body: body,
|
|
URL: p.WorkflowJob.HTMLURL,
|
|
Group: bc.getGroup(p.Repo.FullName),
|
|
Sound: bc.Sound,
|
|
}, nil
|
|
}
|
|
|
|
// truncateString truncates a string to the specified length
|
|
func truncateString(s string, maxLen int) string {
|
|
if len(s) <= maxLen {
|
|
return s
|
|
}
|
|
return s[:maxLen] + "..."
|
|
}
|
|
|
|
func newBarkRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
|
meta := &BarkMeta{}
|
|
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
|
|
return nil, nil, fmt.Errorf("newBarkRequest meta json: %w", err)
|
|
}
|
|
var pc payloadConvertor[BarkPayload] = barkConvertor{
|
|
Sound: meta.Sound,
|
|
Group: meta.Group,
|
|
}
|
|
return newJSONRequest(pc, w, t, true)
|
|
}
|
|
|
|
func init() {
|
|
RegisterWebhookRequester(webhook_module.BARK, newBarkRequest)
|
|
}
|