本站源代码
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.

586 lines
17KB

  1. // Copyright 2017 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 models
  5. import (
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "strconv"
  10. "strings"
  11. "code.gitea.io/gitea/modules/git"
  12. "code.gitea.io/gitea/modules/setting"
  13. api "code.gitea.io/gitea/modules/structs"
  14. )
  15. type (
  16. // DiscordEmbedFooter for Embed Footer Structure.
  17. DiscordEmbedFooter struct {
  18. Text string `json:"text"`
  19. }
  20. // DiscordEmbedAuthor for Embed Author Structure
  21. DiscordEmbedAuthor struct {
  22. Name string `json:"name"`
  23. URL string `json:"url"`
  24. IconURL string `json:"icon_url"`
  25. }
  26. // DiscordEmbedField for Embed Field Structure
  27. DiscordEmbedField struct {
  28. Name string `json:"name"`
  29. Value string `json:"value"`
  30. }
  31. // DiscordEmbed is for Embed Structure
  32. DiscordEmbed struct {
  33. Title string `json:"title"`
  34. Description string `json:"description"`
  35. URL string `json:"url"`
  36. Color int `json:"color"`
  37. Footer DiscordEmbedFooter `json:"footer"`
  38. Author DiscordEmbedAuthor `json:"author"`
  39. Fields []DiscordEmbedField `json:"fields"`
  40. }
  41. // DiscordPayload represents
  42. DiscordPayload struct {
  43. Wait bool `json:"wait"`
  44. Content string `json:"content"`
  45. Username string `json:"username"`
  46. AvatarURL string `json:"avatar_url"`
  47. TTS bool `json:"tts"`
  48. Embeds []DiscordEmbed `json:"embeds"`
  49. }
  50. // DiscordMeta contains the discord metadata
  51. DiscordMeta struct {
  52. Username string `json:"username"`
  53. IconURL string `json:"icon_url"`
  54. }
  55. )
  56. func color(clr string) int {
  57. if clr != "" {
  58. clr = strings.TrimLeft(clr, "#")
  59. if s, err := strconv.ParseInt(clr, 16, 32); err == nil {
  60. return int(s)
  61. }
  62. }
  63. return 0
  64. }
  65. var (
  66. greenColor = color("1ac600")
  67. greenColorLight = color("bfe5bf")
  68. yellowColor = color("ffd930")
  69. greyColor = color("4f545c")
  70. purpleColor = color("7289da")
  71. orangeColor = color("eb6420")
  72. orangeColorLight = color("e68d60")
  73. redColor = color("ff3232")
  74. )
  75. // SetSecret sets the discord secret
  76. func (p *DiscordPayload) SetSecret(_ string) {}
  77. // JSONPayload Marshals the DiscordPayload to json
  78. func (p *DiscordPayload) JSONPayload() ([]byte, error) {
  79. data, err := json.MarshalIndent(p, "", " ")
  80. if err != nil {
  81. return []byte{}, err
  82. }
  83. return data, nil
  84. }
  85. func getDiscordCreatePayload(p *api.CreatePayload, meta *DiscordMeta) (*DiscordPayload, error) {
  86. // created tag/branch
  87. refName := git.RefEndName(p.Ref)
  88. title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName)
  89. return &DiscordPayload{
  90. Username: meta.Username,
  91. AvatarURL: meta.IconURL,
  92. Embeds: []DiscordEmbed{
  93. {
  94. Title: title,
  95. URL: p.Repo.HTMLURL + "/src/" + refName,
  96. Color: greenColor,
  97. Author: DiscordEmbedAuthor{
  98. Name: p.Sender.UserName,
  99. URL: setting.AppURL + p.Sender.UserName,
  100. IconURL: p.Sender.AvatarURL,
  101. },
  102. },
  103. },
  104. }, nil
  105. }
  106. func getDiscordDeletePayload(p *api.DeletePayload, meta *DiscordMeta) (*DiscordPayload, error) {
  107. // deleted tag/branch
  108. refName := git.RefEndName(p.Ref)
  109. title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName)
  110. return &DiscordPayload{
  111. Username: meta.Username,
  112. AvatarURL: meta.IconURL,
  113. Embeds: []DiscordEmbed{
  114. {
  115. Title: title,
  116. URL: p.Repo.HTMLURL + "/src/" + refName,
  117. Color: redColor,
  118. Author: DiscordEmbedAuthor{
  119. Name: p.Sender.UserName,
  120. URL: setting.AppURL + p.Sender.UserName,
  121. IconURL: p.Sender.AvatarURL,
  122. },
  123. },
  124. },
  125. }, nil
  126. }
  127. func getDiscordForkPayload(p *api.ForkPayload, meta *DiscordMeta) (*DiscordPayload, error) {
  128. // fork
  129. title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName)
  130. return &DiscordPayload{
  131. Username: meta.Username,
  132. AvatarURL: meta.IconURL,
  133. Embeds: []DiscordEmbed{
  134. {
  135. Title: title,
  136. URL: p.Repo.HTMLURL,
  137. Color: greenColor,
  138. Author: DiscordEmbedAuthor{
  139. Name: p.Sender.UserName,
  140. URL: setting.AppURL + p.Sender.UserName,
  141. IconURL: p.Sender.AvatarURL,
  142. },
  143. },
  144. },
  145. }, nil
  146. }
  147. func getDiscordPushPayload(p *api.PushPayload, meta *DiscordMeta) (*DiscordPayload, error) {
  148. var (
  149. branchName = git.RefEndName(p.Ref)
  150. commitDesc string
  151. )
  152. var titleLink string
  153. if len(p.Commits) == 1 {
  154. commitDesc = "1 new commit"
  155. titleLink = p.Commits[0].URL
  156. } else {
  157. commitDesc = fmt.Sprintf("%d new commits", len(p.Commits))
  158. titleLink = p.CompareURL
  159. }
  160. if titleLink == "" {
  161. titleLink = p.Repo.HTMLURL + "/src/" + branchName
  162. }
  163. title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc)
  164. var text string
  165. // for each commit, generate attachment text
  166. for i, commit := range p.Commits {
  167. text += fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL,
  168. strings.TrimRight(commit.Message, "\r\n"), commit.Author.Name)
  169. // add linebreak to each commit but the last
  170. if i < len(p.Commits)-1 {
  171. text += "\n"
  172. }
  173. }
  174. return &DiscordPayload{
  175. Username: meta.Username,
  176. AvatarURL: meta.IconURL,
  177. Embeds: []DiscordEmbed{
  178. {
  179. Title: title,
  180. Description: text,
  181. URL: titleLink,
  182. Color: greenColor,
  183. Author: DiscordEmbedAuthor{
  184. Name: p.Sender.UserName,
  185. URL: setting.AppURL + p.Sender.UserName,
  186. IconURL: p.Sender.AvatarURL,
  187. },
  188. },
  189. },
  190. }, nil
  191. }
  192. func getDiscordIssuesPayload(p *api.IssuePayload, meta *DiscordMeta) (*DiscordPayload, error) {
  193. var text, title string
  194. var color int
  195. url := fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index)
  196. switch p.Action {
  197. case api.HookIssueOpened:
  198. title = fmt.Sprintf("[%s] Issue opened: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
  199. text = p.Issue.Body
  200. color = orangeColor
  201. case api.HookIssueClosed:
  202. title = fmt.Sprintf("[%s] Issue closed: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
  203. color = redColor
  204. text = p.Issue.Body
  205. case api.HookIssueReOpened:
  206. title = fmt.Sprintf("[%s] Issue re-opened: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
  207. text = p.Issue.Body
  208. color = yellowColor
  209. case api.HookIssueEdited:
  210. title = fmt.Sprintf("[%s] Issue edited: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
  211. text = p.Issue.Body
  212. color = yellowColor
  213. case api.HookIssueAssigned:
  214. title = fmt.Sprintf("[%s] Issue assigned to %s: #%d %s", p.Repository.FullName,
  215. p.Issue.Assignee.UserName, p.Index, p.Issue.Title)
  216. text = p.Issue.Body
  217. color = greenColor
  218. case api.HookIssueUnassigned:
  219. title = fmt.Sprintf("[%s] Issue unassigned: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
  220. text = p.Issue.Body
  221. color = yellowColor
  222. case api.HookIssueLabelUpdated:
  223. title = fmt.Sprintf("[%s] Issue labels updated: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
  224. text = p.Issue.Body
  225. color = yellowColor
  226. case api.HookIssueLabelCleared:
  227. title = fmt.Sprintf("[%s] Issue labels cleared: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
  228. text = p.Issue.Body
  229. color = yellowColor
  230. case api.HookIssueSynchronized:
  231. title = fmt.Sprintf("[%s] Issue synchronized: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
  232. text = p.Issue.Body
  233. color = yellowColor
  234. case api.HookIssueMilestoned:
  235. title = fmt.Sprintf("[%s] Issue milestone: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
  236. text = p.Issue.Body
  237. color = yellowColor
  238. case api.HookIssueDemilestoned:
  239. title = fmt.Sprintf("[%s] Issue clear milestone: #%d %s", p.Repository.FullName, p.Index, p.Issue.Title)
  240. text = p.Issue.Body
  241. color = yellowColor
  242. }
  243. return &DiscordPayload{
  244. Username: meta.Username,
  245. AvatarURL: meta.IconURL,
  246. Embeds: []DiscordEmbed{
  247. {
  248. Title: title,
  249. Description: text,
  250. URL: url,
  251. Color: color,
  252. Author: DiscordEmbedAuthor{
  253. Name: p.Sender.UserName,
  254. URL: setting.AppURL + p.Sender.UserName,
  255. IconURL: p.Sender.AvatarURL,
  256. },
  257. },
  258. },
  259. }, nil
  260. }
  261. func getDiscordIssueCommentPayload(p *api.IssueCommentPayload, discord *DiscordMeta) (*DiscordPayload, error) {
  262. title := fmt.Sprintf("#%d: %s", p.Issue.Index, p.Issue.Title)
  263. url := fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID))
  264. content := ""
  265. var color int
  266. switch p.Action {
  267. case api.HookIssueCommentCreated:
  268. if p.IsPull {
  269. title = "New comment on pull request " + title
  270. color = greenColorLight
  271. } else {
  272. title = "New comment on issue " + title
  273. color = orangeColorLight
  274. }
  275. content = p.Comment.Body
  276. case api.HookIssueCommentEdited:
  277. if p.IsPull {
  278. title = "Comment edited on pull request " + title
  279. } else {
  280. title = "Comment edited on issue " + title
  281. }
  282. content = p.Comment.Body
  283. color = yellowColor
  284. case api.HookIssueCommentDeleted:
  285. if p.IsPull {
  286. title = "Comment deleted on pull request " + title
  287. } else {
  288. title = "Comment deleted on issue " + title
  289. }
  290. url = fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index)
  291. content = p.Comment.Body
  292. color = redColor
  293. }
  294. title = fmt.Sprintf("[%s] %s", p.Repository.FullName, title)
  295. return &DiscordPayload{
  296. Username: discord.Username,
  297. AvatarURL: discord.IconURL,
  298. Embeds: []DiscordEmbed{
  299. {
  300. Title: title,
  301. Description: content,
  302. URL: url,
  303. Color: color,
  304. Author: DiscordEmbedAuthor{
  305. Name: p.Sender.UserName,
  306. URL: setting.AppURL + p.Sender.UserName,
  307. IconURL: p.Sender.AvatarURL,
  308. },
  309. },
  310. },
  311. }, nil
  312. }
  313. func getDiscordPullRequestPayload(p *api.PullRequestPayload, meta *DiscordMeta) (*DiscordPayload, error) {
  314. var text, title string
  315. var color int
  316. switch p.Action {
  317. case api.HookIssueOpened:
  318. title = fmt.Sprintf("[%s] Pull request opened: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
  319. text = p.PullRequest.Body
  320. color = greenColor
  321. case api.HookIssueClosed:
  322. if p.PullRequest.HasMerged {
  323. title = fmt.Sprintf("[%s] Pull request merged: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
  324. color = purpleColor
  325. } else {
  326. title = fmt.Sprintf("[%s] Pull request closed: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
  327. color = redColor
  328. }
  329. text = p.PullRequest.Body
  330. case api.HookIssueReOpened:
  331. title = fmt.Sprintf("[%s] Pull request re-opened: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
  332. text = p.PullRequest.Body
  333. color = yellowColor
  334. case api.HookIssueEdited:
  335. title = fmt.Sprintf("[%s] Pull request edited: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
  336. text = p.PullRequest.Body
  337. color = yellowColor
  338. case api.HookIssueAssigned:
  339. list := make([]string, len(p.PullRequest.Assignees))
  340. for i, user := range p.PullRequest.Assignees {
  341. list[i] = user.UserName
  342. }
  343. title = fmt.Sprintf("[%s] Pull request assigned to %s: #%d by %s", p.Repository.FullName,
  344. strings.Join(list, ", "),
  345. p.Index, p.PullRequest.Title)
  346. text = p.PullRequest.Body
  347. color = greenColor
  348. case api.HookIssueUnassigned:
  349. title = fmt.Sprintf("[%s] Pull request unassigned: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
  350. text = p.PullRequest.Body
  351. color = yellowColor
  352. case api.HookIssueLabelUpdated:
  353. title = fmt.Sprintf("[%s] Pull request labels updated: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
  354. text = p.PullRequest.Body
  355. color = yellowColor
  356. case api.HookIssueLabelCleared:
  357. title = fmt.Sprintf("[%s] Pull request labels cleared: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
  358. text = p.PullRequest.Body
  359. color = yellowColor
  360. case api.HookIssueSynchronized:
  361. title = fmt.Sprintf("[%s] Pull request synchronized: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
  362. text = p.PullRequest.Body
  363. color = yellowColor
  364. case api.HookIssueMilestoned:
  365. title = fmt.Sprintf("[%s] Pull request milestone: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
  366. text = p.PullRequest.Body
  367. color = yellowColor
  368. case api.HookIssueDemilestoned:
  369. title = fmt.Sprintf("[%s] Pull request clear milestone: #%d %s", p.Repository.FullName, p.Index, p.PullRequest.Title)
  370. text = p.PullRequest.Body
  371. color = yellowColor
  372. }
  373. return &DiscordPayload{
  374. Username: meta.Username,
  375. AvatarURL: meta.IconURL,
  376. Embeds: []DiscordEmbed{
  377. {
  378. Title: title,
  379. Description: text,
  380. URL: p.PullRequest.HTMLURL,
  381. Color: color,
  382. Author: DiscordEmbedAuthor{
  383. Name: p.Sender.UserName,
  384. URL: setting.AppURL + p.Sender.UserName,
  385. IconURL: p.Sender.AvatarURL,
  386. },
  387. },
  388. },
  389. }, nil
  390. }
  391. func getDiscordPullRequestApprovalPayload(p *api.PullRequestPayload, meta *DiscordMeta, event HookEventType) (*DiscordPayload, error) {
  392. var text, title string
  393. var color int
  394. switch p.Action {
  395. case api.HookIssueSynchronized:
  396. action, err := parseHookPullRequestEventType(event)
  397. if err != nil {
  398. return nil, err
  399. }
  400. title = fmt.Sprintf("[%s] Pull request review %s: #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title)
  401. text = p.Review.Content
  402. switch event {
  403. case HookEventPullRequestApproved:
  404. color = greenColor
  405. case HookEventPullRequestRejected:
  406. color = redColor
  407. case HookEventPullRequestComment:
  408. color = greyColor
  409. default:
  410. color = yellowColor
  411. }
  412. }
  413. return &DiscordPayload{
  414. Username: meta.Username,
  415. AvatarURL: meta.IconURL,
  416. Embeds: []DiscordEmbed{
  417. {
  418. Title: title,
  419. Description: text,
  420. URL: p.PullRequest.HTMLURL,
  421. Color: color,
  422. Author: DiscordEmbedAuthor{
  423. Name: p.Sender.UserName,
  424. URL: setting.AppURL + p.Sender.UserName,
  425. IconURL: p.Sender.AvatarURL,
  426. },
  427. },
  428. },
  429. }, nil
  430. }
  431. func getDiscordRepositoryPayload(p *api.RepositoryPayload, meta *DiscordMeta) (*DiscordPayload, error) {
  432. var title, url string
  433. var color int
  434. switch p.Action {
  435. case api.HookRepoCreated:
  436. title = fmt.Sprintf("[%s] Repository created", p.Repository.FullName)
  437. url = p.Repository.HTMLURL
  438. color = greenColor
  439. case api.HookRepoDeleted:
  440. title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName)
  441. color = redColor
  442. }
  443. return &DiscordPayload{
  444. Username: meta.Username,
  445. AvatarURL: meta.IconURL,
  446. Embeds: []DiscordEmbed{
  447. {
  448. Title: title,
  449. URL: url,
  450. Color: color,
  451. Author: DiscordEmbedAuthor{
  452. Name: p.Sender.UserName,
  453. URL: setting.AppURL + p.Sender.UserName,
  454. IconURL: p.Sender.AvatarURL,
  455. },
  456. },
  457. },
  458. }, nil
  459. }
  460. func getDiscordReleasePayload(p *api.ReleasePayload, meta *DiscordMeta) (*DiscordPayload, error) {
  461. var title, url string
  462. var color int
  463. switch p.Action {
  464. case api.HookReleasePublished:
  465. title = fmt.Sprintf("[%s] Release created", p.Release.TagName)
  466. url = p.Release.URL
  467. color = greenColor
  468. case api.HookReleaseUpdated:
  469. title = fmt.Sprintf("[%s] Release updated", p.Release.TagName)
  470. url = p.Release.URL
  471. color = yellowColor
  472. case api.HookReleaseDeleted:
  473. title = fmt.Sprintf("[%s] Release deleted", p.Release.TagName)
  474. url = p.Release.URL
  475. color = redColor
  476. }
  477. return &DiscordPayload{
  478. Username: meta.Username,
  479. AvatarURL: meta.IconURL,
  480. Embeds: []DiscordEmbed{
  481. {
  482. Title: title,
  483. Description: p.Release.Note,
  484. URL: url,
  485. Color: color,
  486. Author: DiscordEmbedAuthor{
  487. Name: p.Sender.UserName,
  488. URL: setting.AppURL + p.Sender.UserName,
  489. IconURL: p.Sender.AvatarURL,
  490. },
  491. },
  492. },
  493. }, nil
  494. }
  495. // GetDiscordPayload converts a discord webhook into a DiscordPayload
  496. func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*DiscordPayload, error) {
  497. s := new(DiscordPayload)
  498. discord := &DiscordMeta{}
  499. if err := json.Unmarshal([]byte(meta), &discord); err != nil {
  500. return s, errors.New("GetDiscordPayload meta json:" + err.Error())
  501. }
  502. switch event {
  503. case HookEventCreate:
  504. return getDiscordCreatePayload(p.(*api.CreatePayload), discord)
  505. case HookEventDelete:
  506. return getDiscordDeletePayload(p.(*api.DeletePayload), discord)
  507. case HookEventFork:
  508. return getDiscordForkPayload(p.(*api.ForkPayload), discord)
  509. case HookEventIssues:
  510. return getDiscordIssuesPayload(p.(*api.IssuePayload), discord)
  511. case HookEventIssueComment:
  512. return getDiscordIssueCommentPayload(p.(*api.IssueCommentPayload), discord)
  513. case HookEventPush:
  514. return getDiscordPushPayload(p.(*api.PushPayload), discord)
  515. case HookEventPullRequest:
  516. return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord)
  517. case HookEventPullRequestRejected, HookEventPullRequestApproved, HookEventPullRequestComment:
  518. return getDiscordPullRequestApprovalPayload(p.(*api.PullRequestPayload), discord, event)
  519. case HookEventRepository:
  520. return getDiscordRepositoryPayload(p.(*api.RepositoryPayload), discord)
  521. case HookEventRelease:
  522. return getDiscordReleasePayload(p.(*api.ReleasePayload), discord)
  523. }
  524. return s, nil
  525. }
  526. func parseHookPullRequestEventType(event HookEventType) (string, error) {
  527. switch event {
  528. case HookEventPullRequestApproved:
  529. return "approved", nil
  530. case HookEventPullRequestRejected:
  531. return "rejected", nil
  532. case HookEventPullRequestComment:
  533. return "comment", nil
  534. default:
  535. return "", errors.New("unknown event type")
  536. }
  537. }
上海开阖软件有限公司 沪ICP备12045867号-1