1// Copyright 2021 The Gitea Authors. All rights reserved.
2// Use of this source code is governed by a MIT-style
3// license that can be found in the LICENSE file.
4
5package feed
6
7import (
8	"fmt"
9	"html"
10	"net/url"
11	"strconv"
12	"strings"
13
14	"code.gitea.io/gitea/models"
15	"code.gitea.io/gitea/modules/context"
16	"code.gitea.io/gitea/modules/setting"
17	"code.gitea.io/gitea/modules/templates"
18	"code.gitea.io/gitea/modules/util"
19
20	"github.com/gorilla/feeds"
21)
22
23func toBranchLink(act *models.Action) string {
24	return act.GetRepoLink() + "/src/branch/" + util.PathEscapeSegments(act.GetBranch())
25}
26
27func toTagLink(act *models.Action) string {
28	return act.GetRepoLink() + "/src/tag/" + util.PathEscapeSegments(act.GetTag())
29}
30
31func toIssueLink(act *models.Action) string {
32	return act.GetRepoLink() + "/issues/" + url.PathEscape(act.GetIssueInfos()[0])
33}
34
35func toPullLink(act *models.Action) string {
36	return act.GetRepoLink() + "/pulls/" + url.PathEscape(act.GetIssueInfos()[0])
37}
38
39func toSrcLink(act *models.Action) string {
40	return act.GetRepoLink() + "/src/" + util.PathEscapeSegments(act.GetBranch())
41}
42
43func toReleaseLink(act *models.Action) string {
44	return act.GetRepoLink() + "/releases/tag/" + util.PathEscapeSegments(act.GetBranch())
45}
46
47// feedActionsToFeedItems convert gitea's Action feed to feeds Item
48func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (items []*feeds.Item, err error) {
49	for _, act := range actions {
50		act.LoadActUser()
51
52		content, desc, title := "", "", ""
53
54		link := &feeds.Link{Href: act.GetCommentLink()}
55
56		// title
57		title = act.ActUser.DisplayName() + " "
58		switch act.OpType {
59		case models.ActionCreateRepo:
60			title += ctx.TrHTMLEscapeArgs("action.create_repo", act.GetRepoLink(), act.ShortRepoPath())
61			link.Href = act.GetRepoLink()
62		case models.ActionRenameRepo:
63			title += ctx.TrHTMLEscapeArgs("action.rename_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath())
64			link.Href = act.GetRepoLink()
65		case models.ActionCommitRepo:
66			link.Href = toBranchLink(act)
67			if len(act.Content) != 0 {
68				title += ctx.TrHTMLEscapeArgs("action.commit_repo", act.GetRepoLink(), link.Href, act.GetBranch(), act.ShortRepoPath())
69			} else {
70				title += ctx.TrHTMLEscapeArgs("action.create_branch", act.GetRepoLink(), link.Href, act.GetBranch(), act.ShortRepoPath())
71			}
72		case models.ActionCreateIssue:
73			link.Href = toIssueLink(act)
74			title += ctx.TrHTMLEscapeArgs("action.create_issue", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath())
75		case models.ActionCreatePullRequest:
76			link.Href = toPullLink(act)
77			title += ctx.TrHTMLEscapeArgs("action.create_pull_request", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath())
78		case models.ActionTransferRepo:
79			link.Href = act.GetRepoLink()
80			title += ctx.TrHTMLEscapeArgs("action.transfer_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath())
81		case models.ActionPushTag:
82			link.Href = toTagLink(act)
83			title += ctx.TrHTMLEscapeArgs("action.push_tag", act.GetRepoLink(), link.Href, act.GetTag(), act.ShortRepoPath())
84		case models.ActionCommentIssue:
85			issueLink := toIssueLink(act)
86			if link.Href == "#" {
87				link.Href = issueLink
88			}
89			title += ctx.TrHTMLEscapeArgs("action.comment_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath())
90		case models.ActionMergePullRequest:
91			pullLink := toPullLink(act)
92			if link.Href == "#" {
93				link.Href = pullLink
94			}
95			title += ctx.TrHTMLEscapeArgs("action.merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath())
96		case models.ActionCloseIssue:
97			issueLink := toIssueLink(act)
98			if link.Href == "#" {
99				link.Href = issueLink
100			}
101			title += ctx.TrHTMLEscapeArgs("action.close_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath())
102		case models.ActionReopenIssue:
103			issueLink := toIssueLink(act)
104			if link.Href == "#" {
105				link.Href = issueLink
106			}
107			title += ctx.TrHTMLEscapeArgs("action.reopen_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath())
108		case models.ActionClosePullRequest:
109			pullLink := toPullLink(act)
110			if link.Href == "#" {
111				link.Href = pullLink
112			}
113			title += ctx.TrHTMLEscapeArgs("action.close_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath())
114		case models.ActionReopenPullRequest:
115			pullLink := toPullLink(act)
116			if link.Href == "#" {
117				link.Href = pullLink
118			}
119			title += ctx.TrHTMLEscapeArgs("action.reopen_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath())
120		case models.ActionDeleteTag:
121			link.Href = act.GetRepoLink()
122			title += ctx.TrHTMLEscapeArgs("action.delete_tag", act.GetRepoLink(), act.GetTag(), act.ShortRepoPath())
123		case models.ActionDeleteBranch:
124			link.Href = act.GetRepoLink()
125			title += ctx.TrHTMLEscapeArgs("action.delete_branch", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath())
126		case models.ActionMirrorSyncPush:
127			srcLink := toSrcLink(act)
128			if link.Href == "#" {
129				link.Href = srcLink
130			}
131			title += ctx.TrHTMLEscapeArgs("action.mirror_sync_push", act.GetRepoLink(), srcLink, act.GetBranch(), act.ShortRepoPath())
132		case models.ActionMirrorSyncCreate:
133			srcLink := toSrcLink(act)
134			if link.Href == "#" {
135				link.Href = srcLink
136			}
137			title += ctx.TrHTMLEscapeArgs("action.mirror_sync_create", act.GetRepoLink(), srcLink, act.GetBranch(), act.ShortRepoPath())
138		case models.ActionMirrorSyncDelete:
139			link.Href = act.GetRepoLink()
140			title += ctx.TrHTMLEscapeArgs("action.mirror_sync_delete", act.GetRepoLink(), act.GetBranch(), act.ShortRepoPath())
141		case models.ActionApprovePullRequest:
142			pullLink := toPullLink(act)
143			title += ctx.TrHTMLEscapeArgs("action.approve_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath())
144		case models.ActionRejectPullRequest:
145			pullLink := toPullLink(act)
146			title += ctx.TrHTMLEscapeArgs("action.reject_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath())
147		case models.ActionCommentPull:
148			pullLink := toPullLink(act)
149			title += ctx.TrHTMLEscapeArgs("action.comment_pull", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath())
150		case models.ActionPublishRelease:
151			releaseLink := toReleaseLink(act)
152			if link.Href == "#" {
153				link.Href = releaseLink
154			}
155			title += ctx.TrHTMLEscapeArgs("action.publish_release", act.GetRepoLink(), releaseLink, act.ShortRepoPath(), act.Content)
156		case models.ActionPullReviewDismissed:
157			pullLink := toPullLink(act)
158			title += ctx.TrHTMLEscapeArgs("action.review_dismissed", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(), act.GetIssueInfos()[1])
159		case models.ActionStarRepo:
160			link.Href = act.GetRepoLink()
161			title += ctx.TrHTMLEscapeArgs("action.starred_repo", act.GetRepoLink(), act.GetRepoPath())
162		case models.ActionWatchRepo:
163			link.Href = act.GetRepoLink()
164			title += ctx.TrHTMLEscapeArgs("action.watched_repo", act.GetRepoLink(), act.GetRepoPath())
165		default:
166			return nil, fmt.Errorf("unknown action type: %v", act.OpType)
167		}
168
169		// description & content
170		{
171			switch act.OpType {
172			case models.ActionCommitRepo, models.ActionMirrorSyncPush:
173				push := templates.ActionContent2Commits(act)
174				repoLink := act.GetRepoLink()
175
176				for _, commit := range push.Commits {
177					if len(desc) != 0 {
178						desc += "\n\n"
179					}
180					desc += fmt.Sprintf("<a href=\"%s\">%s</a>\n%s",
181						html.EscapeString(fmt.Sprintf("%s/commit/%s", act.GetRepoLink(), commit.Sha1)),
182						commit.Sha1,
183						templates.RenderCommitMessage(commit.Message, repoLink, nil),
184					)
185				}
186
187				if push.Len > 1 {
188					link = &feeds.Link{Href: fmt.Sprintf("%s/%s", setting.AppSubURL, push.CompareURL)}
189				} else if push.Len == 1 {
190					link = &feeds.Link{Href: fmt.Sprintf("%s/commit/%s", act.GetRepoLink(), push.Commits[0].Sha1)}
191				}
192
193			case models.ActionCreateIssue, models.ActionCreatePullRequest:
194				desc = strings.Join(act.GetIssueInfos(), "#")
195				content = act.GetIssueContent()
196			case models.ActionCommentIssue, models.ActionApprovePullRequest, models.ActionRejectPullRequest, models.ActionCommentPull:
197				desc = act.GetIssueTitle()
198				comment := act.GetIssueInfos()[1]
199				if len(comment) != 0 {
200					desc += "\n\n" + comment
201				}
202			case models.ActionMergePullRequest:
203				desc = act.GetIssueInfos()[1]
204			case models.ActionCloseIssue, models.ActionReopenIssue, models.ActionClosePullRequest, models.ActionReopenPullRequest:
205				desc = act.GetIssueTitle()
206			case models.ActionPullReviewDismissed:
207				desc = ctx.Tr("action.review_dismissed_reason") + "\n\n" + act.GetIssueInfos()[2]
208			}
209		}
210		if len(content) == 0 {
211			content = desc
212		}
213
214		items = append(items, &feeds.Item{
215			Title:       title,
216			Link:        link,
217			Description: desc,
218			Author: &feeds.Author{
219				Name:  act.ActUser.DisplayName(),
220				Email: act.ActUser.GetEmail(),
221			},
222			Id:      strconv.FormatInt(act.ID, 10),
223			Created: act.CreatedUnix.AsTime(),
224			Content: content,
225		})
226	}
227	return
228}
229