Files
Gitea/services/webhook/telegram.go
T
roman.s 38b07d0c29 feat(webhook): fire repository event on repo rename (#38641)
Repository create and delete already fire `repository` webhooks, rename
did not. This adds the `renamed` action with `changes.name.from`
carrying the previous name, and renders it in the chat converters.

Actions workflows are unaffected, they still do not trigger on rename.

AI assistance was used for the implementation and tests.

Fixes https://github.com/go-gitea/gitea/issues/34891.
Co-authored-by: roman s <roman.sukach@dust-labs.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-07-31 11:31:17 +00:00

227 lines
7.9 KiB
Go

// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package webhook
import (
"context"
"fmt"
"html"
"net/http"
"net/url"
"strings"
webhook_model "gitea.dev/models/webhook"
"gitea.dev/modules/git"
"gitea.dev/modules/json"
"gitea.dev/modules/log"
"gitea.dev/modules/markup"
api "gitea.dev/modules/structs"
"gitea.dev/modules/util"
webhook_module "gitea.dev/modules/webhook"
)
type (
// TelegramPayload represents
TelegramPayload struct {
RichMessage InputRichMessage `json:"rich_message"`
}
// InputRichMessage represents input rich message
InputRichMessage struct {
HTML string `json:"html"`
}
// TelegramMeta contains the telegram metadata
TelegramMeta struct {
BotToken string `json:"bot_token"`
ChatID string `json:"chat_id"`
ThreadID string `json:"thread_id"`
}
)
// GetTelegramHook returns telegram metadata
func GetTelegramHook(w *webhook_model.Webhook) *TelegramMeta {
s := &TelegramMeta{}
if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
log.Error("webhook.GetTelegramHook(%d): %v", w.ID, err)
}
return s
}
type telegramConvertor struct{}
// Create implements PayloadConvertor Create method
func (t telegramConvertor) Create(p *api.CreatePayload) (TelegramPayload, error) {
// created tag/branch
refName := git.RefName(p.Ref).ShortName()
title := fmt.Sprintf(`[%s] %s %s created`,
htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName),
html.EscapeString(p.RefType),
htmlLinkFormatter(p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName), refName),
)
return createTelegramPayloadHTML(title), nil
}
// Delete implements PayloadConvertor Delete method
func (t telegramConvertor) Delete(p *api.DeletePayload) (TelegramPayload, error) {
// created tag/branch
refName := git.RefName(p.Ref).ShortName()
title := fmt.Sprintf(`[%s] %s %s deleted`,
htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName),
html.EscapeString(p.RefType),
htmlLinkFormatter(p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName), refName),
)
return createTelegramPayloadHTML(title), nil
}
// Fork implements PayloadConvertor Fork method
func (t telegramConvertor) Fork(p *api.ForkPayload) (TelegramPayload, error) {
title := fmt.Sprintf(`%s is forked to %s`, html.EscapeString(p.Forkee.FullName), htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName))
return createTelegramPayloadHTML(title), nil
}
// Push implements PayloadConvertor Push method
func (t telegramConvertor) Push(p *api.PushPayload) (TelegramPayload, error) {
branchName := git.RefName(p.Ref).ShortName()
var titleLink, commitDesc string
if p.TotalCommits == 1 {
commitDesc = "1 new commit"
titleLink = p.Commits[0].URL
} else {
commitDesc = fmt.Sprintf("%d new commits", p.TotalCommits)
titleLink = p.CompareURL
}
if titleLink == "" {
titleLink = p.Repo.HTMLURL + "/src/" + util.PathEscapeSegments(branchName)
}
title := fmt.Sprintf(`[%s:%s] %s`, htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName), htmlLinkFormatter(titleLink, branchName), html.EscapeString(commitDesc))
var htmlCommits strings.Builder
for _, commit := range p.Commits {
fmt.Fprintf(&htmlCommits, "\n[%s] %s", htmlLinkFormatter(commit.URL, commit.ID[:7]), html.EscapeString(strings.TrimRight(commit.Message, "\r\n")))
if commit.Author != nil {
htmlCommits.WriteString(" - " + html.EscapeString(commit.Author.Name))
}
}
return createTelegramPayloadHTML(title + htmlCommits.String()), nil
}
// Issue implements PayloadConvertor Issue method
func (t telegramConvertor) Issue(p *api.IssuePayload) (TelegramPayload, error) {
text, _, extraMarkdown, _ := getIssuesPayloadInfo(p, htmlLinkFormatter, true)
// TODO: at the moment the markdown can't be rendered easily because it has context-aware links (eg: attachments)
return createTelegramPayloadHTML(text + "\n\n" + html.EscapeString(extraMarkdown)), nil
}
// IssueComment implements PayloadConvertor IssueComment method
func (t telegramConvertor) IssueComment(p *api.IssueCommentPayload) (TelegramPayload, error) {
text, _, _ := getIssueCommentPayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayloadHTML(text + "\n" + html.EscapeString(p.Comment.Body)), nil
}
// PullRequest implements PayloadConvertor PullRequest method
func (t telegramConvertor) PullRequest(p *api.PullRequestPayload) (TelegramPayload, error) {
text, _, extraMarkdown, _ := getPullRequestPayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayloadHTML(text + "\n" + html.EscapeString(extraMarkdown)), nil
}
// Review implements PayloadConvertor Review method
func (t telegramConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (TelegramPayload, error) {
var text, extraMarkdown string
switch p.Action {
case api.HookIssueReviewed:
action, err := parseHookPullRequestEventType(event)
if err != nil {
return TelegramPayload{}, err
}
text = fmt.Sprintf("[%s] Pull request review %s: #%d %s", html.EscapeString(p.Repository.FullName), html.EscapeString(action), p.Index, html.EscapeString(p.PullRequest.Title))
extraMarkdown = p.Review.Content
}
return createTelegramPayloadHTML(text + "\n" + html.EscapeString(extraMarkdown)), nil
}
// Repository implements PayloadConvertor Repository method
func (t telegramConvertor) Repository(p *api.RepositoryPayload) (TelegramPayload, error) {
var title string
switch p.Action {
case api.HookRepoCreated:
title = fmt.Sprintf(`[%s] Repository created`, htmlLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName))
return createTelegramPayloadHTML(title), nil
case api.HookRepoDeleted:
title = fmt.Sprintf("[%s] Repository deleted", html.EscapeString(p.Repository.FullName))
return createTelegramPayloadHTML(title), nil
case api.HookRepoRenamed:
title = fmt.Sprintf("[%s] Repository renamed from %s", htmlLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName), html.EscapeString(getRepoRenamedFrom(p)))
return createTelegramPayloadHTML(title), nil
}
return TelegramPayload{}, nil
}
// Wiki implements PayloadConvertor Wiki method
func (t telegramConvertor) Wiki(p *api.WikiPayload) (TelegramPayload, error) {
text, _, _ := getWikiPayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayloadHTML(text), nil
}
// Release implements PayloadConvertor Release method
func (t telegramConvertor) Release(p *api.ReleasePayload) (TelegramPayload, error) {
text, _ := getReleasePayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayloadHTML(text), nil
}
func (t telegramConvertor) Package(p *api.PackagePayload) (TelegramPayload, error) {
text, _ := getPackagePayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayloadHTML(text), nil
}
func (t telegramConvertor) Status(p *api.CommitStatusPayload) (TelegramPayload, error) {
text, _ := getStatusPayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayloadHTML(text), nil
}
func (telegramConvertor) WorkflowRun(p *api.WorkflowRunPayload) (TelegramPayload, error) {
text, _ := getWorkflowRunPayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayloadHTML(text), nil
}
func (telegramConvertor) WorkflowJob(p *api.WorkflowJobPayload) (TelegramPayload, error) {
text, _ := getWorkflowJobPayloadInfo(p, htmlLinkFormatter, true)
return createTelegramPayloadHTML(text), nil
}
func createTelegramPayloadHTML(msgHTML string) TelegramPayload {
// https://core.telegram.org/bots/api#formatting-options
return TelegramPayload{
RichMessage: InputRichMessage{
HTML: strings.TrimSpace(string(markup.Sanitize(msgHTML))),
},
}
}
func newTelegramRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
u, err := url.Parse(w.URL)
if err != nil {
return nil, nil, err
}
if urlPrefix, ok := strings.CutSuffix(u.Path, "/sendMessage"); ok {
u.Path = urlPrefix + "/sendRichMessage"
w.URL = u.String()
}
var pc payloadConvertor[TelegramPayload] = telegramConvertor{}
return newJSONRequest(pc, w, t, true)
}
func init() {
RegisterWebhookRequester(webhook_module.TELEGRAM, newTelegramRequest)
}