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  }