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.

msteams.go 14KB

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