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.

563 lines
14 KiB

  1. // Copyright 2019 The Gitea 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 webhook
  5. import (
  6. "encoding/json"
  7. "fmt"
  8. "strings"
  9. "code.gitea.io/gitea/models"
  10. "code.gitea.io/gitea/modules/git"
  11. api "code.gitea.io/gitea/modules/structs"
  12. )
  13. type (
  14. // MSTeamsFact for Fact Structure
  15. MSTeamsFact struct {
  16. Name string `json:"name"`
  17. Value string `json:"value"`
  18. }
  19. // MSTeamsSection is a MessageCard section
  20. MSTeamsSection struct {
  21. ActivityTitle string `json:"activityTitle"`
  22. ActivitySubtitle string `json:"activitySubtitle"`
  23. ActivityImage string `json:"activityImage"`
  24. Facts []MSTeamsFact `json:"facts"`
  25. Text string `json:"text"`
  26. }
  27. // MSTeamsAction is an action (creates buttons, links etc)
  28. MSTeamsAction struct {
  29. Type string `json:"@type"`
  30. Name string `json:"name"`
  31. Targets []MSTeamsActionTarget `json:"targets,omitempty"`
  32. }
  33. // MSTeamsActionTarget is the actual link to follow, etc
  34. MSTeamsActionTarget struct {
  35. Os string `json:"os"`
  36. URI string `json:"uri"`
  37. }
  38. // MSTeamsPayload is the parent object
  39. MSTeamsPayload struct {
  40. Type string `json:"@type"`
  41. Context string `json:"@context"`
  42. ThemeColor string `json:"themeColor"`
  43. Title string `json:"title"`
  44. Summary string `json:"summary"`
  45. Sections []MSTeamsSection `json:"sections"`
  46. PotentialAction []MSTeamsAction `json:"potentialAction"`
  47. }
  48. )
  49. // SetSecret sets the MSTeams secret
  50. func (m *MSTeamsPayload) SetSecret(_ string) {}
  51. // JSONPayload Marshals the MSTeamsPayload to json
  52. func (m *MSTeamsPayload) JSONPayload() ([]byte, error) {
  53. data, err := json.MarshalIndent(m, "", " ")
  54. if err != nil {
  55. return []byte{}, err
  56. }
  57. return data, nil
  58. }
  59. var (
  60. _ PayloadConvertor = &MSTeamsPayload{}
  61. )
  62. // Create implements PayloadConvertor Create method
  63. func (m *MSTeamsPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
  64. // created tag/branch
  65. refName := git.RefEndName(p.Ref)
  66. title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName)
  67. return &MSTeamsPayload{
  68. Type: "MessageCard",
  69. Context: "https://schema.org/extensions",
  70. ThemeColor: fmt.Sprintf("%x", greenColor),
  71. Title: title,
  72. Summary: title,
  73. Sections: []MSTeamsSection{
  74. {
  75. ActivityTitle: p.Sender.FullName,
  76. ActivitySubtitle: p.Sender.UserName,
  77. ActivityImage: p.Sender.AvatarURL,
  78. Facts: []MSTeamsFact{
  79. {
  80. Name: "Repository:",
  81. Value: p.Repo.FullName,
  82. },
  83. {
  84. Name: fmt.Sprintf("%s:", p.RefType),
  85. Value: refName,
  86. },
  87. },
  88. },
  89. },
  90. PotentialAction: []MSTeamsAction{
  91. {
  92. Type: "OpenUri",
  93. Name: "View in Gitea",
  94. Targets: []MSTeamsActionTarget{
  95. {
  96. Os: "default",
  97. URI: p.Repo.HTMLURL + "/src/" + refName,
  98. },
  99. },
  100. },
  101. },
  102. }, nil
  103. }
  104. // Delete implements PayloadConvertor Delete method
  105. func (m *MSTeamsPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
  106. // deleted tag/branch
  107. refName := git.RefEndName(p.Ref)
  108. title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName)
  109. return &MSTeamsPayload{
  110. Type: "MessageCard",
  111. Context: "https://schema.org/extensions",
  112. ThemeColor: fmt.Sprintf("%x", yellowColor),
  113. Title: title,
  114. Summary: title,
  115. Sections: []MSTeamsSection{
  116. {
  117. ActivityTitle: p.Sender.FullName,
  118. ActivitySubtitle: p.Sender.UserName,
  119. ActivityImage: p.Sender.AvatarURL,
  120. Facts: []MSTeamsFact{
  121. {
  122. Name: "Repository:",
  123. Value: p.Repo.FullName,
  124. },
  125. {
  126. Name: fmt.Sprintf("%s:", p.RefType),
  127. Value: refName,
  128. },
  129. },
  130. },
  131. },
  132. PotentialAction: []MSTeamsAction{
  133. {
  134. Type: "OpenUri",
  135. Name: "View in Gitea",
  136. Targets: []MSTeamsActionTarget{
  137. {
  138. Os: "default",
  139. URI: p.Repo.HTMLURL + "/src/" + refName,
  140. },
  141. },
  142. },
  143. },
  144. }, nil
  145. }
  146. // Fork implements PayloadConvertor Fork method
  147. func (m *MSTeamsPayload) Fork(p *api.ForkPayload) (api.Payloader, error) {
  148. title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName)
  149. return &MSTeamsPayload{
  150. Type: "MessageCard",
  151. Context: "https://schema.org/extensions",
  152. ThemeColor: fmt.Sprintf("%x", greenColor),
  153. Title: title,
  154. Summary: title,
  155. Sections: []MSTeamsSection{
  156. {
  157. ActivityTitle: p.Sender.FullName,
  158. ActivitySubtitle: p.Sender.UserName,
  159. ActivityImage: p.Sender.AvatarURL,
  160. Facts: []MSTeamsFact{
  161. {
  162. Name: "Forkee:",
  163. Value: p.Forkee.FullName,
  164. },
  165. {
  166. Name: "Repository:",
  167. Value: p.Repo.FullName,
  168. },
  169. },
  170. },
  171. },
  172. PotentialAction: []MSTeamsAction{
  173. {
  174. Type: "OpenUri",
  175. Name: "View in Gitea",
  176. Targets: []MSTeamsActionTarget{
  177. {
  178. Os: "default",
  179. URI: p.Repo.HTMLURL,
  180. },
  181. },
  182. },
  183. },
  184. }, nil
  185. }
  186. // Push implements PayloadConvertor Push method
  187. func (m *MSTeamsPayload) Push(p *api.PushPayload) (api.Payloader, error) {
  188. var (
  189. branchName = git.RefEndName(p.Ref)
  190. commitDesc string
  191. )
  192. var titleLink string
  193. if len(p.Commits) == 1 {
  194. commitDesc = "1 new commit"
  195. titleLink = p.Commits[0].URL
  196. } else {
  197. commitDesc = fmt.Sprintf("%d new commits", len(p.Commits))
  198. titleLink = p.CompareURL
  199. }
  200. if titleLink == "" {
  201. titleLink = p.Repo.HTMLURL + "/src/" + branchName
  202. }
  203. title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc)
  204. var text string
  205. // for each commit, generate attachment text
  206. for i, commit := range p.Commits {
  207. text += fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL,
  208. strings.TrimRight(commit.Message, "\r\n"), commit.Author.Name)
  209. // add linebreak to each commit but the last
  210. if i < len(p.Commits)-1 {
  211. text += "\n"
  212. }
  213. }
  214. return &MSTeamsPayload{
  215. Type: "MessageCard",
  216. Context: "https://schema.org/extensions",
  217. ThemeColor: fmt.Sprintf("%x", greenColor),
  218. Title: title,
  219. Summary: title,
  220. Sections: []MSTeamsSection{
  221. {
  222. ActivityTitle: p.Sender.FullName,
  223. ActivitySubtitle: p.Sender.UserName,
  224. ActivityImage: p.Sender.AvatarURL,
  225. Text: text,
  226. Facts: []MSTeamsFact{
  227. {
  228. Name: "Repository:",
  229. Value: p.Repo.FullName,
  230. },
  231. {
  232. Name: "Commit count:",
  233. Value: fmt.Sprintf("%d", len(p.Commits)),
  234. },
  235. },
  236. },
  237. },
  238. PotentialAction: []MSTeamsAction{
  239. {
  240. Type: "OpenUri",
  241. Name: "View in Gitea",
  242. Targets: []MSTeamsActionTarget{
  243. {
  244. Os: "default",
  245. URI: titleLink,
  246. },
  247. },
  248. },
  249. },
  250. }, nil
  251. }
  252. // Issue implements PayloadConvertor Issue method
  253. func (m *MSTeamsPayload) Issue(p *api.IssuePayload) (api.Payloader, error) {
  254. text, _, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter, false)
  255. return &MSTeamsPayload{
  256. Type: "MessageCard",
  257. Context: "https://schema.org/extensions",
  258. ThemeColor: fmt.Sprintf("%x", color),
  259. Title: text,
  260. Summary: text,
  261. Sections: []MSTeamsSection{
  262. {
  263. ActivityTitle: p.Sender.FullName,
  264. ActivitySubtitle: p.Sender.UserName,
  265. ActivityImage: p.Sender.AvatarURL,
  266. Text: attachmentText,
  267. Facts: []MSTeamsFact{
  268. {
  269. Name: "Repository:",
  270. Value: p.Repository.FullName,
  271. },
  272. {
  273. Name: "Issue #:",
  274. Value: fmt.Sprintf("%d", p.Issue.ID),
  275. },
  276. },
  277. },
  278. },
  279. PotentialAction: []MSTeamsAction{
  280. {
  281. Type: "OpenUri",
  282. Name: "View in Gitea",
  283. Targets: []MSTeamsActionTarget{
  284. {
  285. Os: "default",
  286. URI: p.Issue.HTMLURL,
  287. },
  288. },
  289. },
  290. },
  291. }, nil
  292. }
  293. // IssueComment implements PayloadConvertor IssueComment method
  294. func (m *MSTeamsPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) {
  295. text, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter, false)
  296. return &MSTeamsPayload{
  297. Type: "MessageCard",
  298. Context: "https://schema.org/extensions",
  299. ThemeColor: fmt.Sprintf("%x", color),
  300. Title: text,
  301. Summary: text,
  302. Sections: []MSTeamsSection{
  303. {
  304. ActivityTitle: p.Sender.FullName,
  305. ActivitySubtitle: p.Sender.UserName,
  306. ActivityImage: p.Sender.AvatarURL,
  307. Text: p.Comment.Body,
  308. Facts: []MSTeamsFact{
  309. {
  310. Name: "Repository:",
  311. Value: p.Repository.FullName,
  312. },
  313. {
  314. Name: "Issue #:",
  315. Value: fmt.Sprintf("%d", p.Issue.ID),
  316. },
  317. },
  318. },
  319. },
  320. PotentialAction: []MSTeamsAction{
  321. {
  322. Type: "OpenUri",
  323. Name: "View in Gitea",
  324. Targets: []MSTeamsActionTarget{
  325. {
  326. Os: "default",
  327. URI: p.Comment.HTMLURL,
  328. },
  329. },
  330. },
  331. },
  332. }, nil
  333. }
  334. // PullRequest implements PayloadConvertor PullRequest method
  335. func (m *MSTeamsPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) {
  336. text, _, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false)
  337. return &MSTeamsPayload{
  338. Type: "MessageCard",
  339. Context: "https://schema.org/extensions",
  340. ThemeColor: fmt.Sprintf("%x", color),
  341. Title: text,
  342. Summary: text,
  343. Sections: []MSTeamsSection{
  344. {
  345. ActivityTitle: p.Sender.FullName,
  346. ActivitySubtitle: p.Sender.UserName,
  347. ActivityImage: p.Sender.AvatarURL,
  348. Text: attachmentText,
  349. Facts: []MSTeamsFact{
  350. {
  351. Name: "Repository:",
  352. Value: p.Repository.FullName,
  353. },
  354. {
  355. Name: "Pull request #:",
  356. Value: fmt.Sprintf("%d", p.PullRequest.ID),
  357. },
  358. },
  359. },
  360. },
  361. PotentialAction: []MSTeamsAction{
  362. {
  363. Type: "OpenUri",
  364. Name: "View in Gitea",
  365. Targets: []MSTeamsActionTarget{
  366. {
  367. Os: "default",
  368. URI: p.PullRequest.HTMLURL,
  369. },
  370. },
  371. },
  372. },
  373. }, nil
  374. }
  375. // Review implements PayloadConvertor Review method
  376. func (m *MSTeamsPayload) Review(p *api.PullRequestPayload, event models.HookEventType) (api.Payloader, error) {
  377. var text, title string
  378. var color int
  379. switch p.Action {
  380. case api.HookIssueReviewed:
  381. action, err := parseHookPullRequestEventType(event)
  382. if err != nil {
  383. return nil, err
  384. }
  385. title = fmt.Sprintf("[%s] Pull request review %s: #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title)
  386. text = p.Review.Content
  387. switch event {
  388. case models.HookEventPullRequestReviewApproved:
  389. color = greenColor
  390. case models.HookEventPullRequestReviewRejected:
  391. color = redColor
  392. case models.HookEventPullRequestComment:
  393. color = greyColor
  394. default:
  395. color = yellowColor
  396. }
  397. }
  398. return &MSTeamsPayload{
  399. Type: "MessageCard",
  400. Context: "https://schema.org/extensions",
  401. ThemeColor: fmt.Sprintf("%x", color),
  402. Title: title,
  403. Summary: title,
  404. Sections: []MSTeamsSection{
  405. {
  406. ActivityTitle: p.Sender.FullName,
  407. ActivitySubtitle: p.Sender.UserName,
  408. ActivityImage: p.Sender.AvatarURL,
  409. Text: text,
  410. Facts: []MSTeamsFact{
  411. {
  412. Name: "Repository:",
  413. Value: p.Repository.FullName,
  414. },
  415. {
  416. Name: "Pull request #:",
  417. Value: fmt.Sprintf("%d", p.PullRequest.ID),
  418. },
  419. },
  420. },
  421. },
  422. PotentialAction: []MSTeamsAction{
  423. {
  424. Type: "OpenUri",
  425. Name: "View in Gitea",
  426. Targets: []MSTeamsActionTarget{
  427. {
  428. Os: "default",
  429. URI: p.PullRequest.HTMLURL,
  430. },
  431. },
  432. },
  433. },
  434. }, nil
  435. }
  436. // Repository implements PayloadConvertor Repository method
  437. func (m *MSTeamsPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) {
  438. var title, url string
  439. var color int
  440. switch p.Action {
  441. case api.HookRepoCreated:
  442. title = fmt.Sprintf("[%s] Repository created", p.Repository.FullName)
  443. url = p.Repository.HTMLURL
  444. color = greenColor
  445. case api.HookRepoDeleted:
  446. title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName)
  447. color = yellowColor
  448. }
  449. return &MSTeamsPayload{
  450. Type: "MessageCard",
  451. Context: "https://schema.org/extensions",
  452. ThemeColor: fmt.Sprintf("%x", color),
  453. Title: title,
  454. Summary: title,
  455. Sections: []MSTeamsSection{
  456. {
  457. ActivityTitle: p.Sender.FullName,
  458. ActivitySubtitle: p.Sender.UserName,
  459. ActivityImage: p.Sender.AvatarURL,
  460. Facts: []MSTeamsFact{
  461. {
  462. Name: "Repository:",
  463. Value: p.Repository.FullName,
  464. },
  465. },
  466. },
  467. },
  468. PotentialAction: []MSTeamsAction{
  469. {
  470. Type: "OpenUri",
  471. Name: "View in Gitea",
  472. Targets: []MSTeamsActionTarget{
  473. {
  474. Os: "default",
  475. URI: url,
  476. },
  477. },
  478. },
  479. },
  480. }, nil
  481. }
  482. // Release implements PayloadConvertor Release method
  483. func (m *MSTeamsPayload) Release(p *api.ReleasePayload) (api.Payloader, error) {
  484. text, color := getReleasePayloadInfo(p, noneLinkFormatter, false)
  485. return &MSTeamsPayload{
  486. Type: "MessageCard",
  487. Context: "https://schema.org/extensions",
  488. ThemeColor: fmt.Sprintf("%x", color),
  489. Title: text,
  490. Summary: text,
  491. Sections: []MSTeamsSection{
  492. {
  493. ActivityTitle: p.Sender.FullName,
  494. ActivitySubtitle: p.Sender.UserName,
  495. ActivityImage: p.Sender.AvatarURL,
  496. Text: p.Release.Note,
  497. Facts: []MSTeamsFact{
  498. {
  499. Name: "Repository:",
  500. Value: p.Repository.FullName,
  501. },
  502. {
  503. Name: "Tag:",
  504. Value: p.Release.TagName,
  505. },
  506. },
  507. },
  508. },
  509. PotentialAction: []MSTeamsAction{
  510. {
  511. Type: "OpenUri",
  512. Name: "View in Gitea",
  513. Targets: []MSTeamsActionTarget{
  514. {
  515. Os: "default",
  516. URI: p.Release.URL,
  517. },
  518. },
  519. },
  520. },
  521. }, nil
  522. }
  523. // GetMSTeamsPayload converts a MSTeams webhook into a MSTeamsPayload
  524. func GetMSTeamsPayload(p api.Payloader, event models.HookEventType, meta string) (api.Payloader, error) {
  525. return convertPayloader(new(MSTeamsPayload), p, event)
  526. }