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