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