You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

webhook_slack.go 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package models
  5. import (
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "strings"
  10. "code.gitea.io/git"
  11. api "code.gitea.io/sdk/gitea"
  12. "code.gitea.io/gitea/modules/setting"
  13. )
  14. // SlackMeta contains the slack metdata
  15. type SlackMeta struct {
  16. Channel string `json:"channel"`
  17. Username string `json:"username"`
  18. IconURL string `json:"icon_url"`
  19. Color string `json:"color"`
  20. }
  21. // SlackPayload contains the information about the slack channel
  22. type SlackPayload struct {
  23. Channel string `json:"channel"`
  24. Text string `json:"text"`
  25. Username string `json:"username"`
  26. IconURL string `json:"icon_url"`
  27. UnfurlLinks int `json:"unfurl_links"`
  28. LinkNames int `json:"link_names"`
  29. Attachments []SlackAttachment `json:"attachments"`
  30. }
  31. // SlackAttachment contains the slack message
  32. type SlackAttachment struct {
  33. Fallback string `json:"fallback"`
  34. Color string `json:"color"`
  35. Title string `json:"title"`
  36. Text string `json:"text"`
  37. }
  38. // SetSecret sets the slack secret
  39. func (p *SlackPayload) SetSecret(_ string) {}
  40. // JSONPayload Marshals the SlackPayload to json
  41. func (p *SlackPayload) JSONPayload() ([]byte, error) {
  42. data, err := json.MarshalIndent(p, "", " ")
  43. if err != nil {
  44. return []byte{}, err
  45. }
  46. return data, nil
  47. }
  48. // SlackTextFormatter replaces &, <, > with HTML characters
  49. // see: https://api.slack.com/docs/formatting
  50. func SlackTextFormatter(s string) string {
  51. // replace & < >
  52. s = strings.Replace(s, "&", "&amp;", -1)
  53. s = strings.Replace(s, "<", "&lt;", -1)
  54. s = strings.Replace(s, ">", "&gt;", -1)
  55. return s
  56. }
  57. // SlackShortTextFormatter replaces &, <, > with HTML characters
  58. func SlackShortTextFormatter(s string) string {
  59. s = strings.Split(s, "\n")[0]
  60. // replace & < >
  61. s = strings.Replace(s, "&", "&amp;", -1)
  62. s = strings.Replace(s, "<", "&lt;", -1)
  63. s = strings.Replace(s, ">", "&gt;", -1)
  64. return s
  65. }
  66. // SlackLinkFormatter creates a link compatablie with slack
  67. func SlackLinkFormatter(url string, text string) string {
  68. return fmt.Sprintf("<%s|%s>", url, SlackTextFormatter(text))
  69. }
  70. func getSlackCreatePayload(p *api.CreatePayload, slack *SlackMeta) (*SlackPayload, error) {
  71. // created tag/branch
  72. refName := git.RefEndName(p.Ref)
  73. repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
  74. refLink := SlackLinkFormatter(p.Repo.HTMLURL+"/src/"+refName, refName)
  75. text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName)
  76. return &SlackPayload{
  77. Channel: slack.Channel,
  78. Text: text,
  79. Username: slack.Username,
  80. IconURL: slack.IconURL,
  81. }, nil
  82. }
  83. func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, error) {
  84. // n new commits
  85. var (
  86. branchName = git.RefEndName(p.Ref)
  87. commitDesc string
  88. commitString string
  89. )
  90. if len(p.Commits) == 1 {
  91. commitDesc = "1 new commit"
  92. } else {
  93. commitDesc = fmt.Sprintf("%d new commits", len(p.Commits))
  94. }
  95. if len(p.CompareURL) > 0 {
  96. commitString = SlackLinkFormatter(p.CompareURL, commitDesc)
  97. } else {
  98. commitString = commitDesc
  99. }
  100. repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
  101. branchLink := SlackLinkFormatter(p.Repo.HTMLURL+"/src/"+branchName, branchName)
  102. text := fmt.Sprintf("[%s:%s] %s pushed by %s", repoLink, branchLink, commitString, p.Pusher.UserName)
  103. var attachmentText string
  104. // for each commit, generate attachment text
  105. for i, commit := range p.Commits {
  106. attachmentText += fmt.Sprintf("%s: %s - %s", SlackLinkFormatter(commit.URL, commit.ID[:7]), SlackShortTextFormatter(commit.Message), SlackTextFormatter(commit.Author.Name))
  107. // add linebreak to each commit but the last
  108. if i < len(p.Commits)-1 {
  109. attachmentText += "\n"
  110. }
  111. }
  112. return &SlackPayload{
  113. Channel: slack.Channel,
  114. Text: text,
  115. Username: slack.Username,
  116. IconURL: slack.IconURL,
  117. Attachments: []SlackAttachment{{
  118. Color: slack.Color,
  119. Text: attachmentText,
  120. }},
  121. }, nil
  122. }
  123. func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*SlackPayload, error) {
  124. senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
  125. titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index),
  126. fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title))
  127. var text, title, attachmentText string
  128. switch p.Action {
  129. case api.HookIssueOpened:
  130. text = fmt.Sprintf("[%s] Pull request submitted by %s", p.Repository.FullName, senderLink)
  131. title = titleLink
  132. attachmentText = SlackTextFormatter(p.PullRequest.Body)
  133. case api.HookIssueClosed:
  134. if p.PullRequest.HasMerged {
  135. text = fmt.Sprintf("[%s] Pull request merged: %s by %s", p.Repository.FullName, titleLink, senderLink)
  136. } else {
  137. text = fmt.Sprintf("[%s] Pull request closed: %s by %s", p.Repository.FullName, titleLink, senderLink)
  138. }
  139. case api.HookIssueReOpened:
  140. text = fmt.Sprintf("[%s] Pull request re-opened: %s by %s", p.Repository.FullName, titleLink, senderLink)
  141. case api.HookIssueEdited:
  142. text = fmt.Sprintf("[%s] Pull request edited: %s by %s", p.Repository.FullName, titleLink, senderLink)
  143. attachmentText = SlackTextFormatter(p.PullRequest.Body)
  144. case api.HookIssueAssigned:
  145. text = fmt.Sprintf("[%s] Pull request assigned to %s: %s by %s", p.Repository.FullName,
  146. SlackLinkFormatter(setting.AppURL+p.PullRequest.Assignee.UserName, p.PullRequest.Assignee.UserName),
  147. titleLink, senderLink)
  148. case api.HookIssueUnassigned:
  149. text = fmt.Sprintf("[%s] Pull request unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink)
  150. case api.HookIssueLabelUpdated:
  151. text = fmt.Sprintf("[%s] Pull request labels updated: %s by %s", p.Repository.FullName, titleLink, senderLink)
  152. case api.HookIssueLabelCleared:
  153. text = fmt.Sprintf("[%s] Pull request labels cleared: %s by %s", p.Repository.FullName, titleLink, senderLink)
  154. case api.HookIssueSynchronized:
  155. text = fmt.Sprintf("[%s] Pull request synchronized: %s by %s", p.Repository.FullName, titleLink, senderLink)
  156. }
  157. return &SlackPayload{
  158. Channel: slack.Channel,
  159. Text: text,
  160. Username: slack.Username,
  161. IconURL: slack.IconURL,
  162. Attachments: []SlackAttachment{{
  163. Color: slack.Color,
  164. Title: title,
  165. Text: attachmentText,
  166. }},
  167. }, nil
  168. }
  169. // GetSlackPayload converts a slack webhook into a SlackPayload
  170. func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (*SlackPayload, error) {
  171. s := new(SlackPayload)
  172. slack := &SlackMeta{}
  173. if err := json.Unmarshal([]byte(meta), &slack); err != nil {
  174. return s, errors.New("GetSlackPayload meta json:" + err.Error())
  175. }
  176. switch event {
  177. case HookEventCreate:
  178. return getSlackCreatePayload(p.(*api.CreatePayload), slack)
  179. case HookEventPush:
  180. return getSlackPushPayload(p.(*api.PushPayload), slack)
  181. case HookEventPullRequest:
  182. return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack)
  183. }
  184. return s, nil
  185. }