diff --git a/modules/setting/webhook.go b/modules/setting/webhook.go index c01261dbbd6e9..17e490644323e 100644 --- a/modules/setting/webhook.go +++ b/modules/setting/webhook.go @@ -35,7 +35,7 @@ func loadWebhookFrom(rootCfg ConfigProvider) { Webhook.DeliverTimeout = sec.Key("DELIVER_TIMEOUT").MustInt(5) Webhook.SkipTLSVerify = sec.Key("SKIP_TLS_VERIFY").MustBool() Webhook.AllowedHostList = sec.Key("ALLOWED_HOST_LIST").MustString("") - Webhook.Types = []string{"gitea", "gogs", "slack", "discord", "dingtalk", "telegram", "msteams", "feishu", "matrix", "wechatwork", "packagist"} + Webhook.Types = []string{"gitea", "gogs", "slack", "discord", "dingtalk", "telegram", "msteams", "feishu", "matrix", "wechatwork", "packagist", "bark"} Webhook.PagingNum = sec.Key("PAGING_NUM").MustInt(10) Webhook.ProxyURL = sec.Key("PROXY_URL").MustString("") if Webhook.ProxyURL != "" { diff --git a/modules/webhook/type.go b/modules/webhook/type.go index 89c6a4bfe5907..3f8f763bd2cae 100644 --- a/modules/webhook/type.go +++ b/modules/webhook/type.go @@ -114,6 +114,7 @@ const ( MATRIX HookType = "matrix" WECHATWORK HookType = "wechatwork" PACKAGIST HookType = "packagist" + BARK HookType = "bark" ) // HookStatus is the status of a web hook diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 46fdf060229e8..e611512b3d3e1 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2467,9 +2467,14 @@ settings.web_hook_name_feishu = Feishu settings.web_hook_name_larksuite = Lark Suite settings.web_hook_name_wechatwork = WeCom (Wechat Work) settings.web_hook_name_packagist = Packagist +settings.web_hook_name_bark = Bark settings.packagist_username = Packagist username settings.packagist_api_token = API token settings.packagist_package_url = Packagist package URL +settings.bark_url = Bark URL +settings.bark_url_help = Full Bark URL including device key (e.g., https://api.day.app/your_device_key/) +settings.bark_sound = Sound (optional) +settings.bark_group = Group (optional) settings.deploy_keys = Deploy Keys settings.add_deploy_key = Add Deploy Key settings.deploy_key_desc = Deploy keys have read-only pull access to the repository. diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 29eb6be949a7f..d6804fd3c433c 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -2465,9 +2465,14 @@ settings.web_hook_name_feishu=飞书 settings.web_hook_name_larksuite=Lark Suite settings.web_hook_name_wechatwork=企业微信 settings.web_hook_name_packagist=Packagist +settings.web_hook_name_bark=Bark settings.packagist_username=Packagist 用户名 settings.packagist_api_token=API 令牌 settings.packagist_package_url=Packagist 软件包 URL +settings.bark_url=Bark URL +settings.bark_url_help=完整的 Bark URL,包含设备密钥(例如:https://api.day.app/your_device_key/) +settings.bark_sound=提示音(可选) +settings.bark_group=分组(可选) settings.deploy_keys=部署密钥 settings.add_deploy_key=添加部署密钥 settings.deploy_key_desc=部署密钥具有对仓库的只读拉取权限。 diff --git a/public/assets/img/bark.png b/public/assets/img/bark.png new file mode 100644 index 0000000000000..96728016cc45c Binary files /dev/null and b/public/assets/img/bark.png differ diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go index f107449749364..4ca2db3474707 100644 --- a/routers/web/repo/setting/webhook.go +++ b/routers/web/repo/setting/webhook.go @@ -581,6 +581,32 @@ func packagistHookParams(ctx *context.Context) webhookParams { } } +// BarkHooksNewPost response for creating Bark webhook +func BarkHooksNewPost(ctx *context.Context) { + createWebhook(ctx, barkHookParams(ctx)) +} + +// BarkHooksEditPost response for editing Bark webhook +func BarkHooksEditPost(ctx *context.Context) { + editWebhook(ctx, barkHookParams(ctx)) +} + +func barkHookParams(ctx *context.Context) webhookParams { + form := web.GetForm(ctx).(*forms.NewBarkHookForm) + + return webhookParams{ + Type: webhook_module.BARK, + URL: form.PayloadURL, + ContentType: webhook.ContentTypeJSON, + HTTPMethod: http.MethodPost, + WebhookForm: form.WebhookForm, + Meta: &webhook_service.BarkMeta{ + Sound: form.Sound, + Group: form.Group, + }, + } +} + func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) { orCtx, err := getOwnerRepoCtx(ctx) if err != nil { @@ -619,6 +645,8 @@ func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) { ctx.Data["MatrixHook"] = webhook_service.GetMatrixHook(w) case webhook_module.PACKAGIST: ctx.Data["PackagistHook"] = webhook_service.GetPackagistHook(w) + case webhook_module.BARK: + ctx.Data["BarkHook"] = webhook_service.GetBarkHook(w) } ctx.Data["History"], err = w.History(ctx, 1) diff --git a/routers/web/web.go b/routers/web/web.go index 9b3cfb6d1670a..ee2e1e97d043c 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -434,6 +434,7 @@ func registerWebRoutes(m *web.Router) { m.Post("/feishu/new", web.Bind(forms.NewFeishuHookForm{}), repo_setting.FeishuHooksNewPost) m.Post("/wechatwork/new", web.Bind(forms.NewWechatWorkHookForm{}), repo_setting.WechatworkHooksNewPost) m.Post("/packagist/new", web.Bind(forms.NewPackagistHookForm{}), repo_setting.PackagistHooksNewPost) + m.Post("/bark/new", web.Bind(forms.NewBarkHookForm{}), repo_setting.BarkHooksNewPost) } addWebhookEditRoutes := func() { @@ -448,6 +449,7 @@ func registerWebRoutes(m *web.Router) { m.Post("/feishu/{id}", web.Bind(forms.NewFeishuHookForm{}), repo_setting.FeishuHooksEditPost) m.Post("/wechatwork/{id}", web.Bind(forms.NewWechatWorkHookForm{}), repo_setting.WechatworkHooksEditPost) m.Post("/packagist/{id}", web.Bind(forms.NewPackagistHookForm{}), repo_setting.PackagistHooksEditPost) + m.Post("/bark/{id}", web.Bind(forms.NewBarkHookForm{}), repo_setting.BarkHooksEditPost) } addSettingsVariablesRoutes := func() { diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 67f24c4cbe0f7..e7adc6515458f 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -410,6 +410,20 @@ func (f *NewPackagistHookForm) Validate(req *http.Request, errs binding.Errors) return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } +// NewBarkHookForm form for creating Bark hook +type NewBarkHookForm struct { + PayloadURL string `binding:"Required;ValidUrl" form:"payload_url"` + Sound string `form:"sound"` + Group string `form:"group"` + WebhookForm +} + +// Validate validates the fields +func (f *NewBarkHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { + ctx := context.GetValidateContext(req) + return middleware.Validate(errs, ctx.Data, f, ctx.Locale) +} + // .___ // | | ______ ________ __ ____ // | |/ ___// ___/ | \_/ __ \ diff --git a/services/webhook/bark.go b/services/webhook/bark.go new file mode 100644 index 0000000000000..2742891aee771 --- /dev/null +++ b/services/webhook/bark.go @@ -0,0 +1,354 @@ +// 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) +} diff --git a/services/webhook/bark_test.go b/services/webhook/bark_test.go new file mode 100644 index 0000000000000..821f8974cc37b --- /dev/null +++ b/services/webhook/bark_test.go @@ -0,0 +1,215 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package webhook + +import ( + "testing" + + webhook_model "code.gitea.io/gitea/models/webhook" + api "code.gitea.io/gitea/modules/structs" + webhook_module "code.gitea.io/gitea/modules/webhook" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBarkPayload(t *testing.T) { + bc := barkConvertor{} + + t.Run("Create", func(t *testing.T) { + p := createTestPayload() + + pl, err := bc.Create(p) + require.NoError(t, err) + + assert.Equal(t, "[test/repo] branch test created", pl.Title) + assert.Equal(t, "user1 created branch test", pl.Body) + assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.URL) + assert.Equal(t, "test/repo", pl.Group) + }) + + t.Run("Delete", func(t *testing.T) { + p := deleteTestPayload() + + pl, err := bc.Delete(p) + require.NoError(t, err) + + assert.Equal(t, "[test/repo] branch test deleted", pl.Title) + assert.Equal(t, "user1 deleted branch test", pl.Body) + assert.Equal(t, "http://localhost:3000/test/repo", pl.URL) + assert.Equal(t, "test/repo", pl.Group) + }) + + t.Run("Fork", func(t *testing.T) { + p := forkTestPayload() + + pl, err := bc.Fork(p) + require.NoError(t, err) + + assert.Equal(t, "[test/repo2] Repository forked", pl.Title) + assert.Equal(t, "user1 forked test/repo2 to test/repo", pl.Body) + assert.Equal(t, "http://localhost:3000/test/repo", pl.URL) + assert.Equal(t, "test/repo2", pl.Group) + }) + + t.Run("Push", func(t *testing.T) { + p := pushTestPayload() + + pl, err := bc.Push(p) + require.NoError(t, err) + + assert.Equal(t, "[test/repo:test] 2 new commit(s)", pl.Title) + assert.Contains(t, pl.Body, "user1 pushed to test") + assert.Contains(t, pl.Body, "2020558: commit message - user1") + assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.URL) + assert.Equal(t, "test/repo", pl.Group) + }) + + t.Run("Issue", func(t *testing.T) { + p := issueTestPayload() + + p.Action = api.HookIssueOpened + pl, err := bc.Issue(p) + require.NoError(t, err) + + assert.Equal(t, "[test/repo] Issue #2: opened", pl.Title) + assert.Equal(t, "user1 opened issue #2: crash", pl.Body) + assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.URL) + assert.Equal(t, "test/repo", pl.Group) + + p.Action = api.HookIssueClosed + pl, err = bc.Issue(p) + require.NoError(t, err) + + assert.Equal(t, "[test/repo] Issue #2: closed", pl.Title) + assert.Equal(t, "user1 closed issue #2: crash", pl.Body) + }) + + t.Run("IssueComment", func(t *testing.T) { + p := issueCommentTestPayload() + + pl, err := bc.IssueComment(p) + require.NoError(t, err) + + assert.Equal(t, "[test/repo] New comment on #2", pl.Title) + assert.Contains(t, pl.Body, "user1 commented on issue #2: crash") + assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", pl.URL) + assert.Equal(t, "test/repo", pl.Group) + }) + + t.Run("PullRequest", func(t *testing.T) { + p := pullRequestTestPayload() + + pl, err := bc.PullRequest(p) + require.NoError(t, err) + + assert.Equal(t, "[test/repo] PR #12: opened", pl.Title) + assert.Equal(t, "user1 opened pull request #12: Fix bug", pl.Body) + assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.URL) + assert.Equal(t, "test/repo", pl.Group) + }) + + t.Run("PullRequestComment", func(t *testing.T) { + p := pullRequestCommentTestPayload() + + pl, err := bc.IssueComment(p) + require.NoError(t, err) + + assert.Equal(t, "[test/repo] New comment on #12", pl.Title) + assert.Contains(t, pl.Body, "user1 commented") + assert.Equal(t, "test/repo", pl.Group) + }) + + t.Run("Review", func(t *testing.T) { + p := pullRequestTestPayload() + p.Action = api.HookIssueReviewed + p.Review = nil // Remove review content for clean test + + pl, err := bc.Review(p, webhook_module.HookEventPullRequestReviewApproved) + require.NoError(t, err) + + assert.Equal(t, "[test/repo] PR #12 review approved", pl.Title) + assert.Equal(t, "PR #12: Fix bug", pl.Body) + assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.URL) + assert.Equal(t, "test/repo", pl.Group) + }) + + t.Run("Repository", func(t *testing.T) { + p := repositoryTestPayload() + + pl, err := bc.Repository(p) + require.NoError(t, err) + + assert.Equal(t, "[test/repo] Repository created", pl.Title) + assert.Equal(t, "user1 created repository", pl.Body) + assert.Equal(t, "http://localhost:3000/test/repo", pl.URL) + assert.Equal(t, "test/repo", pl.Group) + }) + + t.Run("Package", func(t *testing.T) { + p := packageTestPayload() + + pl, err := bc.Package(p) + require.NoError(t, err) + + assert.Equal(t, "[] Package created", pl.Title) + assert.Contains(t, pl.Body, "user1 created package") + assert.Empty(t, pl.Group) + }) + + t.Run("Wiki", func(t *testing.T) { + p := wikiTestPayload() + + p.Action = api.HookWikiCreated + pl, err := bc.Wiki(p) + require.NoError(t, err) + + assert.Equal(t, "[test/repo] Wiki created", pl.Title) + assert.Equal(t, "user1 created wiki page: index", pl.Body) + assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.URL) + assert.Equal(t, "test/repo", pl.Group) + }) + + t.Run("Release", func(t *testing.T) { + p := pullReleaseTestPayload() + + pl, err := bc.Release(p) + require.NoError(t, err) + + assert.Equal(t, "[test/repo] Release published", pl.Title) + assert.Contains(t, pl.Body, "user1 published release v1.0") + assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", pl.URL) + assert.Equal(t, "test/repo", pl.Group) + }) +} + +func TestBarkJSONPayload(t *testing.T) { + p := pushTestPayload() + data, err := p.JSONPayload() + require.NoError(t, err) + + hook := &webhook_model.Webhook{ + RepoID: 3, + IsActive: true, + Type: webhook_module.BARK, + URL: "https://api.day.app/devicekey/", + Meta: `{}`, + HTTPMethod: "POST", + } + task := &webhook_model.HookTask{ + HookID: hook.ID, + EventType: webhook_module.HookEventPush, + PayloadContent: string(data), + PayloadVersion: 2, + } + + req, reqBody, err := newBarkRequest(t.Context(), hook, task) + require.NotNil(t, req) + require.NotNil(t, reqBody) + require.NoError(t, err) + + assert.Equal(t, "POST", req.Method) + assert.Equal(t, "https://api.day.app/devicekey/", req.URL.String()) + assert.Equal(t, "application/json", req.Header.Get("Content-Type")) +} diff --git a/templates/repo/settings/webhook/bark.tmpl b/templates/repo/settings/webhook/bark.tmpl new file mode 100644 index 0000000000000..bf5b8aaf42729 --- /dev/null +++ b/templates/repo/settings/webhook/bark.tmpl @@ -0,0 +1,20 @@ +{{if eq .HookType "bark"}} +

{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://github.com/Finb/Bark" (ctx.Locale.Tr "repo.settings.web_hook_name_bark")}}

+
+ {{.CsrfTokenHtml}} +
+ + +

{{ctx.Locale.Tr "repo.settings.bark_url_help"}}

+
+
+ + +
+
+ + +
+ {{template "repo/settings/webhook/settings" dict "BaseLink" .BaseLink "Webhook" .Webhook "UseAuthorizationHeader" false}} +
+{{end}} diff --git a/templates/repo/settings/webhook/link_menu.tmpl b/templates/repo/settings/webhook/link_menu.tmpl index e2c86dcc3c089..51e2e242425d1 100644 --- a/templates/repo/settings/webhook/link_menu.tmpl +++ b/templates/repo/settings/webhook/link_menu.tmpl @@ -47,4 +47,8 @@ {{template "shared/webhook/icon" (dict "HookType" "packagist" "Size" $size)}} {{ctx.Locale.Tr "repo.settings.web_hook_name_packagist"}} + + {{template "shared/webhook/icon" (dict "HookType" "bark" "Size" $size)}} + {{ctx.Locale.Tr "repo.settings.web_hook_name_bark"}} + diff --git a/templates/shared/webhook/icon.tmpl b/templates/shared/webhook/icon.tmpl index 105212eb56928..c87b056be583c 100644 --- a/templates/shared/webhook/icon.tmpl +++ b/templates/shared/webhook/icon.tmpl @@ -24,4 +24,6 @@ {{else if eq .HookType "packagist"}} +{{else if eq .HookType "bark"}} + {{end}} diff --git a/templates/webhook/new.tmpl b/templates/webhook/new.tmpl index 8ef33df25b45b..7d75b1a5c4dc8 100644 --- a/templates/webhook/new.tmpl +++ b/templates/webhook/new.tmpl @@ -21,5 +21,6 @@ {{template "repo/settings/webhook/matrix" .ctxData}} {{template "repo/settings/webhook/wechatwork" .ctxData}} {{template "repo/settings/webhook/packagist" .ctxData}} + {{template "repo/settings/webhook/bark" .ctxData}} {{template "repo/settings/webhook/history" .ctxData}}