code.gitea.io/gitea@v1.21.7/services/webhook/general.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 "html" 9 "net/url" 10 "strings" 11 12 webhook_model "code.gitea.io/gitea/models/webhook" 13 "code.gitea.io/gitea/modules/setting" 14 api "code.gitea.io/gitea/modules/structs" 15 "code.gitea.io/gitea/modules/util" 16 webhook_module "code.gitea.io/gitea/modules/webhook" 17 ) 18 19 type linkFormatter = func(string, string) string 20 21 // noneLinkFormatter does not create a link but just returns the text 22 func noneLinkFormatter(url, text string) string { 23 return text 24 } 25 26 // htmlLinkFormatter creates a HTML link 27 func htmlLinkFormatter(url, text string) string { 28 return fmt.Sprintf(`<a href="%s">%s</a>`, html.EscapeString(url), html.EscapeString(text)) 29 } 30 31 // getPullRequestInfo gets the information for a pull request 32 func getPullRequestInfo(p *api.PullRequestPayload) (title, link, by, operator, operateResult, assignees string) { 33 title = fmt.Sprintf("[PullRequest-%s #%d]: %s\n%s", p.Repository.FullName, p.PullRequest.Index, p.Action, p.PullRequest.Title) 34 assignList := p.PullRequest.Assignees 35 assignStringList := make([]string, len(assignList)) 36 37 for i, user := range assignList { 38 assignStringList[i] = user.UserName 39 } 40 if p.Action == api.HookIssueAssigned { 41 operateResult = fmt.Sprintf("%s assign this to %s", p.Sender.UserName, assignList[len(assignList)-1].UserName) 42 } else if p.Action == api.HookIssueUnassigned { 43 operateResult = fmt.Sprintf("%s unassigned this for someone", p.Sender.UserName) 44 } else if p.Action == api.HookIssueMilestoned { 45 operateResult = fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.PullRequest.Milestone.ID) 46 } 47 link = p.PullRequest.HTMLURL 48 by = fmt.Sprintf("PullRequest by %s", p.PullRequest.Poster.UserName) 49 if len(assignStringList) > 0 { 50 assignees = fmt.Sprintf("Assignees: %s", strings.Join(assignStringList, ", ")) 51 } 52 operator = fmt.Sprintf("Operator: %s", p.Sender.UserName) 53 return title, link, by, operator, operateResult, assignees 54 } 55 56 // getIssuesInfo gets the information for an issue 57 func getIssuesInfo(p *api.IssuePayload) (issueTitle, link, by, operator, operateResult, assignees string) { 58 issueTitle = fmt.Sprintf("[Issue-%s #%d]: %s\n%s", p.Repository.FullName, p.Issue.Index, p.Action, p.Issue.Title) 59 assignList := p.Issue.Assignees 60 assignStringList := make([]string, len(assignList)) 61 62 for i, user := range assignList { 63 assignStringList[i] = user.UserName 64 } 65 if p.Action == api.HookIssueAssigned { 66 operateResult = fmt.Sprintf("%s assign this to %s", p.Sender.UserName, assignList[len(assignList)-1].UserName) 67 } else if p.Action == api.HookIssueUnassigned { 68 operateResult = fmt.Sprintf("%s unassigned this for someone", p.Sender.UserName) 69 } else if p.Action == api.HookIssueMilestoned { 70 operateResult = fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.Issue.Milestone.ID) 71 } 72 link = p.Issue.HTMLURL 73 by = fmt.Sprintf("Issue by %s", p.Issue.Poster.UserName) 74 if len(assignStringList) > 0 { 75 assignees = fmt.Sprintf("Assignees: %s", strings.Join(assignStringList, ", ")) 76 } 77 operator = fmt.Sprintf("Operator: %s", p.Sender.UserName) 78 return issueTitle, link, by, operator, operateResult, assignees 79 } 80 81 // getIssuesCommentInfo gets the information for a comment 82 func getIssuesCommentInfo(p *api.IssueCommentPayload) (title, link, by, operator string) { 83 title = fmt.Sprintf("[Comment-%s #%d]: %s\n%s", p.Repository.FullName, p.Issue.Index, p.Action, p.Issue.Title) 84 link = p.Issue.HTMLURL 85 if p.IsPull { 86 by = fmt.Sprintf("PullRequest by %s", p.Issue.Poster.UserName) 87 } else { 88 by = fmt.Sprintf("Issue by %s", p.Issue.Poster.UserName) 89 } 90 operator = fmt.Sprintf("Operator: %s", p.Sender.UserName) 91 return title, link, by, operator 92 } 93 94 func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter, withSender bool) (string, string, string, int) { 95 repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) 96 issueTitle := fmt.Sprintf("#%d %s", p.Index, p.Issue.Title) 97 titleLink := linkFormatter(fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Index), issueTitle) 98 var text string 99 color := yellowColor 100 101 switch p.Action { 102 case api.HookIssueOpened: 103 text = fmt.Sprintf("[%s] Issue opened: %s", repoLink, titleLink) 104 color = orangeColor 105 case api.HookIssueClosed: 106 text = fmt.Sprintf("[%s] Issue closed: %s", repoLink, titleLink) 107 color = redColor 108 case api.HookIssueReOpened: 109 text = fmt.Sprintf("[%s] Issue re-opened: %s", repoLink, titleLink) 110 case api.HookIssueEdited: 111 text = fmt.Sprintf("[%s] Issue edited: %s", repoLink, titleLink) 112 case api.HookIssueAssigned: 113 list := make([]string, len(p.Issue.Assignees)) 114 for i, user := range p.Issue.Assignees { 115 list[i] = linkFormatter(setting.AppURL+url.PathEscape(user.UserName), user.UserName) 116 } 117 text = fmt.Sprintf("[%s] Issue assigned to %s: %s", repoLink, strings.Join(list, ", "), titleLink) 118 color = greenColor 119 case api.HookIssueUnassigned: 120 text = fmt.Sprintf("[%s] Issue unassigned: %s", repoLink, titleLink) 121 case api.HookIssueLabelUpdated: 122 text = fmt.Sprintf("[%s] Issue labels updated: %s", repoLink, titleLink) 123 case api.HookIssueLabelCleared: 124 text = fmt.Sprintf("[%s] Issue labels cleared: %s", repoLink, titleLink) 125 case api.HookIssueSynchronized: 126 text = fmt.Sprintf("[%s] Issue synchronized: %s", repoLink, titleLink) 127 case api.HookIssueMilestoned: 128 mileStoneLink := fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.Issue.Milestone.ID) 129 text = fmt.Sprintf("[%s] Issue milestoned to %s: %s", repoLink, 130 linkFormatter(mileStoneLink, p.Issue.Milestone.Title), titleLink) 131 case api.HookIssueDemilestoned: 132 text = fmt.Sprintf("[%s] Issue milestone cleared: %s", repoLink, titleLink) 133 } 134 if withSender { 135 text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) 136 } 137 138 var attachmentText string 139 if p.Action == api.HookIssueOpened || p.Action == api.HookIssueEdited { 140 attachmentText = p.Issue.Body 141 } 142 143 return text, issueTitle, attachmentText, color 144 } 145 146 func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkFormatter, withSender bool) (string, string, string, int) { 147 repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) 148 issueTitle := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title) 149 titleLink := linkFormatter(p.PullRequest.URL, issueTitle) 150 var text string 151 var attachmentText string 152 color := yellowColor 153 154 switch p.Action { 155 case api.HookIssueOpened: 156 text = fmt.Sprintf("[%s] Pull request opened: %s", repoLink, titleLink) 157 attachmentText = p.PullRequest.Body 158 color = greenColor 159 case api.HookIssueClosed: 160 if p.PullRequest.HasMerged { 161 text = fmt.Sprintf("[%s] Pull request merged: %s", repoLink, titleLink) 162 color = purpleColor 163 } else { 164 text = fmt.Sprintf("[%s] Pull request closed: %s", repoLink, titleLink) 165 color = redColor 166 } 167 case api.HookIssueReOpened: 168 text = fmt.Sprintf("[%s] Pull request re-opened: %s", repoLink, titleLink) 169 case api.HookIssueEdited: 170 text = fmt.Sprintf("[%s] Pull request edited: %s", repoLink, titleLink) 171 attachmentText = p.PullRequest.Body 172 case api.HookIssueAssigned: 173 list := make([]string, len(p.PullRequest.Assignees)) 174 for i, user := range p.PullRequest.Assignees { 175 list[i] = linkFormatter(setting.AppURL+user.UserName, user.UserName) 176 } 177 text = fmt.Sprintf("[%s] Pull request assigned to %s: %s", repoLink, 178 strings.Join(list, ", "), titleLink) 179 color = greenColor 180 case api.HookIssueUnassigned: 181 text = fmt.Sprintf("[%s] Pull request unassigned: %s", repoLink, titleLink) 182 case api.HookIssueLabelUpdated: 183 text = fmt.Sprintf("[%s] Pull request labels updated: %s", repoLink, titleLink) 184 case api.HookIssueLabelCleared: 185 text = fmt.Sprintf("[%s] Pull request labels cleared: %s", repoLink, titleLink) 186 case api.HookIssueSynchronized: 187 text = fmt.Sprintf("[%s] Pull request synchronized: %s", repoLink, titleLink) 188 case api.HookIssueMilestoned: 189 mileStoneLink := fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.PullRequest.Milestone.ID) 190 text = fmt.Sprintf("[%s] Pull request milestoned to %s: %s", repoLink, 191 linkFormatter(mileStoneLink, p.PullRequest.Milestone.Title), titleLink) 192 case api.HookIssueDemilestoned: 193 text = fmt.Sprintf("[%s] Pull request milestone cleared: %s", repoLink, titleLink) 194 case api.HookIssueReviewed: 195 text = fmt.Sprintf("[%s] Pull request reviewed: %s", repoLink, titleLink) 196 attachmentText = p.Review.Content 197 case api.HookIssueReviewRequested: 198 text = fmt.Sprintf("[%s] Pull request review requested: %s", repoLink, titleLink) 199 case api.HookIssueReviewRequestRemoved: 200 text = fmt.Sprintf("[%s] Pull request review request removed: %s", repoLink, titleLink) 201 } 202 if withSender { 203 text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)) 204 } 205 206 return text, issueTitle, attachmentText, color 207 } 208 209 func getReleasePayloadInfo(p *api.ReleasePayload, linkFormatter linkFormatter, withSender bool) (text string, color int) { 210 repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) 211 refLink := linkFormatter(p.Repository.HTMLURL+"/releases/tag/"+util.PathEscapeSegments(p.Release.TagName), p.Release.TagName) 212 213 switch p.Action { 214 case api.HookReleasePublished: 215 text = fmt.Sprintf("[%s] Release created: %s", repoLink, refLink) 216 color = greenColor 217 case api.HookReleaseUpdated: 218 text = fmt.Sprintf("[%s] Release updated: %s", repoLink, refLink) 219 color = yellowColor 220 case api.HookReleaseDeleted: 221 text = fmt.Sprintf("[%s] Release deleted: %s", repoLink, refLink) 222 color = redColor 223 } 224 if withSender { 225 text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) 226 } 227 228 return text, color 229 } 230 231 func getWikiPayloadInfo(p *api.WikiPayload, linkFormatter linkFormatter, withSender bool) (string, int, string) { 232 repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) 233 pageLink := linkFormatter(p.Repository.HTMLURL+"/wiki/"+url.PathEscape(p.Page), p.Page) 234 235 var text string 236 color := greenColor 237 238 switch p.Action { 239 case api.HookWikiCreated: 240 text = fmt.Sprintf("[%s] New wiki page '%s'", repoLink, pageLink) 241 case api.HookWikiEdited: 242 text = fmt.Sprintf("[%s] Wiki page '%s' edited", repoLink, pageLink) 243 color = yellowColor 244 case api.HookWikiDeleted: 245 text = fmt.Sprintf("[%s] Wiki page '%s' deleted", repoLink, pageLink) 246 color = redColor 247 } 248 249 if p.Action != api.HookWikiDeleted && p.Comment != "" { 250 text += fmt.Sprintf(" (%s)", p.Comment) 251 } 252 253 if withSender { 254 text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) 255 } 256 257 return text, color, pageLink 258 } 259 260 func getIssueCommentPayloadInfo(p *api.IssueCommentPayload, linkFormatter linkFormatter, withSender bool) (string, string, int) { 261 repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) 262 issueTitle := fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title) 263 264 var text, typ, titleLink string 265 color := yellowColor 266 267 if p.IsPull { 268 typ = "pull request" 269 titleLink = linkFormatter(p.Comment.PRURL, issueTitle) 270 } else { 271 typ = "issue" 272 titleLink = linkFormatter(p.Comment.IssueURL, issueTitle) 273 } 274 275 switch p.Action { 276 case api.HookIssueCommentCreated: 277 text = fmt.Sprintf("[%s] New comment on %s %s", repoLink, typ, titleLink) 278 if p.IsPull { 279 color = greenColorLight 280 } else { 281 color = orangeColorLight 282 } 283 case api.HookIssueCommentEdited: 284 text = fmt.Sprintf("[%s] Comment edited on %s %s", repoLink, typ, titleLink) 285 case api.HookIssueCommentDeleted: 286 text = fmt.Sprintf("[%s] Comment deleted on %s %s", repoLink, typ, titleLink) 287 color = redColor 288 } 289 if withSender { 290 text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) 291 } 292 293 return text, issueTitle, color 294 } 295 296 func getPackagePayloadInfo(p *api.PackagePayload, linkFormatter linkFormatter, withSender bool) (text string, color int) { 297 refLink := linkFormatter(p.Package.HTMLURL, p.Package.Name+":"+p.Package.Version) 298 299 switch p.Action { 300 case api.HookPackageCreated: 301 text = fmt.Sprintf("Package created: %s", refLink) 302 color = greenColor 303 case api.HookPackageDeleted: 304 text = fmt.Sprintf("Package deleted: %s", refLink) 305 color = redColor 306 } 307 if withSender { 308 text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) 309 } 310 311 return text, color 312 } 313 314 // ToHook convert models.Webhook to api.Hook 315 // This function is not part of the convert package to prevent an import cycle 316 func ToHook(repoLink string, w *webhook_model.Webhook) (*api.Hook, error) { 317 config := map[string]string{ 318 "url": w.URL, 319 "content_type": w.ContentType.Name(), 320 } 321 if w.Type == webhook_module.SLACK { 322 s := GetSlackHook(w) 323 config["channel"] = s.Channel 324 config["username"] = s.Username 325 config["icon_url"] = s.IconURL 326 config["color"] = s.Color 327 } 328 329 authorizationHeader, err := w.HeaderAuthorization() 330 if err != nil { 331 return nil, err 332 } 333 334 return &api.Hook{ 335 ID: w.ID, 336 Type: w.Type, 337 URL: fmt.Sprintf("%s/settings/hooks/%d", repoLink, w.ID), 338 Active: w.IsActive, 339 Config: config, 340 Events: w.EventsArray(), 341 AuthorizationHeader: authorizationHeader, 342 Updated: w.UpdatedUnix.AsTime(), 343 Created: w.CreatedUnix.AsTime(), 344 BranchFilter: w.BranchFilter, 345 }, nil 346 }