code.gitea.io/gitea@v1.21.7/routers/web/feed/convert.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package feed 5 6 import ( 7 "fmt" 8 "html" 9 "net/http" 10 "net/url" 11 "strconv" 12 "strings" 13 14 activities_model "code.gitea.io/gitea/models/activities" 15 repo_model "code.gitea.io/gitea/models/repo" 16 "code.gitea.io/gitea/modules/context" 17 "code.gitea.io/gitea/modules/markup" 18 "code.gitea.io/gitea/modules/markup/markdown" 19 "code.gitea.io/gitea/modules/setting" 20 "code.gitea.io/gitea/modules/templates" 21 "code.gitea.io/gitea/modules/util" 22 23 "github.com/gorilla/feeds" 24 ) 25 26 func toBranchLink(ctx *context.Context, act *activities_model.Action) string { 27 return act.GetRepoAbsoluteLink(ctx) + "/src/branch/" + util.PathEscapeSegments(act.GetBranch()) 28 } 29 30 func toTagLink(ctx *context.Context, act *activities_model.Action) string { 31 return act.GetRepoAbsoluteLink(ctx) + "/src/tag/" + util.PathEscapeSegments(act.GetTag()) 32 } 33 34 func toIssueLink(ctx *context.Context, act *activities_model.Action) string { 35 return act.GetRepoAbsoluteLink(ctx) + "/issues/" + url.PathEscape(act.GetIssueInfos()[0]) 36 } 37 38 func toPullLink(ctx *context.Context, act *activities_model.Action) string { 39 return act.GetRepoAbsoluteLink(ctx) + "/pulls/" + url.PathEscape(act.GetIssueInfos()[0]) 40 } 41 42 func toSrcLink(ctx *context.Context, act *activities_model.Action) string { 43 return act.GetRepoAbsoluteLink(ctx) + "/src/" + util.PathEscapeSegments(act.GetBranch()) 44 } 45 46 func toReleaseLink(ctx *context.Context, act *activities_model.Action) string { 47 return act.GetRepoAbsoluteLink(ctx) + "/releases/tag/" + util.PathEscapeSegments(act.GetBranch()) 48 } 49 50 // renderMarkdown creates a minimal markdown render context from an action. 51 // If rendering fails, the original markdown text is returned 52 func renderMarkdown(ctx *context.Context, act *activities_model.Action, content string) string { 53 markdownCtx := &markup.RenderContext{ 54 Ctx: ctx, 55 Links: markup.Links{ 56 Base: act.GetRepoLink(ctx), 57 }, 58 Type: markdown.MarkupName, 59 Metas: map[string]string{ 60 "user": act.GetRepoUserName(ctx), 61 "repo": act.GetRepoName(ctx), 62 }, 63 } 64 markdown, err := markdown.RenderString(markdownCtx, content) 65 if err != nil { 66 return content 67 } 68 return markdown 69 } 70 71 // feedActionsToFeedItems convert gitea's Action feed to feeds Item 72 func feedActionsToFeedItems(ctx *context.Context, actions activities_model.ActionList) (items []*feeds.Item, err error) { 73 for _, act := range actions { 74 act.LoadActUser(ctx) 75 76 var content, desc, title string 77 78 link := &feeds.Link{Href: act.GetCommentHTMLURL(ctx)} 79 80 // title 81 title = act.ActUser.DisplayName() + " " 82 switch act.OpType { 83 case activities_model.ActionCreateRepo: 84 title += ctx.TrHTMLEscapeArgs("action.create_repo", act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx)) 85 link.Href = act.GetRepoAbsoluteLink(ctx) 86 case activities_model.ActionRenameRepo: 87 title += ctx.TrHTMLEscapeArgs("action.rename_repo", act.GetContent(), act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx)) 88 link.Href = act.GetRepoAbsoluteLink(ctx) 89 case activities_model.ActionCommitRepo: 90 link.Href = toBranchLink(ctx, act) 91 if len(act.Content) != 0 { 92 title += ctx.TrHTMLEscapeArgs("action.commit_repo", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetBranch(), act.ShortRepoPath(ctx)) 93 } else { 94 title += ctx.TrHTMLEscapeArgs("action.create_branch", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetBranch(), act.ShortRepoPath(ctx)) 95 } 96 case activities_model.ActionCreateIssue: 97 link.Href = toIssueLink(ctx, act) 98 title += ctx.TrHTMLEscapeArgs("action.create_issue", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) 99 case activities_model.ActionCreatePullRequest: 100 link.Href = toPullLink(ctx, act) 101 title += ctx.TrHTMLEscapeArgs("action.create_pull_request", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) 102 case activities_model.ActionTransferRepo: 103 link.Href = act.GetRepoAbsoluteLink(ctx) 104 title += ctx.TrHTMLEscapeArgs("action.transfer_repo", act.GetContent(), act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx)) 105 case activities_model.ActionPushTag: 106 link.Href = toTagLink(ctx, act) 107 title += ctx.TrHTMLEscapeArgs("action.push_tag", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetTag(), act.ShortRepoPath(ctx)) 108 case activities_model.ActionCommentIssue: 109 issueLink := toIssueLink(ctx, act) 110 if link.Href == "#" { 111 link.Href = issueLink 112 } 113 title += ctx.TrHTMLEscapeArgs("action.comment_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) 114 case activities_model.ActionMergePullRequest: 115 pullLink := toPullLink(ctx, act) 116 if link.Href == "#" { 117 link.Href = pullLink 118 } 119 title += ctx.TrHTMLEscapeArgs("action.merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) 120 case activities_model.ActionAutoMergePullRequest: 121 pullLink := toPullLink(ctx, act) 122 if link.Href == "#" { 123 link.Href = pullLink 124 } 125 title += ctx.TrHTMLEscapeArgs("action.auto_merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) 126 case activities_model.ActionCloseIssue: 127 issueLink := toIssueLink(ctx, act) 128 if link.Href == "#" { 129 link.Href = issueLink 130 } 131 title += ctx.TrHTMLEscapeArgs("action.close_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) 132 case activities_model.ActionReopenIssue: 133 issueLink := toIssueLink(ctx, act) 134 if link.Href == "#" { 135 link.Href = issueLink 136 } 137 title += ctx.TrHTMLEscapeArgs("action.reopen_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) 138 case activities_model.ActionClosePullRequest: 139 pullLink := toPullLink(ctx, act) 140 if link.Href == "#" { 141 link.Href = pullLink 142 } 143 title += ctx.TrHTMLEscapeArgs("action.close_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) 144 case activities_model.ActionReopenPullRequest: 145 pullLink := toPullLink(ctx, act) 146 if link.Href == "#" { 147 link.Href = pullLink 148 } 149 title += ctx.TrHTMLEscapeArgs("action.reopen_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) 150 case activities_model.ActionDeleteTag: 151 link.Href = act.GetRepoAbsoluteLink(ctx) 152 title += ctx.TrHTMLEscapeArgs("action.delete_tag", act.GetRepoAbsoluteLink(ctx), act.GetTag(), act.ShortRepoPath(ctx)) 153 case activities_model.ActionDeleteBranch: 154 link.Href = act.GetRepoAbsoluteLink(ctx) 155 title += ctx.TrHTMLEscapeArgs("action.delete_branch", act.GetRepoAbsoluteLink(ctx), html.EscapeString(act.GetBranch()), act.ShortRepoPath(ctx)) 156 case activities_model.ActionMirrorSyncPush: 157 srcLink := toSrcLink(ctx, act) 158 if link.Href == "#" { 159 link.Href = srcLink 160 } 161 title += ctx.TrHTMLEscapeArgs("action.mirror_sync_push", act.GetRepoAbsoluteLink(ctx), srcLink, act.GetBranch(), act.ShortRepoPath(ctx)) 162 case activities_model.ActionMirrorSyncCreate: 163 srcLink := toSrcLink(ctx, act) 164 if link.Href == "#" { 165 link.Href = srcLink 166 } 167 title += ctx.TrHTMLEscapeArgs("action.mirror_sync_create", act.GetRepoAbsoluteLink(ctx), srcLink, act.GetBranch(), act.ShortRepoPath(ctx)) 168 case activities_model.ActionMirrorSyncDelete: 169 link.Href = act.GetRepoAbsoluteLink(ctx) 170 title += ctx.TrHTMLEscapeArgs("action.mirror_sync_delete", act.GetRepoAbsoluteLink(ctx), act.GetBranch(), act.ShortRepoPath(ctx)) 171 case activities_model.ActionApprovePullRequest: 172 pullLink := toPullLink(ctx, act) 173 title += ctx.TrHTMLEscapeArgs("action.approve_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) 174 case activities_model.ActionRejectPullRequest: 175 pullLink := toPullLink(ctx, act) 176 title += ctx.TrHTMLEscapeArgs("action.reject_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) 177 case activities_model.ActionCommentPull: 178 pullLink := toPullLink(ctx, act) 179 title += ctx.TrHTMLEscapeArgs("action.comment_pull", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) 180 case activities_model.ActionPublishRelease: 181 releaseLink := toReleaseLink(ctx, act) 182 if link.Href == "#" { 183 link.Href = releaseLink 184 } 185 title += ctx.TrHTMLEscapeArgs("action.publish_release", act.GetRepoAbsoluteLink(ctx), releaseLink, act.ShortRepoPath(ctx), act.Content) 186 case activities_model.ActionPullReviewDismissed: 187 pullLink := toPullLink(ctx, act) 188 title += ctx.TrHTMLEscapeArgs("action.review_dismissed", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx), act.GetIssueInfos()[1]) 189 case activities_model.ActionStarRepo: 190 link.Href = act.GetRepoAbsoluteLink(ctx) 191 title += ctx.TrHTMLEscapeArgs("action.starred_repo", act.GetRepoAbsoluteLink(ctx), act.GetRepoPath(ctx)) 192 case activities_model.ActionWatchRepo: 193 link.Href = act.GetRepoAbsoluteLink(ctx) 194 title += ctx.TrHTMLEscapeArgs("action.watched_repo", act.GetRepoAbsoluteLink(ctx), act.GetRepoPath(ctx)) 195 default: 196 return nil, fmt.Errorf("unknown action type: %v", act.OpType) 197 } 198 199 // description & content 200 { 201 switch act.OpType { 202 case activities_model.ActionCommitRepo, activities_model.ActionMirrorSyncPush: 203 push := templates.ActionContent2Commits(act) 204 205 for _, commit := range push.Commits { 206 if len(desc) != 0 { 207 desc += "\n\n" 208 } 209 desc += fmt.Sprintf("<a href=\"%s\">%s</a>\n%s", 210 html.EscapeString(fmt.Sprintf("%s/commit/%s", act.GetRepoAbsoluteLink(ctx), commit.Sha1)), 211 commit.Sha1, 212 templates.RenderCommitMessage(ctx, commit.Message, nil), 213 ) 214 } 215 216 if push.Len > 1 { 217 link = &feeds.Link{Href: fmt.Sprintf("%s/%s", setting.AppSubURL, push.CompareURL)} 218 } else if push.Len == 1 { 219 link = &feeds.Link{Href: fmt.Sprintf("%s/commit/%s", act.GetRepoAbsoluteLink(ctx), push.Commits[0].Sha1)} 220 } 221 222 case activities_model.ActionCreateIssue, activities_model.ActionCreatePullRequest: 223 desc = strings.Join(act.GetIssueInfos(), "#") 224 content = renderMarkdown(ctx, act, act.GetIssueContent(ctx)) 225 case activities_model.ActionCommentIssue, activities_model.ActionApprovePullRequest, activities_model.ActionRejectPullRequest, activities_model.ActionCommentPull: 226 desc = act.GetIssueTitle(ctx) 227 comment := act.GetIssueInfos()[1] 228 if len(comment) != 0 { 229 desc += "\n\n" + renderMarkdown(ctx, act, comment) 230 } 231 case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest: 232 desc = act.GetIssueInfos()[1] 233 case activities_model.ActionCloseIssue, activities_model.ActionReopenIssue, activities_model.ActionClosePullRequest, activities_model.ActionReopenPullRequest: 234 desc = act.GetIssueTitle(ctx) 235 case activities_model.ActionPullReviewDismissed: 236 desc = ctx.Tr("action.review_dismissed_reason") + "\n\n" + act.GetIssueInfos()[2] 237 } 238 } 239 if len(content) == 0 { 240 content = desc 241 } 242 243 items = append(items, &feeds.Item{ 244 Title: title, 245 Link: link, 246 Description: desc, 247 Author: &feeds.Author{ 248 Name: act.ActUser.DisplayName(), 249 Email: act.ActUser.GetEmail(), 250 }, 251 Id: fmt.Sprintf("%v: %v", strconv.FormatInt(act.ID, 10), link.Href), 252 Created: act.CreatedUnix.AsTime(), 253 Content: content, 254 }) 255 } 256 return items, err 257 } 258 259 // GetFeedType return if it is a feed request and altered name and feed type. 260 func GetFeedType(name string, req *http.Request) (bool, string, string) { 261 if strings.HasSuffix(name, ".rss") || 262 strings.Contains(req.Header.Get("Accept"), "application/rss+xml") { 263 return true, strings.TrimSuffix(name, ".rss"), "rss" 264 } 265 266 if strings.HasSuffix(name, ".atom") || 267 strings.Contains(req.Header.Get("Accept"), "application/atom+xml") { 268 return true, strings.TrimSuffix(name, ".atom"), "atom" 269 } 270 271 return false, name, "" 272 } 273 274 // feedActionsToFeedItems convert gitea's Repo's Releases to feeds Item 275 func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release, isReleasesOnly bool) (items []*feeds.Item, err error) { 276 for _, rel := range releases { 277 err := rel.LoadAttributes(ctx) 278 if err != nil { 279 return nil, err 280 } 281 282 var title, content string 283 284 if rel.IsTag { 285 title = rel.TagName 286 } else { 287 title = rel.Title 288 } 289 290 link := &feeds.Link{Href: rel.HTMLURL()} 291 content, err = markdown.RenderString(&markup.RenderContext{ 292 Ctx: ctx, 293 Links: markup.Links{ 294 Base: rel.Repo.Link(), 295 }, 296 Metas: rel.Repo.ComposeMetas(), 297 }, rel.Note) 298 299 if err != nil { 300 return nil, err 301 } 302 303 items = append(items, &feeds.Item{ 304 Title: title, 305 Link: link, 306 Created: rel.CreatedUnix.AsTime(), 307 Author: &feeds.Author{ 308 Name: rel.Publisher.DisplayName(), 309 Email: rel.Publisher.GetEmail(), 310 }, 311 Id: fmt.Sprintf("%v: %v", strconv.FormatInt(rel.ID, 10), link.Href), 312 Content: content, 313 }) 314 } 315 316 return items, err 317 }