From a966a0298ea1a545c383541ca4e72c61de1ed59e Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Mon, 4 Nov 2019 06:13:25 +0800
Subject: [PATCH] Move more webhook codes from models to webhook module (#8802)

* Move more webhook codes from models to webhook module
---
 models/webhook.go                             | 27 ----------
 models/webhook_test.go                        | 12 -----
 .../webhook/dingtalk.go                       | 29 +++++-----
 .../webhook/discord.go                        | 53 +++++++++++--------
 .../webhook/msteams.go                        | 35 ++++++------
 .../webhook/slack.go                          | 39 +++++++++-----
 .../webhook/telegram.go                       | 37 ++++++++-----
 modules/webhook/webhook.go                    | 10 ++--
 modules/webhook/webhook_test.go               | 12 +++++
 routers/api/v1/convert/convert.go             |  3 +-
 routers/api/v1/utils/hook.go                  |  5 +-
 routers/repo/webhook.go                       | 18 +++----
 12 files changed, 145 insertions(+), 135 deletions(-)
 rename models/webhook_dingtalk.go => modules/webhook/dingtalk.go (95%)
 rename models/webhook_discord.go => modules/webhook/discord.go (92%)
 rename models/webhook_msteams.go => modules/webhook/msteams.go (96%)
 rename models/webhook_slack.go => modules/webhook/slack.go (93%)
 rename models/webhook_telegram.go => modules/webhook/telegram.go (93%)

diff --git a/models/webhook.go b/models/webhook.go
index d3a8b52d86..7eb17caaf6 100644
--- a/models/webhook.go
+++ b/models/webhook.go
@@ -118,33 +118,6 @@ func (w *Webhook) AfterLoad() {
 	}
 }
 
-// GetSlackHook returns slack metadata
-func (w *Webhook) GetSlackHook() *SlackMeta {
-	s := &SlackMeta{}
-	if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
-		log.Error("webhook.GetSlackHook(%d): %v", w.ID, err)
-	}
-	return s
-}
-
-// GetDiscordHook returns discord metadata
-func (w *Webhook) GetDiscordHook() *DiscordMeta {
-	s := &DiscordMeta{}
-	if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
-		log.Error("webhook.GetDiscordHook(%d): %v", w.ID, err)
-	}
-	return s
-}
-
-// GetTelegramHook returns telegram metadata
-func (w *Webhook) GetTelegramHook() *TelegramMeta {
-	s := &TelegramMeta{}
-	if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
-		log.Error("webhook.GetTelegramHook(%d): %v", w.ID, err)
-	}
-	return s
-}
-
 // History returns history of webhook by given conditions.
 func (w *Webhook) History(page int) ([]*HookTask, error) {
 	return HookTasks(w.ID, page)
diff --git a/models/webhook_test.go b/models/webhook_test.go
index 7bdaadc5ae..0fd9b245ca 100644
--- a/models/webhook_test.go
+++ b/models/webhook_test.go
@@ -24,18 +24,6 @@ func TestIsValidHookContentType(t *testing.T) {
 	assert.False(t, IsValidHookContentType("invalid"))
 }
 
-func TestWebhook_GetSlackHook(t *testing.T) {
-	w := &Webhook{
-		Meta: `{"channel": "foo", "username": "username", "color": "blue"}`,
-	}
-	slackHook := w.GetSlackHook()
-	assert.Equal(t, *slackHook, SlackMeta{
-		Channel:  "foo",
-		Username: "username",
-		Color:    "blue",
-	})
-}
-
 func TestWebhook_History(t *testing.T) {
 	assert.NoError(t, PrepareTestDatabase())
 	webhook := AssertExistsAndLoadBean(t, &Webhook{ID: 1}).(*Webhook)
diff --git a/models/webhook_dingtalk.go b/modules/webhook/dingtalk.go
similarity index 95%
rename from models/webhook_dingtalk.go
rename to modules/webhook/dingtalk.go
index 1c6c0a83b8..b6d58f55cf 100644
--- a/models/webhook_dingtalk.go
+++ b/modules/webhook/dingtalk.go
@@ -2,13 +2,14 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package webhook
 
 import (
 	"encoding/json"
 	"fmt"
 	"strings"
 
+	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/git"
 	api "code.gitea.io/gitea/modules/structs"
 
@@ -184,7 +185,7 @@ func getDingtalkIssuesPayload(p *api.IssuePayload) (*DingtalkPayload, error) {
 
 func getDingtalkIssueCommentPayload(p *api.IssueCommentPayload) (*DingtalkPayload, error) {
 	title := fmt.Sprintf("#%d: %s", p.Issue.Index, p.Issue.Title)
-	url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID))
+	url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, models.CommentHashTag(p.Comment.ID))
 	var content string
 	switch p.Action {
 	case api.HookIssueCommentCreated:
@@ -286,7 +287,7 @@ func getDingtalkPullRequestPayload(p *api.PullRequestPayload) (*DingtalkPayload,
 	}, nil
 }
 
-func getDingtalkPullRequestApprovalPayload(p *api.PullRequestPayload, event HookEventType) (*DingtalkPayload, error) {
+func getDingtalkPullRequestApprovalPayload(p *api.PullRequestPayload, event models.HookEventType) (*DingtalkPayload, error) {
 	var text, title string
 	switch p.Action {
 	case api.HookIssueSynchronized:
@@ -392,29 +393,29 @@ func getDingtalkReleasePayload(p *api.ReleasePayload) (*DingtalkPayload, error)
 }
 
 // GetDingtalkPayload converts a ding talk webhook into a DingtalkPayload
-func GetDingtalkPayload(p api.Payloader, event HookEventType, meta string) (*DingtalkPayload, error) {
+func GetDingtalkPayload(p api.Payloader, event models.HookEventType, meta string) (*DingtalkPayload, error) {
 	s := new(DingtalkPayload)
 
 	switch event {
-	case HookEventCreate:
+	case models.HookEventCreate:
 		return getDingtalkCreatePayload(p.(*api.CreatePayload))
-	case HookEventDelete:
+	case models.HookEventDelete:
 		return getDingtalkDeletePayload(p.(*api.DeletePayload))
-	case HookEventFork:
+	case models.HookEventFork:
 		return getDingtalkForkPayload(p.(*api.ForkPayload))
-	case HookEventIssues:
+	case models.HookEventIssues:
 		return getDingtalkIssuesPayload(p.(*api.IssuePayload))
-	case HookEventIssueComment:
+	case models.HookEventIssueComment:
 		return getDingtalkIssueCommentPayload(p.(*api.IssueCommentPayload))
-	case HookEventPush:
+	case models.HookEventPush:
 		return getDingtalkPushPayload(p.(*api.PushPayload))
-	case HookEventPullRequest:
+	case models.HookEventPullRequest:
 		return getDingtalkPullRequestPayload(p.(*api.PullRequestPayload))
-	case HookEventPullRequestApproved, HookEventPullRequestRejected, HookEventPullRequestComment:
+	case models.HookEventPullRequestApproved, models.HookEventPullRequestRejected, models.HookEventPullRequestComment:
 		return getDingtalkPullRequestApprovalPayload(p.(*api.PullRequestPayload), event)
-	case HookEventRepository:
+	case models.HookEventRepository:
 		return getDingtalkRepositoryPayload(p.(*api.RepositoryPayload))
-	case HookEventRelease:
+	case models.HookEventRelease:
 		return getDingtalkReleasePayload(p.(*api.ReleasePayload))
 	}
 
diff --git a/models/webhook_discord.go b/modules/webhook/discord.go
similarity index 92%
rename from models/webhook_discord.go
rename to modules/webhook/discord.go
index 32039edc9d..f92157a1ab 100644
--- a/models/webhook_discord.go
+++ b/modules/webhook/discord.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package webhook
 
 import (
 	"encoding/json"
@@ -11,7 +11,9 @@ import (
 	"strconv"
 	"strings"
 
+	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
 )
@@ -63,6 +65,15 @@ type (
 	}
 )
 
+// GetDiscordHook returns discord metadata
+func GetDiscordHook(w *models.Webhook) *DiscordMeta {
+	s := &DiscordMeta{}
+	if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
+		log.Error("webhook.GetDiscordHook(%d): %v", w.ID, err)
+	}
+	return s
+}
+
 func color(clr string) int {
 	if clr != "" {
 		clr = strings.TrimLeft(clr, "#")
@@ -288,7 +299,7 @@ func getDiscordIssuesPayload(p *api.IssuePayload, meta *DiscordMeta) (*DiscordPa
 
 func getDiscordIssueCommentPayload(p *api.IssueCommentPayload, discord *DiscordMeta) (*DiscordPayload, error) {
 	title := fmt.Sprintf("#%d: %s", p.Issue.Index, p.Issue.Title)
-	url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID))
+	url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, models.CommentHashTag(p.Comment.ID))
 	content := ""
 	var color int
 	switch p.Action {
@@ -421,7 +432,7 @@ func getDiscordPullRequestPayload(p *api.PullRequestPayload, meta *DiscordMeta)
 	}, nil
 }
 
-func getDiscordPullRequestApprovalPayload(p *api.PullRequestPayload, meta *DiscordMeta, event HookEventType) (*DiscordPayload, error) {
+func getDiscordPullRequestApprovalPayload(p *api.PullRequestPayload, meta *DiscordMeta, event models.HookEventType) (*DiscordPayload, error) {
 	var text, title string
 	var color int
 	switch p.Action {
@@ -435,11 +446,11 @@ func getDiscordPullRequestApprovalPayload(p *api.PullRequestPayload, meta *Disco
 		text = p.Review.Content
 
 		switch event {
-		case HookEventPullRequestApproved:
+		case models.HookEventPullRequestApproved:
 			color = greenColor
-		case HookEventPullRequestRejected:
+		case models.HookEventPullRequestRejected:
 			color = redColor
-		case HookEventPullRequestComment:
+		case models.HookEventPullRequestComment:
 			color = greyColor
 		default:
 			color = yellowColor
@@ -534,7 +545,7 @@ func getDiscordReleasePayload(p *api.ReleasePayload, meta *DiscordMeta) (*Discor
 }
 
 // GetDiscordPayload converts a discord webhook into a DiscordPayload
-func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*DiscordPayload, error) {
+func GetDiscordPayload(p api.Payloader, event models.HookEventType, meta string) (*DiscordPayload, error) {
 	s := new(DiscordPayload)
 
 	discord := &DiscordMeta{}
@@ -543,40 +554,40 @@ func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*Disc
 	}
 
 	switch event {
-	case HookEventCreate:
+	case models.HookEventCreate:
 		return getDiscordCreatePayload(p.(*api.CreatePayload), discord)
-	case HookEventDelete:
+	case models.HookEventDelete:
 		return getDiscordDeletePayload(p.(*api.DeletePayload), discord)
-	case HookEventFork:
+	case models.HookEventFork:
 		return getDiscordForkPayload(p.(*api.ForkPayload), discord)
-	case HookEventIssues:
+	case models.HookEventIssues:
 		return getDiscordIssuesPayload(p.(*api.IssuePayload), discord)
-	case HookEventIssueComment:
+	case models.HookEventIssueComment:
 		return getDiscordIssueCommentPayload(p.(*api.IssueCommentPayload), discord)
-	case HookEventPush:
+	case models.HookEventPush:
 		return getDiscordPushPayload(p.(*api.PushPayload), discord)
-	case HookEventPullRequest:
+	case models.HookEventPullRequest:
 		return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord)
-	case HookEventPullRequestRejected, HookEventPullRequestApproved, HookEventPullRequestComment:
+	case models.HookEventPullRequestRejected, models.HookEventPullRequestApproved, models.HookEventPullRequestComment:
 		return getDiscordPullRequestApprovalPayload(p.(*api.PullRequestPayload), discord, event)
-	case HookEventRepository:
+	case models.HookEventRepository:
 		return getDiscordRepositoryPayload(p.(*api.RepositoryPayload), discord)
-	case HookEventRelease:
+	case models.HookEventRelease:
 		return getDiscordReleasePayload(p.(*api.ReleasePayload), discord)
 	}
 
 	return s, nil
 }
 
-func parseHookPullRequestEventType(event HookEventType) (string, error) {
+func parseHookPullRequestEventType(event models.HookEventType) (string, error) {
 
 	switch event {
 
-	case HookEventPullRequestApproved:
+	case models.HookEventPullRequestApproved:
 		return "approved", nil
-	case HookEventPullRequestRejected:
+	case models.HookEventPullRequestRejected:
 		return "rejected", nil
-	case HookEventPullRequestComment:
+	case models.HookEventPullRequestComment:
 		return "comment", nil
 
 	default:
diff --git a/models/webhook_msteams.go b/modules/webhook/msteams.go
similarity index 96%
rename from models/webhook_msteams.go
rename to modules/webhook/msteams.go
index e8cdcca3ca..2636e29983 100644
--- a/models/webhook_msteams.go
+++ b/modules/webhook/msteams.go
@@ -2,13 +2,14 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package webhook
 
 import (
 	"encoding/json"
 	"fmt"
 	"strings"
 
+	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/git"
 	api "code.gitea.io/gitea/modules/structs"
 )
@@ -357,7 +358,7 @@ func getMSTeamsIssuesPayload(p *api.IssuePayload) (*MSTeamsPayload, error) {
 
 func getMSTeamsIssueCommentPayload(p *api.IssueCommentPayload) (*MSTeamsPayload, error) {
 	title := fmt.Sprintf("#%d: %s", p.Issue.Index, p.Issue.Title)
-	url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID))
+	url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, models.CommentHashTag(p.Comment.ID))
 	content := ""
 	var color int
 	switch p.Action {
@@ -530,7 +531,7 @@ func getMSTeamsPullRequestPayload(p *api.PullRequestPayload) (*MSTeamsPayload, e
 	}, nil
 }
 
-func getMSTeamsPullRequestApprovalPayload(p *api.PullRequestPayload, event HookEventType) (*MSTeamsPayload, error) {
+func getMSTeamsPullRequestApprovalPayload(p *api.PullRequestPayload, event models.HookEventType) (*MSTeamsPayload, error) {
 	var text, title string
 	var color int
 	switch p.Action {
@@ -544,11 +545,11 @@ func getMSTeamsPullRequestApprovalPayload(p *api.PullRequestPayload, event HookE
 		text = p.Review.Content
 
 		switch event {
-		case HookEventPullRequestApproved:
+		case models.HookEventPullRequestApproved:
 			color = greenColor
-		case HookEventPullRequestRejected:
+		case models.HookEventPullRequestRejected:
 			color = redColor
-		case HookEventPullRequestComment:
+		case models.HookEventPullRequestComment:
 			color = greyColor
 		default:
 			color = yellowColor
@@ -699,29 +700,29 @@ func getMSTeamsReleasePayload(p *api.ReleasePayload) (*MSTeamsPayload, error) {
 }
 
 // GetMSTeamsPayload converts a MSTeams webhook into a MSTeamsPayload
-func GetMSTeamsPayload(p api.Payloader, event HookEventType, meta string) (*MSTeamsPayload, error) {
+func GetMSTeamsPayload(p api.Payloader, event models.HookEventType, meta string) (*MSTeamsPayload, error) {
 	s := new(MSTeamsPayload)
 
 	switch event {
-	case HookEventCreate:
+	case models.HookEventCreate:
 		return getMSTeamsCreatePayload(p.(*api.CreatePayload))
-	case HookEventDelete:
+	case models.HookEventDelete:
 		return getMSTeamsDeletePayload(p.(*api.DeletePayload))
-	case HookEventFork:
+	case models.HookEventFork:
 		return getMSTeamsForkPayload(p.(*api.ForkPayload))
-	case HookEventIssues:
+	case models.HookEventIssues:
 		return getMSTeamsIssuesPayload(p.(*api.IssuePayload))
-	case HookEventIssueComment:
+	case models.HookEventIssueComment:
 		return getMSTeamsIssueCommentPayload(p.(*api.IssueCommentPayload))
-	case HookEventPush:
+	case models.HookEventPush:
 		return getMSTeamsPushPayload(p.(*api.PushPayload))
-	case HookEventPullRequest:
+	case models.HookEventPullRequest:
 		return getMSTeamsPullRequestPayload(p.(*api.PullRequestPayload))
-	case HookEventPullRequestRejected, HookEventPullRequestApproved, HookEventPullRequestComment:
+	case models.HookEventPullRequestRejected, models.HookEventPullRequestApproved, models.HookEventPullRequestComment:
 		return getMSTeamsPullRequestApprovalPayload(p.(*api.PullRequestPayload), event)
-	case HookEventRepository:
+	case models.HookEventRepository:
 		return getMSTeamsRepositoryPayload(p.(*api.RepositoryPayload))
-	case HookEventRelease:
+	case models.HookEventRelease:
 		return getMSTeamsReleasePayload(p.(*api.ReleasePayload))
 	}
 
diff --git a/models/webhook_slack.go b/modules/webhook/slack.go
similarity index 93%
rename from models/webhook_slack.go
rename to modules/webhook/slack.go
index 9c179bb24a..7d844bfa50 100644
--- a/models/webhook_slack.go
+++ b/modules/webhook/slack.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package webhook
 
 import (
 	"encoding/json"
@@ -10,7 +10,9 @@ import (
 	"fmt"
 	"strings"
 
+	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
 )
@@ -23,6 +25,15 @@ type SlackMeta struct {
 	Color    string `json:"color"`
 }
 
+// GetSlackHook returns slack metadata
+func GetSlackHook(w *models.Webhook) *SlackMeta {
+	s := &SlackMeta{}
+	if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
+		log.Error("webhook.GetSlackHook(%d): %v", w.ID, err)
+	}
+	return s
+}
+
 // SlackPayload contains the information about the slack channel
 type SlackPayload struct {
 	Channel     string            `json:"channel"`
@@ -181,7 +192,7 @@ func getSlackIssuesPayload(p *api.IssuePayload, slack *SlackMeta) (*SlackPayload
 
 func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (*SlackPayload, error) {
 	senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
-	titleLink := SlackLinkFormatter(fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)),
+	titleLink := SlackLinkFormatter(fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, models.CommentHashTag(p.Comment.ID)),
 		fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title))
 	var text, title, attachmentText string
 	switch p.Action {
@@ -335,7 +346,7 @@ func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*S
 	}, nil
 }
 
-func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackMeta, event HookEventType) (*SlackPayload, error) {
+func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackMeta, event models.HookEventType) (*SlackPayload, error) {
 	senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
 	titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index),
 		fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title))
@@ -388,7 +399,7 @@ func getSlackRepositoryPayload(p *api.RepositoryPayload, slack *SlackMeta) (*Sla
 }
 
 // GetSlackPayload converts a slack webhook into a SlackPayload
-func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (*SlackPayload, error) {
+func GetSlackPayload(p api.Payloader, event models.HookEventType, meta string) (*SlackPayload, error) {
 	s := new(SlackPayload)
 
 	slack := &SlackMeta{}
@@ -397,25 +408,25 @@ func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (*SlackP
 	}
 
 	switch event {
-	case HookEventCreate:
+	case models.HookEventCreate:
 		return getSlackCreatePayload(p.(*api.CreatePayload), slack)
-	case HookEventDelete:
+	case models.HookEventDelete:
 		return getSlackDeletePayload(p.(*api.DeletePayload), slack)
-	case HookEventFork:
+	case models.HookEventFork:
 		return getSlackForkPayload(p.(*api.ForkPayload), slack)
-	case HookEventIssues:
+	case models.HookEventIssues:
 		return getSlackIssuesPayload(p.(*api.IssuePayload), slack)
-	case HookEventIssueComment:
+	case models.HookEventIssueComment:
 		return getSlackIssueCommentPayload(p.(*api.IssueCommentPayload), slack)
-	case HookEventPush:
+	case models.HookEventPush:
 		return getSlackPushPayload(p.(*api.PushPayload), slack)
-	case HookEventPullRequest:
+	case models.HookEventPullRequest:
 		return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack)
-	case HookEventPullRequestRejected, HookEventPullRequestApproved, HookEventPullRequestComment:
+	case models.HookEventPullRequestRejected, models.HookEventPullRequestApproved, models.HookEventPullRequestComment:
 		return getSlackPullRequestApprovalPayload(p.(*api.PullRequestPayload), slack, event)
-	case HookEventRepository:
+	case models.HookEventRepository:
 		return getSlackRepositoryPayload(p.(*api.RepositoryPayload), slack)
-	case HookEventRelease:
+	case models.HookEventRelease:
 		return getSlackReleasePayload(p.(*api.ReleasePayload), slack)
 	}
 
diff --git a/models/webhook_telegram.go b/modules/webhook/telegram.go
similarity index 93%
rename from models/webhook_telegram.go
rename to modules/webhook/telegram.go
index ead669dd08..4e67b22954 100644
--- a/models/webhook_telegram.go
+++ b/modules/webhook/telegram.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package webhook
 
 import (
 	"encoding/json"
@@ -10,7 +10,9 @@ import (
 	"html"
 	"strings"
 
+	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/markup"
 	api "code.gitea.io/gitea/modules/structs"
 )
@@ -30,6 +32,15 @@ type (
 	}
 )
 
+// GetTelegramHook returns telegram metadata
+func GetTelegramHook(w *models.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
+}
+
 // SetSecret sets the telegram secret
 func (p *TelegramPayload) SetSecret(_ string) {}
 
@@ -169,7 +180,7 @@ func getTelegramIssuesPayload(p *api.IssuePayload) (*TelegramPayload, error) {
 }
 
 func getTelegramIssueCommentPayload(p *api.IssueCommentPayload) (*TelegramPayload, error) {
-	url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID))
+	url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, models.CommentHashTag(p.Comment.ID))
 	title := fmt.Sprintf(`<a href="%s">#%d %s</a>`, url, p.Issue.Index, html.EscapeString(p.Issue.Title))
 	var text string
 	switch p.Action {
@@ -214,7 +225,7 @@ func getTelegramPullRequestPayload(p *api.PullRequestPayload) (*TelegramPayload,
 			p.PullRequest.HTMLURL, p.Index, p.PullRequest.Title)
 		text = p.PullRequest.Body
 	case api.HookIssueAssigned:
-		list, err := MakeAssigneeList(&Issue{ID: p.PullRequest.ID})
+		list, err := models.MakeAssigneeList(&models.Issue{ID: p.PullRequest.ID})
 		if err != nil {
 			return &TelegramPayload{}, err
 		}
@@ -297,27 +308,27 @@ func getTelegramReleasePayload(p *api.ReleasePayload) (*TelegramPayload, error)
 }
 
 // GetTelegramPayload converts a telegram webhook into a TelegramPayload
-func GetTelegramPayload(p api.Payloader, event HookEventType, meta string) (*TelegramPayload, error) {
+func GetTelegramPayload(p api.Payloader, event models.HookEventType, meta string) (*TelegramPayload, error) {
 	s := new(TelegramPayload)
 
 	switch event {
-	case HookEventCreate:
+	case models.HookEventCreate:
 		return getTelegramCreatePayload(p.(*api.CreatePayload))
-	case HookEventDelete:
+	case models.HookEventDelete:
 		return getTelegramDeletePayload(p.(*api.DeletePayload))
-	case HookEventFork:
+	case models.HookEventFork:
 		return getTelegramForkPayload(p.(*api.ForkPayload))
-	case HookEventIssues:
+	case models.HookEventIssues:
 		return getTelegramIssuesPayload(p.(*api.IssuePayload))
-	case HookEventIssueComment:
+	case models.HookEventIssueComment:
 		return getTelegramIssueCommentPayload(p.(*api.IssueCommentPayload))
-	case HookEventPush:
+	case models.HookEventPush:
 		return getTelegramPushPayload(p.(*api.PushPayload))
-	case HookEventPullRequest:
+	case models.HookEventPullRequest:
 		return getTelegramPullRequestPayload(p.(*api.PullRequestPayload))
-	case HookEventRepository:
+	case models.HookEventRepository:
 		return getTelegramRepositoryPayload(p.(*api.RepositoryPayload))
-	case HookEventRelease:
+	case models.HookEventRelease:
 		return getTelegramReleasePayload(p.(*api.ReleasePayload))
 	}
 
diff --git a/modules/webhook/webhook.go b/modules/webhook/webhook.go
index 623a475df9..410e47461f 100644
--- a/modules/webhook/webhook.go
+++ b/modules/webhook/webhook.go
@@ -90,27 +90,27 @@ func prepareWebhook(w *models.Webhook, repo *models.Repository, event models.Hoo
 	// Use separate objects so modifications won't be made on payload on non-Gogs/Gitea type hooks.
 	switch w.HookTaskType {
 	case models.SLACK:
-		payloader, err = models.GetSlackPayload(p, event, w.Meta)
+		payloader, err = GetSlackPayload(p, event, w.Meta)
 		if err != nil {
 			return fmt.Errorf("GetSlackPayload: %v", err)
 		}
 	case models.DISCORD:
-		payloader, err = models.GetDiscordPayload(p, event, w.Meta)
+		payloader, err = GetDiscordPayload(p, event, w.Meta)
 		if err != nil {
 			return fmt.Errorf("GetDiscordPayload: %v", err)
 		}
 	case models.DINGTALK:
-		payloader, err = models.GetDingtalkPayload(p, event, w.Meta)
+		payloader, err = GetDingtalkPayload(p, event, w.Meta)
 		if err != nil {
 			return fmt.Errorf("GetDingtalkPayload: %v", err)
 		}
 	case models.TELEGRAM:
-		payloader, err = models.GetTelegramPayload(p, event, w.Meta)
+		payloader, err = GetTelegramPayload(p, event, w.Meta)
 		if err != nil {
 			return fmt.Errorf("GetTelegramPayload: %v", err)
 		}
 	case models.MSTEAMS:
-		payloader, err = models.GetMSTeamsPayload(p, event, w.Meta)
+		payloader, err = GetMSTeamsPayload(p, event, w.Meta)
 		if err != nil {
 			return fmt.Errorf("GetMSTeamsPayload: %v", err)
 		}
diff --git a/modules/webhook/webhook_test.go b/modules/webhook/webhook_test.go
index c944bc477d..e88e67e9bf 100644
--- a/modules/webhook/webhook_test.go
+++ b/modules/webhook/webhook_test.go
@@ -12,6 +12,18 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+func TestWebhook_GetSlackHook(t *testing.T) {
+	w := &models.Webhook{
+		Meta: `{"channel": "foo", "username": "username", "color": "blue"}`,
+	}
+	slackHook := GetSlackHook(w)
+	assert.Equal(t, *slackHook, SlackMeta{
+		Channel:  "foo",
+		Username: "username",
+		Color:    "blue",
+	})
+}
+
 func TestPrepareWebhooks(t *testing.T) {
 	assert.NoError(t, models.PrepareTestDatabase())
 
diff --git a/routers/api/v1/convert/convert.go b/routers/api/v1/convert/convert.go
index 07456f8dd6..6da53d6275 100644
--- a/routers/api/v1/convert/convert.go
+++ b/routers/api/v1/convert/convert.go
@@ -15,6 +15,7 @@ import (
 	"code.gitea.io/gitea/modules/structs"
 	api "code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/webhook"
 
 	"github.com/unknwon/com"
 )
@@ -166,7 +167,7 @@ func ToHook(repoLink string, w *models.Webhook) *api.Hook {
 		"content_type": w.ContentType.Name(),
 	}
 	if w.HookTaskType == models.SLACK {
-		s := w.GetSlackHook()
+		s := webhook.GetSlackHook(w)
 		config["channel"] = s.Channel
 		config["username"] = s.Username
 		config["icon_url"] = s.IconURL
diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go
index 7903d58334..6f72e99b71 100644
--- a/routers/api/v1/utils/hook.go
+++ b/routers/api/v1/utils/hook.go
@@ -12,6 +12,7 @@ import (
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/context"
 	api "code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/webhook"
 	"code.gitea.io/gitea/routers/api/v1/convert"
 	"code.gitea.io/gitea/routers/utils"
 
@@ -129,7 +130,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID
 			return nil, false
 		}
 
-		meta, err := json.Marshal(&models.SlackMeta{
+		meta, err := json.Marshal(&webhook.SlackMeta{
 			Channel:  strings.TrimSpace(channel),
 			Username: form.Config["username"],
 			IconURL:  form.Config["icon_url"],
@@ -203,7 +204,7 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *models.Webho
 
 		if w.HookTaskType == models.SLACK {
 			if channel, ok := form.Config["channel"]; ok {
-				meta, err := json.Marshal(&models.SlackMeta{
+				meta, err := json.Marshal(&webhook.SlackMeta{
 					Channel:  channel,
 					Username: form.Config["username"],
 					IconURL:  form.Config["icon_url"],
diff --git a/routers/repo/webhook.go b/routers/repo/webhook.go
index a6bd3af264..9ae15882c1 100644
--- a/routers/repo/webhook.go
+++ b/routers/repo/webhook.go
@@ -268,7 +268,7 @@ func DiscordHooksNewPost(ctx *context.Context, form auth.NewDiscordHookForm) {
 		return
 	}
 
-	meta, err := json.Marshal(&models.DiscordMeta{
+	meta, err := json.Marshal(&webhook.DiscordMeta{
 		Username: form.Username,
 		IconURL:  form.IconURL,
 	})
@@ -357,7 +357,7 @@ func TelegramHooksNewPost(ctx *context.Context, form auth.NewTelegramHookForm) {
 		return
 	}
 
-	meta, err := json.Marshal(&models.TelegramMeta{
+	meta, err := json.Marshal(&webhook.TelegramMeta{
 		BotToken: form.BotToken,
 		ChatID:   form.ChatID,
 	})
@@ -452,7 +452,7 @@ func SlackHooksNewPost(ctx *context.Context, form auth.NewSlackHookForm) {
 		return
 	}
 
-	meta, err := json.Marshal(&models.SlackMeta{
+	meta, err := json.Marshal(&webhook.SlackMeta{
 		Channel:  strings.TrimSpace(form.Channel),
 		Username: form.Username,
 		IconURL:  form.IconURL,
@@ -515,11 +515,11 @@ func checkWebhook(ctx *context.Context) (*orgRepoCtx, *models.Webhook) {
 	ctx.Data["HookType"] = w.HookTaskType.Name()
 	switch w.HookTaskType {
 	case models.SLACK:
-		ctx.Data["SlackHook"] = w.GetSlackHook()
+		ctx.Data["SlackHook"] = webhook.GetSlackHook(w)
 	case models.DISCORD:
-		ctx.Data["DiscordHook"] = w.GetDiscordHook()
+		ctx.Data["DiscordHook"] = webhook.GetDiscordHook(w)
 	case models.TELEGRAM:
-		ctx.Data["TelegramHook"] = w.GetTelegramHook()
+		ctx.Data["TelegramHook"] = webhook.GetTelegramHook(w)
 	}
 
 	ctx.Data["History"], err = w.History(1)
@@ -646,7 +646,7 @@ func SlackHooksEditPost(ctx *context.Context, form auth.NewSlackHookForm) {
 		return
 	}
 
-	meta, err := json.Marshal(&models.SlackMeta{
+	meta, err := json.Marshal(&webhook.SlackMeta{
 		Channel:  strings.TrimSpace(form.Channel),
 		Username: form.Username,
 		IconURL:  form.IconURL,
@@ -690,7 +690,7 @@ func DiscordHooksEditPost(ctx *context.Context, form auth.NewDiscordHookForm) {
 		return
 	}
 
-	meta, err := json.Marshal(&models.DiscordMeta{
+	meta, err := json.Marshal(&webhook.DiscordMeta{
 		Username: form.Username,
 		IconURL:  form.IconURL,
 	})
@@ -763,7 +763,7 @@ func TelegramHooksEditPost(ctx *context.Context, form auth.NewTelegramHookForm)
 		ctx.HTML(200, orCtx.NewTemplate)
 		return
 	}
-	meta, err := json.Marshal(&models.TelegramMeta{
+	meta, err := json.Marshal(&webhook.TelegramMeta{
 		BotToken: form.BotToken,
 		ChatID:   form.ChatID,
 	})