gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package webhook
  4. import (
  5. "context"
  6. "fmt"
  7. "net/http"
  8. "net/url"
  9. "strconv"
  10. "strings"
  11. webhook_model "code.gitea.io/gitea/models/webhook"
  12. "code.gitea.io/gitea/modules/git"
  13. api "code.gitea.io/gitea/modules/structs"
  14. "code.gitea.io/gitea/modules/util"
  15. webhook_module "code.gitea.io/gitea/modules/webhook"
  16. )
  17. type (
  18. // MSTeamsFact for Fact Structure
  19. MSTeamsFact struct {
  20. Name string `json:"name"`
  21. Value string `json:"value"`
  22. }
  23. // MSTeamsSection is a MessageCard section
  24. MSTeamsSection struct {
  25. ActivityTitle string `json:"activityTitle"`
  26. ActivitySubtitle string `json:"activitySubtitle"`
  27. ActivityImage string `json:"activityImage"`
  28. Facts []MSTeamsFact `json:"facts"`
  29. Text string `json:"text"`
  30. }
  31. // MSTeamsAction is an action (creates buttons, links etc)
  32. MSTeamsAction struct {
  33. Type string `json:"@type"`
  34. Name string `json:"name"`
  35. Targets []MSTeamsActionTarget `json:"targets,omitempty"`
  36. }
  37. // MSTeamsActionTarget is the actual link to follow, etc
  38. MSTeamsActionTarget struct {
  39. Os string `json:"os"`
  40. URI string `json:"uri"`
  41. }
  42. // MSTeamsPayload is the parent object
  43. MSTeamsPayload struct {
  44. Type string `json:"@type"`
  45. Context string `json:"@context"`
  46. ThemeColor string `json:"themeColor"`
  47. Title string `json:"title"`
  48. Summary string `json:"summary"`
  49. Sections []MSTeamsSection `json:"sections"`
  50. PotentialAction []MSTeamsAction `json:"potentialAction"`
  51. }
  52. )
  53. type msteamsConvertor struct{}
  54. // Create implements PayloadConvertor Create method
  55. func (m msteamsConvertor) Create(p *api.CreatePayload) (MSTeamsPayload, error) {
  56. // created tag/branch
  57. refName := git.RefName(p.Ref).ShortName()
  58. title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName)
  59. return createMSTeamsPayload(
  60. p.Repo,
  61. p.Sender,
  62. title,
  63. "",
  64. p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName),
  65. greenColor,
  66. &MSTeamsFact{p.RefType + ":", refName},
  67. ), nil
  68. }
  69. // Delete implements PayloadConvertor Delete method
  70. func (m msteamsConvertor) Delete(p *api.DeletePayload) (MSTeamsPayload, error) {
  71. // deleted tag/branch
  72. refName := git.RefName(p.Ref).ShortName()
  73. title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName)
  74. return createMSTeamsPayload(
  75. p.Repo,
  76. p.Sender,
  77. title,
  78. "",
  79. p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName),
  80. yellowColor,
  81. &MSTeamsFact{p.RefType + ":", refName},
  82. ), nil
  83. }
  84. // Fork implements PayloadConvertor Fork method
  85. func (m msteamsConvertor) Fork(p *api.ForkPayload) (MSTeamsPayload, error) {
  86. title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName)
  87. return createMSTeamsPayload(
  88. p.Repo,
  89. p.Sender,
  90. title,
  91. "",
  92. p.Repo.HTMLURL,
  93. greenColor,
  94. &MSTeamsFact{"Forkee:", p.Forkee.FullName},
  95. ), nil
  96. }
  97. // Push implements PayloadConvertor Push method
  98. func (m msteamsConvertor) Push(p *api.PushPayload) (MSTeamsPayload, error) {
  99. var (
  100. branchName = git.RefName(p.Ref).ShortName()
  101. commitDesc string
  102. )
  103. var titleLink string
  104. if p.TotalCommits == 1 {
  105. commitDesc = "1 new commit"
  106. titleLink = p.Commits[0].URL
  107. } else {
  108. commitDesc = fmt.Sprintf("%d new commits", p.TotalCommits)
  109. titleLink = p.CompareURL
  110. }
  111. if titleLink == "" {
  112. titleLink = p.Repo.HTMLURL + "/src/" + util.PathEscapeSegments(branchName)
  113. }
  114. title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc)
  115. var text string
  116. // for each commit, generate attachment text
  117. for i, commit := range p.Commits {
  118. text += fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL,
  119. strings.TrimRight(commit.Message, "\r\n"), commit.Author.Name)
  120. // add linebreak to each commit but the last
  121. if i < len(p.Commits)-1 {
  122. text += "\n\n"
  123. }
  124. }
  125. return createMSTeamsPayload(
  126. p.Repo,
  127. p.Sender,
  128. title,
  129. text,
  130. titleLink,
  131. greenColor,
  132. &MSTeamsFact{"Commit count:", strconv.Itoa(p.TotalCommits)},
  133. ), nil
  134. }
  135. // Issue implements PayloadConvertor Issue method
  136. func (m msteamsConvertor) Issue(p *api.IssuePayload) (MSTeamsPayload, error) {
  137. title, _, extraMarkdown, color := getIssuesPayloadInfo(p, noneLinkFormatter, false)
  138. return createMSTeamsPayload(
  139. p.Repository,
  140. p.Sender,
  141. title,
  142. extraMarkdown,
  143. p.Issue.HTMLURL,
  144. color,
  145. &MSTeamsFact{"Issue #:", strconv.FormatInt(p.Issue.ID, 10)},
  146. ), nil
  147. }
  148. // IssueComment implements PayloadConvertor IssueComment method
  149. func (m msteamsConvertor) IssueComment(p *api.IssueCommentPayload) (MSTeamsPayload, error) {
  150. title, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter, false)
  151. return createMSTeamsPayload(
  152. p.Repository,
  153. p.Sender,
  154. title,
  155. p.Comment.Body,
  156. p.Comment.HTMLURL,
  157. color,
  158. &MSTeamsFact{"Issue #:", strconv.FormatInt(p.Issue.ID, 10)},
  159. ), nil
  160. }
  161. // PullRequest implements PayloadConvertor PullRequest method
  162. func (m msteamsConvertor) PullRequest(p *api.PullRequestPayload) (MSTeamsPayload, error) {
  163. title, _, extraMarkdown, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false)
  164. return createMSTeamsPayload(
  165. p.Repository,
  166. p.Sender,
  167. title,
  168. extraMarkdown,
  169. p.PullRequest.HTMLURL,
  170. color,
  171. &MSTeamsFact{"Pull request #:", strconv.FormatInt(p.PullRequest.ID, 10)},
  172. ), nil
  173. }
  174. // Review implements PayloadConvertor Review method
  175. func (m msteamsConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (MSTeamsPayload, error) {
  176. var text, title string
  177. var color int
  178. switch p.Action {
  179. case api.HookIssueReviewed:
  180. action, err := parseHookPullRequestEventType(event)
  181. if err != nil {
  182. return MSTeamsPayload{}, err
  183. }
  184. title = fmt.Sprintf("[%s] Pull request review %s: #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title)
  185. text = p.Review.Content
  186. switch event {
  187. case webhook_module.HookEventPullRequestReviewApproved:
  188. color = greenColor
  189. case webhook_module.HookEventPullRequestReviewRejected:
  190. color = redColor
  191. case webhook_module.HookEventPullRequestReviewComment:
  192. color = greyColor
  193. default:
  194. color = yellowColor
  195. }
  196. }
  197. return createMSTeamsPayload(
  198. p.Repository,
  199. p.Sender,
  200. title,
  201. text,
  202. p.PullRequest.HTMLURL,
  203. color,
  204. &MSTeamsFact{"Pull request #:", strconv.FormatInt(p.PullRequest.ID, 10)},
  205. ), nil
  206. }
  207. // Repository implements PayloadConvertor Repository method
  208. func (m msteamsConvertor) Repository(p *api.RepositoryPayload) (MSTeamsPayload, error) {
  209. var title, url string
  210. var color int
  211. switch p.Action {
  212. case api.HookRepoCreated:
  213. title = fmt.Sprintf("[%s] Repository created", p.Repository.FullName)
  214. url = p.Repository.HTMLURL
  215. color = greenColor
  216. case api.HookRepoDeleted:
  217. title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName)
  218. color = yellowColor
  219. }
  220. return createMSTeamsPayload(
  221. p.Repository,
  222. p.Sender,
  223. title,
  224. "",
  225. url,
  226. color,
  227. nil,
  228. ), nil
  229. }
  230. // Wiki implements PayloadConvertor Wiki method
  231. func (m msteamsConvertor) Wiki(p *api.WikiPayload) (MSTeamsPayload, error) {
  232. title, color, _ := getWikiPayloadInfo(p, noneLinkFormatter, false)
  233. return createMSTeamsPayload(
  234. p.Repository,
  235. p.Sender,
  236. title,
  237. "",
  238. p.Repository.HTMLURL+"/wiki/"+url.PathEscape(p.Page),
  239. color,
  240. &MSTeamsFact{"Repository:", p.Repository.FullName},
  241. ), nil
  242. }
  243. // Release implements PayloadConvertor Release method
  244. func (m msteamsConvertor) Release(p *api.ReleasePayload) (MSTeamsPayload, error) {
  245. title, color := getReleasePayloadInfo(p, noneLinkFormatter, false)
  246. return createMSTeamsPayload(
  247. p.Repository,
  248. p.Sender,
  249. title,
  250. "",
  251. p.Release.HTMLURL,
  252. color,
  253. &MSTeamsFact{"Tag:", p.Release.TagName},
  254. ), nil
  255. }
  256. func (m msteamsConvertor) Package(p *api.PackagePayload) (MSTeamsPayload, error) {
  257. title, color := getPackagePayloadInfo(p, noneLinkFormatter, false)
  258. return createMSTeamsPayload(
  259. p.Repository,
  260. p.Sender,
  261. title,
  262. "",
  263. p.Package.HTMLURL,
  264. color,
  265. &MSTeamsFact{"Package:", p.Package.Name},
  266. ), nil
  267. }
  268. func (m msteamsConvertor) Status(p *api.CommitStatusPayload) (MSTeamsPayload, error) {
  269. title, color := getStatusPayloadInfo(p, noneLinkFormatter, false)
  270. return createMSTeamsPayload(
  271. p.Repo,
  272. p.Sender,
  273. title,
  274. "",
  275. p.TargetURL,
  276. color,
  277. &MSTeamsFact{"CommitStatus:", p.Context},
  278. ), nil
  279. }
  280. func (msteamsConvertor) WorkflowRun(p *api.WorkflowRunPayload) (MSTeamsPayload, error) {
  281. title, color := getWorkflowRunPayloadInfo(p, noneLinkFormatter, false)
  282. return createMSTeamsPayload(
  283. p.Repo,
  284. p.Sender,
  285. title,
  286. "",
  287. p.WorkflowRun.HTMLURL,
  288. color,
  289. &MSTeamsFact{"WorkflowRun:", p.WorkflowRun.DisplayTitle},
  290. ), nil
  291. }
  292. func (msteamsConvertor) WorkflowJob(p *api.WorkflowJobPayload) (MSTeamsPayload, error) {
  293. title, color := getWorkflowJobPayloadInfo(p, noneLinkFormatter, false)
  294. return createMSTeamsPayload(
  295. p.Repo,
  296. p.Sender,
  297. title,
  298. "",
  299. p.WorkflowJob.HTMLURL,
  300. color,
  301. &MSTeamsFact{"WorkflowJob:", p.WorkflowJob.Name},
  302. ), nil
  303. }
  304. func createMSTeamsPayload(r *api.Repository, s *api.User, title, text, actionTarget string, color int, fact *MSTeamsFact) MSTeamsPayload {
  305. facts := make([]MSTeamsFact, 0, 2)
  306. if r != nil {
  307. facts = append(facts, MSTeamsFact{
  308. Name: "Repository:",
  309. Value: r.FullName,
  310. })
  311. }
  312. if fact != nil {
  313. facts = append(facts, *fact)
  314. }
  315. return MSTeamsPayload{
  316. Type: "MessageCard",
  317. Context: "https://schema.org/extensions",
  318. ThemeColor: fmt.Sprintf("%x", color),
  319. Title: title,
  320. Summary: title,
  321. Sections: []MSTeamsSection{
  322. {
  323. ActivityTitle: s.FullName,
  324. ActivitySubtitle: s.UserName,
  325. ActivityImage: s.AvatarURL,
  326. Text: text,
  327. Facts: facts,
  328. },
  329. },
  330. PotentialAction: []MSTeamsAction{
  331. {
  332. Type: "OpenUri",
  333. Name: "View in Gitea",
  334. Targets: []MSTeamsActionTarget{
  335. {
  336. Os: "default",
  337. URI: actionTarget,
  338. },
  339. },
  340. },
  341. },
  342. }
  343. }
  344. func newMSTeamsRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
  345. var pc payloadConvertor[MSTeamsPayload] = msteamsConvertor{}
  346. return newJSONRequest(pc, w, t, true)
  347. }
  348. func init() {
  349. RegisterWebhookRequester(webhook_module.MSTEAMS, newMSTeamsRequest)
  350. }