mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Add RSS/Atom feed support for user actions (#16002)
Return rss/atom feed for user based on rss url suffix or Content-Type header.
This commit is contained in:
		
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							| @@ -57,6 +57,7 @@ require ( | ||||
| 	github.com/google/go-querystring v1.1.0 // indirect | ||||
| 	github.com/google/uuid v1.2.0 | ||||
| 	github.com/gorilla/context v1.1.1 | ||||
| 	github.com/gorilla/feeds v1.1.1 | ||||
| 	github.com/gorilla/mux v1.8.0 // indirect | ||||
| 	github.com/gorilla/sessions v1.2.1 // indirect | ||||
| 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect | ||||
|   | ||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							| @@ -598,6 +598,8 @@ github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8 | ||||
| github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= | ||||
| github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= | ||||
| github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= | ||||
| github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY= | ||||
| github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA= | ||||
| github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= | ||||
| github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= | ||||
| github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= | ||||
|   | ||||
| @@ -320,7 +320,7 @@ func (ctx *Context) PlainText(status int, bs []byte) { | ||||
| 	ctx.Resp.WriteHeader(status) | ||||
| 	ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8") | ||||
| 	if _, err := ctx.Resp.Write(bs); err != nil { | ||||
| 		ctx.ServerError("Render JSON failed", err) | ||||
| 		ctx.ServerError("Write bytes failed", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -228,6 +228,7 @@ view_home = View %s | ||||
| search_repos = Find a repository… | ||||
| filter = Other Filters | ||||
| filter_by_team_repositories = Filter by team repositories | ||||
| feed_of = Feed of "%s" | ||||
|  | ||||
| show_archived = Archived | ||||
| show_both_archived_unarchived = Showing both archived and unarchived | ||||
| @@ -2777,6 +2778,8 @@ publish_release  = `released <a href="%s/releases/tag/%s"> "%[4]s" </a> at <a hr | ||||
| review_dismissed = `dismissed review from <b>%[4]s</b> for <a href="%[1]s/pulls/%[2]s">%[3]s#%[2]s</a>` | ||||
| review_dismissed_reason = Reason: | ||||
| create_branch = created branch <a href="%[1]s/src/branch/%[2]s">%[3]s</a> in <a href="%[1]s">%[4]s</a> | ||||
| stared_repo = stared <a href="%[1]s">%[2]s</a> | ||||
| watched_repo = started watching <a href="%[1]s">%[2]s</a> | ||||
|  | ||||
| [tool] | ||||
| ago = %s ago | ||||
|   | ||||
							
								
								
									
										154
									
								
								routers/web/feed/convert.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								routers/web/feed/convert.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,154 @@ | ||||
| // Copyright 2021 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package feed | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"html" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"code.gitea.io/gitea/models" | ||||
| 	"code.gitea.io/gitea/modules/context" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/templates" | ||||
|  | ||||
| 	"github.com/gorilla/feeds" | ||||
| ) | ||||
|  | ||||
| // feedActionsToFeedItems convert gitea's Action feed to feeds Item | ||||
| func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (items []*feeds.Item, err error) { | ||||
| 	for _, act := range actions { | ||||
| 		act.LoadActUser() | ||||
|  | ||||
| 		content, desc, title := "", "", "" | ||||
|  | ||||
| 		link := &feeds.Link{Href: act.GetCommentLink()} | ||||
|  | ||||
| 		// title | ||||
| 		title = act.ActUser.DisplayName() + " " | ||||
| 		switch act.OpType { | ||||
| 		case models.ActionCreateRepo: | ||||
| 			title += ctx.Tr("action.create_repo", act.GetRepoLink(), act.ShortRepoPath()) | ||||
| 		case models.ActionRenameRepo: | ||||
| 			title += ctx.Tr("action.rename_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath()) | ||||
| 		case models.ActionCommitRepo: | ||||
| 			branchLink := act.GetBranch() | ||||
| 			if len(act.Content) != 0 { | ||||
| 				title += ctx.Tr("action.commit_repo", act.GetRepoLink(), branchLink, act.GetBranch(), act.ShortRepoPath()) | ||||
| 			} else { | ||||
| 				title += ctx.Tr("action.create_branch", act.GetRepoLink(), branchLink, act.GetBranch(), act.ShortRepoPath()) | ||||
| 			} | ||||
| 		case models.ActionCreateIssue: | ||||
| 			title += ctx.Tr("action.create_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) | ||||
| 		case models.ActionCreatePullRequest: | ||||
| 			title += ctx.Tr("action.create_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) | ||||
| 		case models.ActionTransferRepo: | ||||
| 			title += ctx.Tr("action.transfer_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath()) | ||||
| 		case models.ActionPushTag: | ||||
| 			title += ctx.Tr("action.push_tag", act.GetRepoLink(), url.QueryEscape(act.GetTag()), act.ShortRepoPath()) | ||||
| 		case models.ActionCommentIssue: | ||||
| 			title += ctx.Tr("action.comment_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) | ||||
| 		case models.ActionMergePullRequest: | ||||
| 			title += ctx.Tr("action.merge_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) | ||||
| 		case models.ActionCloseIssue: | ||||
| 			title += ctx.Tr("action.close_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) | ||||
| 		case models.ActionReopenIssue: | ||||
| 			title += ctx.Tr("action.reopen_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) | ||||
| 		case models.ActionClosePullRequest: | ||||
| 			title += ctx.Tr("action.close_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) | ||||
| 		case models.ActionReopenPullRequest: | ||||
| 			title += ctx.Tr("action.reopen_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath) | ||||
| 		case models.ActionDeleteTag: | ||||
| 			title += ctx.Tr("action.delete_tag", act.GetRepoLink(), html.EscapeString(act.GetTag()), act.ShortRepoPath()) | ||||
| 		case models.ActionDeleteBranch: | ||||
| 			title += ctx.Tr("action.delete_branch", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) | ||||
| 		case models.ActionMirrorSyncPush: | ||||
| 			title += ctx.Tr("action.mirror_sync_push", act.GetRepoLink(), url.QueryEscape(act.GetBranch()), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) | ||||
| 		case models.ActionMirrorSyncCreate: | ||||
| 			title += ctx.Tr("action.mirror_sync_create", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) | ||||
| 		case models.ActionMirrorSyncDelete: | ||||
| 			title += ctx.Tr("action.mirror_sync_delete", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) | ||||
| 		case models.ActionApprovePullRequest: | ||||
| 			title += ctx.Tr("action.approve_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) | ||||
| 		case models.ActionRejectPullRequest: | ||||
| 			title += ctx.Tr("action.reject_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) | ||||
| 		case models.ActionCommentPull: | ||||
| 			title += ctx.Tr("action.comment_pull", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) | ||||
| 		case models.ActionPublishRelease: | ||||
| 			title += ctx.Tr("action.publish_release", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath(), act.Content) | ||||
| 		case models.ActionPullReviewDismissed: | ||||
| 			title += ctx.Tr("action.review_dismissed", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath(), act.GetIssueInfos()[1]) | ||||
| 		case models.ActionStarRepo: | ||||
| 			title += ctx.Tr("action.stared_repo", act.GetRepoLink(), act.GetRepoPath()) | ||||
| 			link = &feeds.Link{Href: act.GetRepoLink()} | ||||
| 		case models.ActionWatchRepo: | ||||
| 			title += ctx.Tr("action.watched_repo", act.GetRepoLink(), act.GetRepoPath()) | ||||
| 			link = &feeds.Link{Href: act.GetRepoLink()} | ||||
| 		default: | ||||
| 			return nil, fmt.Errorf("unknown action type: %v", act.OpType) | ||||
| 		} | ||||
|  | ||||
| 		// description & content | ||||
| 		{ | ||||
| 			switch act.OpType { | ||||
| 			case models.ActionCommitRepo, models.ActionMirrorSyncPush: | ||||
| 				push := templates.ActionContent2Commits(act) | ||||
| 				repoLink := act.GetRepoLink() | ||||
|  | ||||
| 				for _, commit := range push.Commits { | ||||
| 					if len(desc) != 0 { | ||||
| 						desc += "\n\n" | ||||
| 					} | ||||
| 					desc += fmt.Sprintf("<a href=\"%s\">%s</a>\n%s", | ||||
| 						fmt.Sprintf("%s/commit/%s", act.GetRepoLink(), commit.Sha1), | ||||
| 						commit.Sha1, | ||||
| 						templates.RenderCommitMessage(commit.Message, repoLink, nil), | ||||
| 					) | ||||
| 				} | ||||
|  | ||||
| 				if push.Len > 1 { | ||||
| 					link = &feeds.Link{Href: fmt.Sprintf("%s/%s", setting.AppSubURL, push.CompareURL)} | ||||
| 				} else if push.Len == 1 { | ||||
| 					link = &feeds.Link{Href: fmt.Sprintf("%s/commit/%s", act.GetRepoLink(), push.Commits[0].Sha1)} | ||||
| 				} | ||||
|  | ||||
| 			case models.ActionCreateIssue, models.ActionCreatePullRequest: | ||||
| 				desc = strings.Join(act.GetIssueInfos(), "#") | ||||
| 				content = act.GetIssueContent() | ||||
| 			case models.ActionCommentIssue, models.ActionApprovePullRequest, models.ActionRejectPullRequest, models.ActionCommentPull: | ||||
| 				desc = act.GetIssueTitle() | ||||
| 				comment := act.GetIssueInfos()[1] | ||||
| 				if len(comment) != 0 { | ||||
| 					desc += "\n\n" + comment | ||||
| 				} | ||||
| 			case models.ActionMergePullRequest: | ||||
| 				desc = act.GetIssueInfos()[1] | ||||
| 			case models.ActionCloseIssue, models.ActionReopenIssue, models.ActionClosePullRequest, models.ActionReopenPullRequest: | ||||
| 				desc = act.GetIssueTitle() | ||||
| 			case models.ActionPullReviewDismissed: | ||||
| 				desc = ctx.Tr("action.review_dismissed_reason") + "\n\n" + act.GetIssueInfos()[2] | ||||
| 			} | ||||
| 		} | ||||
| 		if len(content) == 0 { | ||||
| 			content = desc | ||||
| 		} | ||||
|  | ||||
| 		items = append(items, &feeds.Item{ | ||||
| 			Title:       title, | ||||
| 			Link:        link, | ||||
| 			Description: desc, | ||||
| 			Author: &feeds.Author{ | ||||
| 				Name:  act.ActUser.DisplayName(), | ||||
| 				Email: act.ActUser.GetEmail(), | ||||
| 			}, | ||||
| 			Id:      strconv.FormatInt(act.ID, 10), | ||||
| 			Created: act.CreatedUnix.AsTime(), | ||||
| 			Content: content, | ||||
| 		}) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										98
									
								
								routers/web/feed/profile.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								routers/web/feed/profile.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | ||||
| // Copyright 2021 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package feed | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| 	"code.gitea.io/gitea/models" | ||||
| 	"code.gitea.io/gitea/modules/context" | ||||
|  | ||||
| 	"github.com/gorilla/feeds" | ||||
| ) | ||||
|  | ||||
| // RetrieveFeeds loads feeds for the specified user | ||||
| func RetrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) []*models.Action { | ||||
| 	actions, err := models.GetFeeds(options) | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("GetFeeds", err) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	userCache := map[int64]*models.User{options.RequestedUser.ID: options.RequestedUser} | ||||
| 	if ctx.User != nil { | ||||
| 		userCache[ctx.User.ID] = ctx.User | ||||
| 	} | ||||
| 	for _, act := range actions { | ||||
| 		if act.ActUser != nil { | ||||
| 			userCache[act.ActUserID] = act.ActUser | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for _, act := range actions { | ||||
| 		repoOwner, ok := userCache[act.Repo.OwnerID] | ||||
| 		if !ok { | ||||
| 			repoOwner, err = models.GetUserByID(act.Repo.OwnerID) | ||||
| 			if err != nil { | ||||
| 				if models.IsErrUserNotExist(err) { | ||||
| 					continue | ||||
| 				} | ||||
| 				ctx.ServerError("GetUserByID", err) | ||||
| 				return nil | ||||
| 			} | ||||
| 			userCache[repoOwner.ID] = repoOwner | ||||
| 		} | ||||
| 		act.Repo.Owner = repoOwner | ||||
| 	} | ||||
| 	return actions | ||||
| } | ||||
|  | ||||
| // ShowUserFeed show user activity as RSS / Atom feed | ||||
| func ShowUserFeed(ctx *context.Context, ctxUser *models.User, formatType string) { | ||||
| 	actions := RetrieveFeeds(ctx, models.GetFeedsOptions{ | ||||
| 		RequestedUser:   ctxUser, | ||||
| 		Actor:           ctx.User, | ||||
| 		IncludePrivate:  false, | ||||
| 		OnlyPerformedBy: true, | ||||
| 		IncludeDeleted:  false, | ||||
| 		Date:            ctx.FormString("date"), | ||||
| 	}) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	feed := &feeds.Feed{ | ||||
| 		Title:       ctx.Tr("home.feed_of", ctxUser.DisplayName()), | ||||
| 		Link:        &feeds.Link{Href: ctxUser.HTMLURL()}, | ||||
| 		Description: ctxUser.Description, | ||||
| 		Created:     time.Now(), | ||||
| 	} | ||||
|  | ||||
| 	var err error | ||||
| 	feed.Items, err = feedActionsToFeedItems(ctx, actions) | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("convert feed", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	writeFeed(ctx, feed, formatType) | ||||
| } | ||||
|  | ||||
| // writeFeed write a feeds.Feed as atom or rss to ctx.Resp | ||||
| func writeFeed(ctx *context.Context, feed *feeds.Feed, formatType string) { | ||||
| 	ctx.Resp.WriteHeader(http.StatusOK) | ||||
| 	if formatType == "atom" { | ||||
| 		ctx.Resp.Header().Set("Content-Type", "application/atom+xml;charset=utf-8") | ||||
| 		if err := feed.WriteAtom(ctx.Resp); err != nil { | ||||
| 			ctx.ServerError("Render Atom failed", err) | ||||
| 		} | ||||
| 	} else { | ||||
| 		ctx.Resp.Header().Set("Content-Type", "application/rss+xml;charset=utf-8") | ||||
| 		if err := feed.WriteRss(ctx.Resp); err != nil { | ||||
| 			ctx.ServerError("Render RSS failed", err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -25,6 +25,7 @@ import ( | ||||
| 	"code.gitea.io/gitea/modules/markup/markdown" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/util" | ||||
| 	"code.gitea.io/gitea/routers/web/feed" | ||||
| 	issue_service "code.gitea.io/gitea/services/issue" | ||||
| 	pull_service "code.gitea.io/gitea/services/pull" | ||||
|  | ||||
| @@ -60,42 +61,6 @@ func getDashboardContextUser(ctx *context.Context) *models.User { | ||||
| 	return ctxUser | ||||
| } | ||||
|  | ||||
| // retrieveFeeds loads feeds for the specified user | ||||
| func retrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) { | ||||
| 	actions, err := models.GetFeeds(options) | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("GetFeeds", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	userCache := map[int64]*models.User{options.RequestedUser.ID: options.RequestedUser} | ||||
| 	if ctx.User != nil { | ||||
| 		userCache[ctx.User.ID] = ctx.User | ||||
| 	} | ||||
| 	for _, act := range actions { | ||||
| 		if act.ActUser != nil { | ||||
| 			userCache[act.ActUserID] = act.ActUser | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for _, act := range actions { | ||||
| 		repoOwner, ok := userCache[act.Repo.OwnerID] | ||||
| 		if !ok { | ||||
| 			repoOwner, err = models.GetUserByID(act.Repo.OwnerID) | ||||
| 			if err != nil { | ||||
| 				if models.IsErrUserNotExist(err) { | ||||
| 					continue | ||||
| 				} | ||||
| 				ctx.ServerError("GetUserByID", err) | ||||
| 				return | ||||
| 			} | ||||
| 			userCache[repoOwner.ID] = repoOwner | ||||
| 		} | ||||
| 		act.Repo.Owner = repoOwner | ||||
| 	} | ||||
| 	ctx.Data["Feeds"] = actions | ||||
| } | ||||
|  | ||||
| // Dashboard render the dashboard page | ||||
| func Dashboard(ctx *context.Context) { | ||||
| 	ctxUser := getDashboardContextUser(ctx) | ||||
| @@ -154,7 +119,7 @@ func Dashboard(ctx *context.Context) { | ||||
| 	ctx.Data["MirrorCount"] = len(mirrors) | ||||
| 	ctx.Data["Mirrors"] = mirrors | ||||
|  | ||||
| 	retrieveFeeds(ctx, models.GetFeedsOptions{ | ||||
| 	ctx.Data["Feeds"] = feed.RetrieveFeeds(ctx, models.GetFeedsOptions{ | ||||
| 		RequestedUser:   ctxUser, | ||||
| 		RequestedTeam:   ctx.Org.Team, | ||||
| 		Actor:           ctx.User, | ||||
| @@ -167,6 +132,7 @@ func Dashboard(ctx *context.Context) { | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	ctx.HTML(http.StatusOK, tplDashboard) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -18,6 +18,7 @@ import ( | ||||
| 	"code.gitea.io/gitea/modules/markup/markdown" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/util" | ||||
| 	"code.gitea.io/gitea/routers/web/feed" | ||||
| 	"code.gitea.io/gitea/routers/web/org" | ||||
| ) | ||||
|  | ||||
| @@ -71,12 +72,35 @@ func Profile(ctx *context.Context) { | ||||
| 		uname = strings.TrimSuffix(uname, ".gpg") | ||||
| 	} | ||||
|  | ||||
| 	showFeedType := "" | ||||
| 	if strings.HasSuffix(uname, ".rss") { | ||||
| 		showFeedType = "rss" | ||||
| 		uname = strings.TrimSuffix(uname, ".rss") | ||||
| 	} else if strings.Contains(ctx.Req.Header.Get("Accept"), "application/rss+xml") { | ||||
| 		showFeedType = "rss" | ||||
| 	} | ||||
| 	if strings.HasSuffix(uname, ".atom") { | ||||
| 		showFeedType = "atom" | ||||
| 		uname = strings.TrimSuffix(uname, ".atom") | ||||
| 	} else if strings.Contains(ctx.Req.Header.Get("Accept"), "application/atom+xml") { | ||||
| 		showFeedType = "atom" | ||||
| 	} | ||||
|  | ||||
| 	ctxUser := GetUserByName(ctx, uname) | ||||
| 	if ctx.Written() { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if ctxUser.IsOrganization() { | ||||
| 		/* | ||||
| 			// TODO: enable after rss.RetrieveFeeds() do handle org correctly | ||||
| 			// Show Org RSS feed | ||||
| 			if len(showFeedType) != 0 { | ||||
| 				rss.ShowUserFeed(ctx, ctxUser, showFeedType) | ||||
| 				return | ||||
| 			} | ||||
| 		*/ | ||||
|  | ||||
| 		org.Home(ctx) | ||||
| 		return | ||||
| 	} | ||||
| @@ -99,6 +123,12 @@ func Profile(ctx *context.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Show User RSS feed | ||||
| 	if len(showFeedType) != 0 { | ||||
| 		feed.ShowUserFeed(ctx, ctxUser, showFeedType) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Show OpenID URIs | ||||
| 	openIDs, err := models.GetUserOpenIDs(ctxUser.ID) | ||||
| 	if err != nil { | ||||
| @@ -217,7 +247,7 @@ func Profile(ctx *context.Context) { | ||||
|  | ||||
| 		total = ctxUser.NumFollowing | ||||
| 	case "activity": | ||||
| 		retrieveFeeds(ctx, models.GetFeedsOptions{RequestedUser: ctxUser, | ||||
| 		ctx.Data["Feeds"] = feed.RetrieveFeeds(ctx, models.GetFeedsOptions{RequestedUser: ctxUser, | ||||
| 			Actor:           ctx.User, | ||||
| 			IncludePrivate:  showPrivate, | ||||
| 			OnlyPerformedBy: true, | ||||
|   | ||||
							
								
								
									
										16
									
								
								vendor/github.com/gorilla/feeds/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								vendor/github.com/gorilla/feeds/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| language: go | ||||
| sudo: false | ||||
| matrix: | ||||
|   include: | ||||
|     - go: 1.8 | ||||
|     - go: 1.9 | ||||
|     - go: "1.10" | ||||
|     - go: 1.x | ||||
|     - go: tip | ||||
|   allow_failures: | ||||
|     - go: tip | ||||
| script: | ||||
|   - go get -t -v ./... | ||||
|   - diff -u <(echo -n) <(gofmt -d -s .) | ||||
|   - go vet . | ||||
|   - go test -v -race ./... | ||||
							
								
								
									
										29
									
								
								vendor/github.com/gorilla/feeds/AUTHORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								vendor/github.com/gorilla/feeds/AUTHORS
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| # This is the official list of gorilla/feeds authors for copyright purposes. | ||||
| # Please keep the list sorted. | ||||
|  | ||||
| Dmitry Chestnykh <dmitry@codingrobots.com> | ||||
| Eddie Scholtz <eascholtz@gmail.com> | ||||
| Gabriel Simmer <bladesimmer@gmail.com> | ||||
| Google LLC (https://opensource.google.com/) | ||||
| honky <honky@defendtheplanet.net> | ||||
| James Gregory <james@jagregory.com> | ||||
| Jason Hall <imjasonh@gmail.com> | ||||
| Jason Moiron <jmoiron@jmoiron.net> | ||||
| Kamil Kisiel <kamil@kamilkisiel.net> | ||||
| Kevin Stock <kevinstock@tantalic.com> | ||||
| Markus Zimmermann <markus.zimmermann@nethead.at> | ||||
| Matt Silverlock <matt@eatsleeprepeat.net> | ||||
| Matthew Dawson <matthew@mjdsystems.ca> | ||||
| Milan Aleksic <milanaleksic@gmail.com> | ||||
| Milan Aleksić <milanaleksic@gmail.com> | ||||
| nlimpid <jshuangzl@gmail.com> | ||||
| Paul Petring <paul@defendtheplanet.net> | ||||
| Sean Enck <enckse@users.noreply.github.com> | ||||
| Sue Spence <virtuallysue@gmail.com> | ||||
| Supermighty <ukiah@faction.com> | ||||
| Toru Fukui <fukuimone@gmail.com> | ||||
| Vabd <vabd@anon.acme> | ||||
| Volker <lists.volker@gmail.com> | ||||
| ZhiFeng Hu <hufeng1987@gmail.com> | ||||
| weberc2 <weberc2@gmail.com> | ||||
|  | ||||
							
								
								
									
										22
									
								
								vendor/github.com/gorilla/feeds/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/gorilla/feeds/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| Copyright (c) 2013-2018 The Gorilla Feeds Authors. All rights reserved. | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are met: | ||||
|  | ||||
|   Redistributions of source code must retain the above copyright notice, this | ||||
|   list of conditions and the following disclaimer. | ||||
|  | ||||
|   Redistributions in binary form must reproduce the above copyright notice, | ||||
|   this list of conditions and the following disclaimer in the documentation | ||||
|   and/or other materials provided with the distribution. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||||
| ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||||
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
| FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
| DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
| OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
							
								
								
									
										185
									
								
								vendor/github.com/gorilla/feeds/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								vendor/github.com/gorilla/feeds/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,185 @@ | ||||
| ## gorilla/feeds | ||||
| [](https://godoc.org/github.com/gorilla/feeds) | ||||
| [](https://travis-ci.org/gorilla/feeds) | ||||
|  | ||||
| feeds is a web feed generator library for generating RSS, Atom and JSON feeds from Go | ||||
| applications. | ||||
|  | ||||
| ### Goals | ||||
|  | ||||
|  * Provide a simple interface to create both Atom & RSS 2.0 feeds | ||||
|  * Full support for [Atom][atom], [RSS 2.0][rss], and [JSON Feed Version 1][jsonfeed] spec elements | ||||
|  * Ability to modify particulars for each spec | ||||
|  | ||||
| [atom]: https://tools.ietf.org/html/rfc4287 | ||||
| [rss]: http://www.rssboard.org/rss-specification | ||||
| [jsonfeed]: https://jsonfeed.org/version/1 | ||||
|  | ||||
| ### Usage | ||||
|  | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
|     "fmt" | ||||
|     "log" | ||||
|     "time" | ||||
|     "github.com/gorilla/feeds" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
|     now := time.Now() | ||||
|     feed := &feeds.Feed{ | ||||
|         Title:       "jmoiron.net blog", | ||||
|         Link:        &feeds.Link{Href: "http://jmoiron.net/blog"}, | ||||
|         Description: "discussion about tech, footie, photos", | ||||
|         Author:      &feeds.Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"}, | ||||
|         Created:     now, | ||||
|     } | ||||
|  | ||||
|     feed.Items = []*feeds.Item{ | ||||
|         &feeds.Item{ | ||||
|             Title:       "Limiting Concurrency in Go", | ||||
|             Link:        &feeds.Link{Href: "http://jmoiron.net/blog/limiting-concurrency-in-go/"}, | ||||
|             Description: "A discussion on controlled parallelism in golang", | ||||
|             Author:      &feeds.Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"}, | ||||
|             Created:     now, | ||||
|         }, | ||||
|         &feeds.Item{ | ||||
|             Title:       "Logic-less Template Redux", | ||||
|             Link:        &feeds.Link{Href: "http://jmoiron.net/blog/logicless-template-redux/"}, | ||||
|             Description: "More thoughts on logicless templates", | ||||
|             Created:     now, | ||||
|         }, | ||||
|         &feeds.Item{ | ||||
|             Title:       "Idiomatic Code Reuse in Go", | ||||
|             Link:        &feeds.Link{Href: "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/"}, | ||||
|             Description: "How to use interfaces <em>effectively</em>", | ||||
|             Created:     now, | ||||
|         }, | ||||
|     } | ||||
|  | ||||
|     atom, err := feed.ToAtom() | ||||
|     if err != nil { | ||||
|         log.Fatal(err) | ||||
|     } | ||||
|  | ||||
|     rss, err := feed.ToRss() | ||||
|     if err != nil { | ||||
|         log.Fatal(err) | ||||
|     } | ||||
|  | ||||
|     json, err := feed.ToJSON() | ||||
|     if err != nil { | ||||
|         log.Fatal(err) | ||||
|     } | ||||
|  | ||||
|     fmt.Println(atom, "\n", rss, "\n", json) | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Outputs: | ||||
|  | ||||
| ```xml | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <feed xmlns="http://www.w3.org/2005/Atom"> | ||||
|   <title>jmoiron.net blog</title> | ||||
|   <link href="http://jmoiron.net/blog"></link> | ||||
|   <id>http://jmoiron.net/blog</id> | ||||
|   <updated>2013-01-16T03:26:01-05:00</updated> | ||||
|   <summary>discussion about tech, footie, photos</summary> | ||||
|   <entry> | ||||
|     <title>Limiting Concurrency in Go</title> | ||||
|     <link href="http://jmoiron.net/blog/limiting-concurrency-in-go/"></link> | ||||
|     <updated>2013-01-16T03:26:01-05:00</updated> | ||||
|     <id>tag:jmoiron.net,2013-01-16:/blog/limiting-concurrency-in-go/</id> | ||||
|     <summary type="html">A discussion on controlled parallelism in golang</summary> | ||||
|     <author> | ||||
|       <name>Jason Moiron</name> | ||||
|       <email>jmoiron@jmoiron.net</email> | ||||
|     </author> | ||||
|   </entry> | ||||
|   <entry> | ||||
|     <title>Logic-less Template Redux</title> | ||||
|     <link href="http://jmoiron.net/blog/logicless-template-redux/"></link> | ||||
|     <updated>2013-01-16T03:26:01-05:00</updated> | ||||
|     <id>tag:jmoiron.net,2013-01-16:/blog/logicless-template-redux/</id> | ||||
|     <summary type="html">More thoughts on logicless templates</summary> | ||||
|     <author></author> | ||||
|   </entry> | ||||
|   <entry> | ||||
|     <title>Idiomatic Code Reuse in Go</title> | ||||
|     <link href="http://jmoiron.net/blog/idiomatic-code-reuse-in-go/"></link> | ||||
|     <updated>2013-01-16T03:26:01-05:00</updated> | ||||
|     <id>tag:jmoiron.net,2013-01-16:/blog/idiomatic-code-reuse-in-go/</id> | ||||
|     <summary type="html">How to use interfaces <em>effectively</em></summary> | ||||
|     <author></author> | ||||
|   </entry> | ||||
| </feed> | ||||
|  | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <rss version="2.0"> | ||||
|   <channel> | ||||
|     <title>jmoiron.net blog</title> | ||||
|     <link>http://jmoiron.net/blog</link> | ||||
|     <description>discussion about tech, footie, photos</description> | ||||
|     <managingEditor>jmoiron@jmoiron.net (Jason Moiron)</managingEditor> | ||||
|     <pubDate>2013-01-16T03:22:24-05:00</pubDate> | ||||
|     <item> | ||||
|       <title>Limiting Concurrency in Go</title> | ||||
|       <link>http://jmoiron.net/blog/limiting-concurrency-in-go/</link> | ||||
|       <description>A discussion on controlled parallelism in golang</description> | ||||
|       <pubDate>2013-01-16T03:22:24-05:00</pubDate> | ||||
|     </item> | ||||
|     <item> | ||||
|       <title>Logic-less Template Redux</title> | ||||
|       <link>http://jmoiron.net/blog/logicless-template-redux/</link> | ||||
|       <description>More thoughts on logicless templates</description> | ||||
|       <pubDate>2013-01-16T03:22:24-05:00</pubDate> | ||||
|     </item> | ||||
|     <item> | ||||
|       <title>Idiomatic Code Reuse in Go</title> | ||||
|       <link>http://jmoiron.net/blog/idiomatic-code-reuse-in-go/</link> | ||||
|       <description>How to use interfaces <em>effectively</em></description> | ||||
|       <pubDate>2013-01-16T03:22:24-05:00</pubDate> | ||||
|     </item> | ||||
|   </channel> | ||||
| </rss> | ||||
|  | ||||
| { | ||||
|   "version": "https://jsonfeed.org/version/1", | ||||
|   "title": "jmoiron.net blog", | ||||
|   "home_page_url": "http://jmoiron.net/blog", | ||||
|   "description": "discussion about tech, footie, photos", | ||||
|   "author": { | ||||
|     "name": "Jason Moiron" | ||||
|   }, | ||||
|   "items": [ | ||||
|     { | ||||
|       "id": "", | ||||
|       "url": "http://jmoiron.net/blog/limiting-concurrency-in-go/", | ||||
|       "title": "Limiting Concurrency in Go", | ||||
|       "summary": "A discussion on controlled parallelism in golang", | ||||
|       "date_published": "2013-01-16T03:22:24.530817846-05:00", | ||||
|       "author": { | ||||
|         "name": "Jason Moiron" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "id": "", | ||||
|       "url": "http://jmoiron.net/blog/logicless-template-redux/", | ||||
|       "title": "Logic-less Template Redux", | ||||
|       "summary": "More thoughts on logicless templates", | ||||
|       "date_published": "2013-01-16T03:22:24.530817846-05:00" | ||||
|     }, | ||||
|     { | ||||
|       "id": "", | ||||
|       "url": "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/", | ||||
|       "title": "Idiomatic Code Reuse in Go", | ||||
|       "summary": "How to use interfaces \u003cem\u003eeffectively\u003c/em\u003e", | ||||
|       "date_published": "2013-01-16T03:22:24.530817846-05:00" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| ``` | ||||
|  | ||||
							
								
								
									
										169
									
								
								vendor/github.com/gorilla/feeds/atom.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								vendor/github.com/gorilla/feeds/atom.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,169 @@ | ||||
| package feeds | ||||
|  | ||||
| import ( | ||||
| 	"encoding/xml" | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // Generates Atom feed as XML | ||||
|  | ||||
| const ns = "http://www.w3.org/2005/Atom" | ||||
|  | ||||
| type AtomPerson struct { | ||||
| 	Name  string `xml:"name,omitempty"` | ||||
| 	Uri   string `xml:"uri,omitempty"` | ||||
| 	Email string `xml:"email,omitempty"` | ||||
| } | ||||
|  | ||||
| type AtomSummary struct { | ||||
| 	XMLName xml.Name `xml:"summary"` | ||||
| 	Content string   `xml:",chardata"` | ||||
| 	Type    string   `xml:"type,attr"` | ||||
| } | ||||
|  | ||||
| type AtomContent struct { | ||||
| 	XMLName xml.Name `xml:"content"` | ||||
| 	Content string   `xml:",chardata"` | ||||
| 	Type    string   `xml:"type,attr"` | ||||
| } | ||||
|  | ||||
| type AtomAuthor struct { | ||||
| 	XMLName xml.Name `xml:"author"` | ||||
| 	AtomPerson | ||||
| } | ||||
|  | ||||
| type AtomContributor struct { | ||||
| 	XMLName xml.Name `xml:"contributor"` | ||||
| 	AtomPerson | ||||
| } | ||||
|  | ||||
| type AtomEntry struct { | ||||
| 	XMLName     xml.Name `xml:"entry"` | ||||
| 	Xmlns       string   `xml:"xmlns,attr,omitempty"` | ||||
| 	Title       string   `xml:"title"`   // required | ||||
| 	Updated     string   `xml:"updated"` // required | ||||
| 	Id          string   `xml:"id"`      // required | ||||
| 	Category    string   `xml:"category,omitempty"` | ||||
| 	Content     *AtomContent | ||||
| 	Rights      string `xml:"rights,omitempty"` | ||||
| 	Source      string `xml:"source,omitempty"` | ||||
| 	Published   string `xml:"published,omitempty"` | ||||
| 	Contributor *AtomContributor | ||||
| 	Links       []AtomLink   // required if no child 'content' elements | ||||
| 	Summary     *AtomSummary // required if content has src or content is base64 | ||||
| 	Author      *AtomAuthor  // required if feed lacks an author | ||||
| } | ||||
|  | ||||
| // Multiple links with different rel can coexist | ||||
| type AtomLink struct { | ||||
| 	//Atom 1.0 <link rel="enclosure" type="audio/mpeg" title="MP3" href="http://www.example.org/myaudiofile.mp3" length="1234" /> | ||||
| 	XMLName xml.Name `xml:"link"` | ||||
| 	Href    string   `xml:"href,attr"` | ||||
| 	Rel     string   `xml:"rel,attr,omitempty"` | ||||
| 	Type    string   `xml:"type,attr,omitempty"` | ||||
| 	Length  string   `xml:"length,attr,omitempty"` | ||||
| } | ||||
|  | ||||
| type AtomFeed struct { | ||||
| 	XMLName     xml.Name `xml:"feed"` | ||||
| 	Xmlns       string   `xml:"xmlns,attr"` | ||||
| 	Title       string   `xml:"title"`   // required | ||||
| 	Id          string   `xml:"id"`      // required | ||||
| 	Updated     string   `xml:"updated"` // required | ||||
| 	Category    string   `xml:"category,omitempty"` | ||||
| 	Icon        string   `xml:"icon,omitempty"` | ||||
| 	Logo        string   `xml:"logo,omitempty"` | ||||
| 	Rights      string   `xml:"rights,omitempty"` // copyright used | ||||
| 	Subtitle    string   `xml:"subtitle,omitempty"` | ||||
| 	Link        *AtomLink | ||||
| 	Author      *AtomAuthor `xml:"author,omitempty"` | ||||
| 	Contributor *AtomContributor | ||||
| 	Entries     []*AtomEntry `xml:"entry"` | ||||
| } | ||||
|  | ||||
| type Atom struct { | ||||
| 	*Feed | ||||
| } | ||||
|  | ||||
| func newAtomEntry(i *Item) *AtomEntry { | ||||
| 	id := i.Id | ||||
| 	// assume the description is html | ||||
| 	s := &AtomSummary{Content: i.Description, Type: "html"} | ||||
|  | ||||
| 	if len(id) == 0 { | ||||
| 		// if there's no id set, try to create one, either from data or just a uuid | ||||
| 		if len(i.Link.Href) > 0 && (!i.Created.IsZero() || !i.Updated.IsZero()) { | ||||
| 			dateStr := anyTimeFormat("2006-01-02", i.Updated, i.Created) | ||||
| 			host, path := i.Link.Href, "/invalid.html" | ||||
| 			if url, err := url.Parse(i.Link.Href); err == nil { | ||||
| 				host, path = url.Host, url.Path | ||||
| 			} | ||||
| 			id = fmt.Sprintf("tag:%s,%s:%s", host, dateStr, path) | ||||
| 		} else { | ||||
| 			id = "urn:uuid:" + NewUUID().String() | ||||
| 		} | ||||
| 	} | ||||
| 	var name, email string | ||||
| 	if i.Author != nil { | ||||
| 		name, email = i.Author.Name, i.Author.Email | ||||
| 	} | ||||
|  | ||||
| 	link_rel := i.Link.Rel | ||||
| 	if link_rel == "" { | ||||
| 		link_rel = "alternate" | ||||
| 	} | ||||
| 	x := &AtomEntry{ | ||||
| 		Title:   i.Title, | ||||
| 		Links:   []AtomLink{{Href: i.Link.Href, Rel: link_rel, Type: i.Link.Type}}, | ||||
| 		Id:      id, | ||||
| 		Updated: anyTimeFormat(time.RFC3339, i.Updated, i.Created), | ||||
| 		Summary: s, | ||||
| 	} | ||||
|  | ||||
| 	// if there's a content, assume it's html | ||||
| 	if len(i.Content) > 0 { | ||||
| 		x.Content = &AtomContent{Content: i.Content, Type: "html"} | ||||
| 	} | ||||
|  | ||||
| 	if i.Enclosure != nil && link_rel != "enclosure" { | ||||
| 		x.Links = append(x.Links, AtomLink{Href: i.Enclosure.Url, Rel: "enclosure", Type: i.Enclosure.Type, Length: i.Enclosure.Length}) | ||||
| 	} | ||||
|  | ||||
| 	if len(name) > 0 || len(email) > 0 { | ||||
| 		x.Author = &AtomAuthor{AtomPerson: AtomPerson{Name: name, Email: email}} | ||||
| 	} | ||||
| 	return x | ||||
| } | ||||
|  | ||||
| // create a new AtomFeed with a generic Feed struct's data | ||||
| func (a *Atom) AtomFeed() *AtomFeed { | ||||
| 	updated := anyTimeFormat(time.RFC3339, a.Updated, a.Created) | ||||
| 	feed := &AtomFeed{ | ||||
| 		Xmlns:    ns, | ||||
| 		Title:    a.Title, | ||||
| 		Link:     &AtomLink{Href: a.Link.Href, Rel: a.Link.Rel}, | ||||
| 		Subtitle: a.Description, | ||||
| 		Id:       a.Link.Href, | ||||
| 		Updated:  updated, | ||||
| 		Rights:   a.Copyright, | ||||
| 	} | ||||
| 	if a.Author != nil { | ||||
| 		feed.Author = &AtomAuthor{AtomPerson: AtomPerson{Name: a.Author.Name, Email: a.Author.Email}} | ||||
| 	} | ||||
| 	for _, e := range a.Items { | ||||
| 		feed.Entries = append(feed.Entries, newAtomEntry(e)) | ||||
| 	} | ||||
| 	return feed | ||||
| } | ||||
|  | ||||
| // FeedXml returns an XML-Ready object for an Atom object | ||||
| func (a *Atom) FeedXml() interface{} { | ||||
| 	return a.AtomFeed() | ||||
| } | ||||
|  | ||||
| // FeedXml returns an XML-ready object for an AtomFeed object | ||||
| func (a *AtomFeed) FeedXml() interface{} { | ||||
| 	return a | ||||
| } | ||||
							
								
								
									
										73
									
								
								vendor/github.com/gorilla/feeds/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								vendor/github.com/gorilla/feeds/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| /* | ||||
| Syndication (feed) generator library for golang. | ||||
|  | ||||
| Installing | ||||
|  | ||||
| 	go get github.com/gorilla/feeds | ||||
|  | ||||
| Feeds provides a simple, generic Feed interface with a generic Item object as well as RSS, Atom and JSON Feed specific RssFeed, AtomFeed and JSONFeed objects which allow access to all of each spec's defined elements. | ||||
|  | ||||
| Examples | ||||
|  | ||||
| Create a Feed and some Items in that feed using the generic interfaces: | ||||
|  | ||||
| 	import ( | ||||
| 		"time" | ||||
| 		. "github.com/gorilla/feeds" | ||||
| 	) | ||||
|  | ||||
| 	now = time.Now() | ||||
|  | ||||
| 	feed := &Feed{ | ||||
| 		Title:       "jmoiron.net blog", | ||||
| 		Link:        &Link{Href: "http://jmoiron.net/blog"}, | ||||
| 		Description: "discussion about tech, footie, photos", | ||||
| 		Author:      &Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"}, | ||||
| 		Created:     now, | ||||
| 		Copyright:   "This work is copyright © Benjamin Button", | ||||
| 	} | ||||
|  | ||||
| 	feed.Items = []*Item{ | ||||
| 		&Item{ | ||||
| 			Title:       "Limiting Concurrency in Go", | ||||
| 			Link:        &Link{Href: "http://jmoiron.net/blog/limiting-concurrency-in-go/"}, | ||||
| 			Description: "A discussion on controlled parallelism in golang", | ||||
| 			Author:      &Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"}, | ||||
| 			Created:     now, | ||||
| 		}, | ||||
| 		&Item{ | ||||
| 			Title:       "Logic-less Template Redux", | ||||
| 			Link:        &Link{Href: "http://jmoiron.net/blog/logicless-template-redux/"}, | ||||
| 			Description: "More thoughts on logicless templates", | ||||
| 			Created:     now, | ||||
| 		}, | ||||
| 		&Item{ | ||||
| 			Title:       "Idiomatic Code Reuse in Go", | ||||
| 			Link:        &Link{Href: "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/"}, | ||||
| 			Description: "How to use interfaces <em>effectively</em>", | ||||
| 			Created:     now, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| From here, you can output Atom, RSS, or JSON Feed versions of this feed easily | ||||
|  | ||||
| 	atom, err := feed.ToAtom() | ||||
| 	rss, err := feed.ToRss() | ||||
| 	json, err := feed.ToJSON() | ||||
|  | ||||
| You can also get access to the underlying objects that feeds uses to export its XML | ||||
|  | ||||
| 	atomFeed := (&Atom{Feed: feed}).AtomFeed() | ||||
| 	rssFeed := (&Rss{Feed: feed}).RssFeed() | ||||
| 	jsonFeed := (&JSON{Feed: feed}).JSONFeed() | ||||
|  | ||||
| From here, you can modify or add each syndication's specific fields before outputting | ||||
|  | ||||
| 	atomFeed.Subtitle = "plays the blues" | ||||
| 	atom, err := ToXML(atomFeed) | ||||
| 	rssFeed.Generator = "gorilla/feeds v1.0 (github.com/gorilla/feeds)" | ||||
| 	rss, err := ToXML(rssFeed) | ||||
| 	jsonFeed.NextUrl = "https://www.example.com/feed.json?page=2" | ||||
| 	json, err := jsonFeed.ToJSON() | ||||
| */ | ||||
| package feeds | ||||
							
								
								
									
										145
									
								
								vendor/github.com/gorilla/feeds/feed.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								vendor/github.com/gorilla/feeds/feed.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,145 @@ | ||||
| package feeds | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"encoding/xml" | ||||
| 	"io" | ||||
| 	"sort" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type Link struct { | ||||
| 	Href, Rel, Type, Length string | ||||
| } | ||||
|  | ||||
| type Author struct { | ||||
| 	Name, Email string | ||||
| } | ||||
|  | ||||
| type Image struct { | ||||
| 	Url, Title, Link string | ||||
| 	Width, Height    int | ||||
| } | ||||
|  | ||||
| type Enclosure struct { | ||||
| 	Url, Length, Type string | ||||
| } | ||||
|  | ||||
| type Item struct { | ||||
| 	Title       string | ||||
| 	Link        *Link | ||||
| 	Source      *Link | ||||
| 	Author      *Author | ||||
| 	Description string // used as description in rss, summary in atom | ||||
| 	Id          string // used as guid in rss, id in atom | ||||
| 	Updated     time.Time | ||||
| 	Created     time.Time | ||||
| 	Enclosure   *Enclosure | ||||
| 	Content     string | ||||
| } | ||||
|  | ||||
| type Feed struct { | ||||
| 	Title       string | ||||
| 	Link        *Link | ||||
| 	Description string | ||||
| 	Author      *Author | ||||
| 	Updated     time.Time | ||||
| 	Created     time.Time | ||||
| 	Id          string | ||||
| 	Subtitle    string | ||||
| 	Items       []*Item | ||||
| 	Copyright   string | ||||
| 	Image       *Image | ||||
| } | ||||
|  | ||||
| // add a new Item to a Feed | ||||
| func (f *Feed) Add(item *Item) { | ||||
| 	f.Items = append(f.Items, item) | ||||
| } | ||||
|  | ||||
| // returns the first non-zero time formatted as a string or "" | ||||
| func anyTimeFormat(format string, times ...time.Time) string { | ||||
| 	for _, t := range times { | ||||
| 		if !t.IsZero() { | ||||
| 			return t.Format(format) | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| // interface used by ToXML to get a object suitable for exporting XML. | ||||
| type XmlFeed interface { | ||||
| 	FeedXml() interface{} | ||||
| } | ||||
|  | ||||
| // turn a feed object (either a Feed, AtomFeed, or RssFeed) into xml | ||||
| // returns an error if xml marshaling fails | ||||
| func ToXML(feed XmlFeed) (string, error) { | ||||
| 	x := feed.FeedXml() | ||||
| 	data, err := xml.MarshalIndent(x, "", "  ") | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	// strip empty line from default xml header | ||||
| 	s := xml.Header[:len(xml.Header)-1] + string(data) | ||||
| 	return s, nil | ||||
| } | ||||
|  | ||||
| // WriteXML writes a feed object (either a Feed, AtomFeed, or RssFeed) as XML into | ||||
| // the writer. Returns an error if XML marshaling fails. | ||||
| func WriteXML(feed XmlFeed, w io.Writer) error { | ||||
| 	x := feed.FeedXml() | ||||
| 	// write default xml header, without the newline | ||||
| 	if _, err := w.Write([]byte(xml.Header[:len(xml.Header)-1])); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	e := xml.NewEncoder(w) | ||||
| 	e.Indent("", "  ") | ||||
| 	return e.Encode(x) | ||||
| } | ||||
|  | ||||
| // creates an Atom representation of this feed | ||||
| func (f *Feed) ToAtom() (string, error) { | ||||
| 	a := &Atom{f} | ||||
| 	return ToXML(a) | ||||
| } | ||||
|  | ||||
| // WriteAtom writes an Atom representation of this feed to the writer. | ||||
| func (f *Feed) WriteAtom(w io.Writer) error { | ||||
| 	return WriteXML(&Atom{f}, w) | ||||
| } | ||||
|  | ||||
| // creates an Rss representation of this feed | ||||
| func (f *Feed) ToRss() (string, error) { | ||||
| 	r := &Rss{f} | ||||
| 	return ToXML(r) | ||||
| } | ||||
|  | ||||
| // WriteRss writes an RSS representation of this feed to the writer. | ||||
| func (f *Feed) WriteRss(w io.Writer) error { | ||||
| 	return WriteXML(&Rss{f}, w) | ||||
| } | ||||
|  | ||||
| // ToJSON creates a JSON Feed representation of this feed | ||||
| func (f *Feed) ToJSON() (string, error) { | ||||
| 	j := &JSON{f} | ||||
| 	return j.ToJSON() | ||||
| } | ||||
|  | ||||
| // WriteJSON writes an JSON representation of this feed to the writer. | ||||
| func (f *Feed) WriteJSON(w io.Writer) error { | ||||
| 	j := &JSON{f} | ||||
| 	feed := j.JSONFeed() | ||||
|  | ||||
| 	e := json.NewEncoder(w) | ||||
| 	e.SetIndent("", "  ") | ||||
| 	return e.Encode(feed) | ||||
| } | ||||
|  | ||||
| // Sort sorts the Items in the feed with the given less function. | ||||
| func (f *Feed) Sort(less func(a, b *Item) bool) { | ||||
| 	lessFunc := func(i, j int) bool { | ||||
| 		return less(f.Items[i], f.Items[j]) | ||||
| 	} | ||||
| 	sort.SliceStable(f.Items, lessFunc) | ||||
| } | ||||
							
								
								
									
										183
									
								
								vendor/github.com/gorilla/feeds/json.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								vendor/github.com/gorilla/feeds/json.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,183 @@ | ||||
| package feeds | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| const jsonFeedVersion = "https://jsonfeed.org/version/1" | ||||
|  | ||||
| // JSONAuthor represents the author of the feed or of an individual item | ||||
| // in the feed | ||||
| type JSONAuthor struct { | ||||
| 	Name   string `json:"name,omitempty"` | ||||
| 	Url    string `json:"url,omitempty"` | ||||
| 	Avatar string `json:"avatar,omitempty"` | ||||
| } | ||||
|  | ||||
| // JSONAttachment represents a related resource. Podcasts, for instance, would | ||||
| // include an attachment that’s an audio or video file. | ||||
| type JSONAttachment struct { | ||||
| 	Url      string        `json:"url,omitempty"` | ||||
| 	MIMEType string        `json:"mime_type,omitempty"` | ||||
| 	Title    string        `json:"title,omitempty"` | ||||
| 	Size     int32         `json:"size,omitempty"` | ||||
| 	Duration time.Duration `json:"duration_in_seconds,omitempty"` | ||||
| } | ||||
|  | ||||
| // MarshalJSON implements the json.Marshaler interface. | ||||
| // The Duration field is marshaled in seconds, all other fields are marshaled | ||||
| // based upon the definitions in struct tags. | ||||
| func (a *JSONAttachment) MarshalJSON() ([]byte, error) { | ||||
| 	type EmbeddedJSONAttachment JSONAttachment | ||||
| 	return json.Marshal(&struct { | ||||
| 		Duration float64 `json:"duration_in_seconds,omitempty"` | ||||
| 		*EmbeddedJSONAttachment | ||||
| 	}{ | ||||
| 		EmbeddedJSONAttachment: (*EmbeddedJSONAttachment)(a), | ||||
| 		Duration:               a.Duration.Seconds(), | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // UnmarshalJSON implements the json.Unmarshaler interface. | ||||
| // The Duration field is expected to be in seconds, all other field types | ||||
| // match the struct definition. | ||||
| func (a *JSONAttachment) UnmarshalJSON(data []byte) error { | ||||
| 	type EmbeddedJSONAttachment JSONAttachment | ||||
| 	var raw struct { | ||||
| 		Duration float64 `json:"duration_in_seconds,omitempty"` | ||||
| 		*EmbeddedJSONAttachment | ||||
| 	} | ||||
| 	raw.EmbeddedJSONAttachment = (*EmbeddedJSONAttachment)(a) | ||||
|  | ||||
| 	err := json.Unmarshal(data, &raw) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if raw.Duration > 0 { | ||||
| 		nsec := int64(raw.Duration * float64(time.Second)) | ||||
| 		raw.EmbeddedJSONAttachment.Duration = time.Duration(nsec) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // JSONItem represents a single entry/post for the feed. | ||||
| type JSONItem struct { | ||||
| 	Id            string           `json:"id"` | ||||
| 	Url           string           `json:"url,omitempty"` | ||||
| 	ExternalUrl   string           `json:"external_url,omitempty"` | ||||
| 	Title         string           `json:"title,omitempty"` | ||||
| 	ContentHTML   string           `json:"content_html,omitempty"` | ||||
| 	ContentText   string           `json:"content_text,omitempty"` | ||||
| 	Summary       string           `json:"summary,omitempty"` | ||||
| 	Image         string           `json:"image,omitempty"` | ||||
| 	BannerImage   string           `json:"banner_,omitempty"` | ||||
| 	PublishedDate *time.Time       `json:"date_published,omitempty"` | ||||
| 	ModifiedDate  *time.Time       `json:"date_modified,omitempty"` | ||||
| 	Author        *JSONAuthor      `json:"author,omitempty"` | ||||
| 	Tags          []string         `json:"tags,omitempty"` | ||||
| 	Attachments   []JSONAttachment `json:"attachments,omitempty"` | ||||
| } | ||||
|  | ||||
| // JSONHub describes an endpoint that can be used to subscribe to real-time | ||||
| // notifications from the publisher of this feed. | ||||
| type JSONHub struct { | ||||
| 	Type string `json:"type"` | ||||
| 	Url  string `json:"url"` | ||||
| } | ||||
|  | ||||
| // JSONFeed represents a syndication feed in the JSON Feed Version 1 format. | ||||
| // Matching the specification found here: https://jsonfeed.org/version/1. | ||||
| type JSONFeed struct { | ||||
| 	Version     string      `json:"version"` | ||||
| 	Title       string      `json:"title"` | ||||
| 	HomePageUrl string      `json:"home_page_url,omitempty"` | ||||
| 	FeedUrl     string      `json:"feed_url,omitempty"` | ||||
| 	Description string      `json:"description,omitempty"` | ||||
| 	UserComment string      `json:"user_comment,omitempty"` | ||||
| 	NextUrl     string      `json:"next_url,omitempty"` | ||||
| 	Icon        string      `json:"icon,omitempty"` | ||||
| 	Favicon     string      `json:"favicon,omitempty"` | ||||
| 	Author      *JSONAuthor `json:"author,omitempty"` | ||||
| 	Expired     *bool       `json:"expired,omitempty"` | ||||
| 	Hubs        []*JSONItem `json:"hubs,omitempty"` | ||||
| 	Items       []*JSONItem `json:"items,omitempty"` | ||||
| } | ||||
|  | ||||
| // JSON is used to convert a generic Feed to a JSONFeed. | ||||
| type JSON struct { | ||||
| 	*Feed | ||||
| } | ||||
|  | ||||
| // ToJSON encodes f into a JSON string. Returns an error if marshalling fails. | ||||
| func (f *JSON) ToJSON() (string, error) { | ||||
| 	return f.JSONFeed().ToJSON() | ||||
| } | ||||
|  | ||||
| // ToJSON encodes f into a JSON string. Returns an error if marshalling fails. | ||||
| func (f *JSONFeed) ToJSON() (string, error) { | ||||
| 	data, err := json.MarshalIndent(f, "", "  ") | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	return string(data), nil | ||||
| } | ||||
|  | ||||
| // JSONFeed creates a new JSONFeed with a generic Feed struct's data. | ||||
| func (f *JSON) JSONFeed() *JSONFeed { | ||||
| 	feed := &JSONFeed{ | ||||
| 		Version:     jsonFeedVersion, | ||||
| 		Title:       f.Title, | ||||
| 		Description: f.Description, | ||||
| 	} | ||||
|  | ||||
| 	if f.Link != nil { | ||||
| 		feed.HomePageUrl = f.Link.Href | ||||
| 	} | ||||
| 	if f.Author != nil { | ||||
| 		feed.Author = &JSONAuthor{ | ||||
| 			Name: f.Author.Name, | ||||
| 		} | ||||
| 	} | ||||
| 	for _, e := range f.Items { | ||||
| 		feed.Items = append(feed.Items, newJSONItem(e)) | ||||
| 	} | ||||
| 	return feed | ||||
| } | ||||
|  | ||||
| func newJSONItem(i *Item) *JSONItem { | ||||
| 	item := &JSONItem{ | ||||
| 		Id:      i.Id, | ||||
| 		Title:   i.Title, | ||||
| 		Summary: i.Description, | ||||
|  | ||||
| 		ContentHTML: i.Content, | ||||
| 	} | ||||
|  | ||||
| 	if i.Link != nil { | ||||
| 		item.Url = i.Link.Href | ||||
| 	} | ||||
| 	if i.Source != nil { | ||||
| 		item.ExternalUrl = i.Source.Href | ||||
| 	} | ||||
| 	if i.Author != nil { | ||||
| 		item.Author = &JSONAuthor{ | ||||
| 			Name: i.Author.Name, | ||||
| 		} | ||||
| 	} | ||||
| 	if !i.Created.IsZero() { | ||||
| 		item.PublishedDate = &i.Created | ||||
| 	} | ||||
| 	if !i.Updated.IsZero() { | ||||
| 		item.ModifiedDate = &i.Updated | ||||
| 	} | ||||
| 	if i.Enclosure != nil && strings.HasPrefix(i.Enclosure.Type, "image/") { | ||||
| 		item.Image = i.Enclosure.Url | ||||
| 	} | ||||
|  | ||||
| 	return item | ||||
| } | ||||
							
								
								
									
										168
									
								
								vendor/github.com/gorilla/feeds/rss.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								vendor/github.com/gorilla/feeds/rss.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,168 @@ | ||||
| package feeds | ||||
|  | ||||
| // rss support | ||||
| // validation done according to spec here: | ||||
| //    http://cyber.law.harvard.edu/rss/rss.html | ||||
|  | ||||
| import ( | ||||
| 	"encoding/xml" | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // private wrapper around the RssFeed which gives us the <rss>..</rss> xml | ||||
| type RssFeedXml struct { | ||||
| 	XMLName          xml.Name `xml:"rss"` | ||||
| 	Version          string   `xml:"version,attr"` | ||||
| 	ContentNamespace string   `xml:"xmlns:content,attr"` | ||||
| 	Channel          *RssFeed | ||||
| } | ||||
|  | ||||
| type RssContent struct { | ||||
| 	XMLName xml.Name `xml:"content:encoded"` | ||||
| 	Content string   `xml:",cdata"` | ||||
| } | ||||
|  | ||||
| type RssImage struct { | ||||
| 	XMLName xml.Name `xml:"image"` | ||||
| 	Url     string   `xml:"url"` | ||||
| 	Title   string   `xml:"title"` | ||||
| 	Link    string   `xml:"link"` | ||||
| 	Width   int      `xml:"width,omitempty"` | ||||
| 	Height  int      `xml:"height,omitempty"` | ||||
| } | ||||
|  | ||||
| type RssTextInput struct { | ||||
| 	XMLName     xml.Name `xml:"textInput"` | ||||
| 	Title       string   `xml:"title"` | ||||
| 	Description string   `xml:"description"` | ||||
| 	Name        string   `xml:"name"` | ||||
| 	Link        string   `xml:"link"` | ||||
| } | ||||
|  | ||||
| type RssFeed struct { | ||||
| 	XMLName        xml.Name `xml:"channel"` | ||||
| 	Title          string   `xml:"title"`       // required | ||||
| 	Link           string   `xml:"link"`        // required | ||||
| 	Description    string   `xml:"description"` // required | ||||
| 	Language       string   `xml:"language,omitempty"` | ||||
| 	Copyright      string   `xml:"copyright,omitempty"` | ||||
| 	ManagingEditor string   `xml:"managingEditor,omitempty"` // Author used | ||||
| 	WebMaster      string   `xml:"webMaster,omitempty"` | ||||
| 	PubDate        string   `xml:"pubDate,omitempty"`       // created or updated | ||||
| 	LastBuildDate  string   `xml:"lastBuildDate,omitempty"` // updated used | ||||
| 	Category       string   `xml:"category,omitempty"` | ||||
| 	Generator      string   `xml:"generator,omitempty"` | ||||
| 	Docs           string   `xml:"docs,omitempty"` | ||||
| 	Cloud          string   `xml:"cloud,omitempty"` | ||||
| 	Ttl            int      `xml:"ttl,omitempty"` | ||||
| 	Rating         string   `xml:"rating,omitempty"` | ||||
| 	SkipHours      string   `xml:"skipHours,omitempty"` | ||||
| 	SkipDays       string   `xml:"skipDays,omitempty"` | ||||
| 	Image          *RssImage | ||||
| 	TextInput      *RssTextInput | ||||
| 	Items          []*RssItem `xml:"item"` | ||||
| } | ||||
|  | ||||
| type RssItem struct { | ||||
| 	XMLName     xml.Name `xml:"item"` | ||||
| 	Title       string   `xml:"title"`       // required | ||||
| 	Link        string   `xml:"link"`        // required | ||||
| 	Description string   `xml:"description"` // required | ||||
| 	Content     *RssContent | ||||
| 	Author      string `xml:"author,omitempty"` | ||||
| 	Category    string `xml:"category,omitempty"` | ||||
| 	Comments    string `xml:"comments,omitempty"` | ||||
| 	Enclosure   *RssEnclosure | ||||
| 	Guid        string `xml:"guid,omitempty"`    // Id used | ||||
| 	PubDate     string `xml:"pubDate,omitempty"` // created or updated | ||||
| 	Source      string `xml:"source,omitempty"` | ||||
| } | ||||
|  | ||||
| type RssEnclosure struct { | ||||
| 	//RSS 2.0 <enclosure url="http://example.com/file.mp3" length="123456789" type="audio/mpeg" /> | ||||
| 	XMLName xml.Name `xml:"enclosure"` | ||||
| 	Url     string   `xml:"url,attr"` | ||||
| 	Length  string   `xml:"length,attr"` | ||||
| 	Type    string   `xml:"type,attr"` | ||||
| } | ||||
|  | ||||
| type Rss struct { | ||||
| 	*Feed | ||||
| } | ||||
|  | ||||
| // create a new RssItem with a generic Item struct's data | ||||
| func newRssItem(i *Item) *RssItem { | ||||
| 	item := &RssItem{ | ||||
| 		Title:       i.Title, | ||||
| 		Link:        i.Link.Href, | ||||
| 		Description: i.Description, | ||||
| 		Guid:        i.Id, | ||||
| 		PubDate:     anyTimeFormat(time.RFC1123Z, i.Created, i.Updated), | ||||
| 	} | ||||
| 	if len(i.Content) > 0 { | ||||
| 		item.Content = &RssContent{Content: i.Content} | ||||
| 	} | ||||
| 	if i.Source != nil { | ||||
| 		item.Source = i.Source.Href | ||||
| 	} | ||||
|  | ||||
| 	// Define a closure | ||||
| 	if i.Enclosure != nil && i.Enclosure.Type != "" && i.Enclosure.Length != "" { | ||||
| 		item.Enclosure = &RssEnclosure{Url: i.Enclosure.Url, Type: i.Enclosure.Type, Length: i.Enclosure.Length} | ||||
| 	} | ||||
|  | ||||
| 	if i.Author != nil { | ||||
| 		item.Author = i.Author.Name | ||||
| 	} | ||||
| 	return item | ||||
| } | ||||
|  | ||||
| // create a new RssFeed with a generic Feed struct's data | ||||
| func (r *Rss) RssFeed() *RssFeed { | ||||
| 	pub := anyTimeFormat(time.RFC1123Z, r.Created, r.Updated) | ||||
| 	build := anyTimeFormat(time.RFC1123Z, r.Updated) | ||||
| 	author := "" | ||||
| 	if r.Author != nil { | ||||
| 		author = r.Author.Email | ||||
| 		if len(r.Author.Name) > 0 { | ||||
| 			author = fmt.Sprintf("%s (%s)", r.Author.Email, r.Author.Name) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	var image *RssImage | ||||
| 	if r.Image != nil { | ||||
| 		image = &RssImage{Url: r.Image.Url, Title: r.Image.Title, Link: r.Image.Link, Width: r.Image.Width, Height: r.Image.Height} | ||||
| 	} | ||||
|  | ||||
| 	channel := &RssFeed{ | ||||
| 		Title:          r.Title, | ||||
| 		Link:           r.Link.Href, | ||||
| 		Description:    r.Description, | ||||
| 		ManagingEditor: author, | ||||
| 		PubDate:        pub, | ||||
| 		LastBuildDate:  build, | ||||
| 		Copyright:      r.Copyright, | ||||
| 		Image:          image, | ||||
| 	} | ||||
| 	for _, i := range r.Items { | ||||
| 		channel.Items = append(channel.Items, newRssItem(i)) | ||||
| 	} | ||||
| 	return channel | ||||
| } | ||||
|  | ||||
| // FeedXml returns an XML-Ready object for an Rss object | ||||
| func (r *Rss) FeedXml() interface{} { | ||||
| 	// only generate version 2.0 feeds for now | ||||
| 	return r.RssFeed().FeedXml() | ||||
|  | ||||
| } | ||||
|  | ||||
| // FeedXml returns an XML-ready object for an RssFeed object | ||||
| func (r *RssFeed) FeedXml() interface{} { | ||||
| 	return &RssFeedXml{ | ||||
| 		Version:          "2.0", | ||||
| 		Channel:          r, | ||||
| 		ContentNamespace: "http://purl.org/rss/1.0/modules/content/", | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										92
									
								
								vendor/github.com/gorilla/feeds/test.atom
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								vendor/github.com/gorilla/feeds/test.atom
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <feed xmlns:atom="http://www.w3.org/2005/Atom"> | ||||
|         <title><![CDATA[Lorem ipsum feed for an interval of 1 minutes]]></title> | ||||
|         <description><![CDATA[This is a constantly updating lorem ipsum feed]]></description> | ||||
|         <link>http://example.com/</link> | ||||
|         <generator>RSS for Node</generator> | ||||
|         <lastBuildDate>Tue, 30 Oct 2018 23:22:37 GMT</lastBuildDate> | ||||
|         <author><![CDATA[John Smith]]></author> | ||||
|         <pubDate>Tue, 30 Oct 2018 23:22:00 GMT</pubDate> | ||||
|         <copyright><![CDATA[Michael Bertolacci, licensed under a Creative Commons Attribution 3.0 Unported License.]]></copyright> | ||||
|         <ttl>60</ttl> | ||||
|         <entry> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:22:00+00:00]]></title> | ||||
|             <description><![CDATA[Exercitation ut Lorem sint proident.]]></description> | ||||
|             <link>http://example.com/test/1540941720</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941720</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:22:00 GMT</pubDate> | ||||
|         </entry> | ||||
|         <entry> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:21:00+00:00]]></title> | ||||
|             <description><![CDATA[Ea est do quis fugiat exercitation.]]></description> | ||||
|             <link>http://example.com/test/1540941660</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941660</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:21:00 GMT</pubDate> | ||||
|         </entry> | ||||
|         <entry> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:20:00+00:00]]></title> | ||||
|             <description><![CDATA[Ipsum velit cillum ad laborum sit nulla exercitation consequat sint veniam culpa veniam voluptate incididunt.]]></description> | ||||
|             <link>http://example.com/test/1540941600</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941600</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:20:00 GMT</pubDate> | ||||
|         </entry> | ||||
|         <entry> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:19:00+00:00]]></title> | ||||
|             <description><![CDATA[Ullamco pariatur aliqua consequat ea veniam id qui incididunt laborum.]]></description> | ||||
|             <link>http://example.com/test/1540941540</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941540</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:19:00 GMT</pubDate> | ||||
|         </entry> | ||||
|         <entry> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:18:00+00:00]]></title> | ||||
|             <description><![CDATA[Velit proident aliquip aliquip anim mollit voluptate laboris voluptate et occaecat occaecat laboris ea nulla.]]></description> | ||||
|             <link>http://example.com/test/1540941480</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941480</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:18:00 GMT</pubDate> | ||||
|         </entry> | ||||
|         <entry> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:17:00+00:00]]></title> | ||||
|             <description><![CDATA[Do in quis mollit consequat id in minim laborum sint exercitation laborum elit officia.]]></description> | ||||
|             <link>http://example.com/test/1540941420</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941420</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:17:00 GMT</pubDate> | ||||
|         </entry> | ||||
|         <entry> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:16:00+00:00]]></title> | ||||
|             <description><![CDATA[Irure id sint ullamco Lorem magna consectetur officia adipisicing duis incididunt.]]></description> | ||||
|             <link>http://example.com/test/1540941360</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941360</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:16:00 GMT</pubDate> | ||||
|         </entry> | ||||
|         <entry> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:15:00+00:00]]></title> | ||||
|             <description><![CDATA[Sunt anim excepteur esse nisi commodo culpa laborum exercitation ad anim ex elit.]]></description> | ||||
|             <link>http://example.com/test/1540941300</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941300</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:15:00 GMT</pubDate> | ||||
|         </entry> | ||||
|         <entry> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:14:00+00:00]]></title> | ||||
|             <description><![CDATA[Excepteur aliquip fugiat ex labore nisi.]]></description> | ||||
|             <link>http://example.com/test/1540941240</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941240</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:14:00 GMT</pubDate> | ||||
|         </entry> | ||||
|         <entry> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:13:00+00:00]]></title> | ||||
|             <description><![CDATA[Id proident adipisicing proident pariatur aute pariatur pariatur dolor dolor in voluptate dolor.]]></description> | ||||
|             <link>http://example.com/test/1540941180</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941180</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:13:00 GMT</pubDate> | ||||
|         </entry> | ||||
| </feed> | ||||
							
								
								
									
										96
									
								
								vendor/github.com/gorilla/feeds/test.rss
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								vendor/github.com/gorilla/feeds/test.rss
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <rss xmlns:dc="http://purl.org/dc/elements/1.1/"  | ||||
|     xmlns:content="http://purl.org/rss/1.0/modules/content/"  | ||||
|     xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"> | ||||
|     <channel> | ||||
|         <title><![CDATA[Lorem ipsum feed for an interval of 1 minutes]]></title> | ||||
|         <description><![CDATA[This is a constantly updating lorem ipsum feed]]></description> | ||||
|         <link>http://example.com/</link> | ||||
|         <generator>RSS for Node</generator> | ||||
|         <lastBuildDate>Tue, 30 Oct 2018 23:22:37 GMT</lastBuildDate> | ||||
|         <author><![CDATA[John Smith]]></author> | ||||
|         <pubDate>Tue, 30 Oct 2018 23:22:00 GMT</pubDate> | ||||
|         <copyright><![CDATA[Michael Bertolacci, licensed under a Creative Commons Attribution 3.0 Unported License.]]></copyright> | ||||
|         <ttl>60</ttl> | ||||
|         <item> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:22:00+00:00]]></title> | ||||
|             <description><![CDATA[Exercitation ut Lorem sint proident.]]></description> | ||||
|             <link>http://example.com/test/1540941720</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941720</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:22:00 GMT</pubDate> | ||||
|         </item> | ||||
|         <item> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:21:00+00:00]]></title> | ||||
|             <description><![CDATA[Ea est do quis fugiat exercitation.]]></description> | ||||
|             <link>http://example.com/test/1540941660</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941660</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:21:00 GMT</pubDate> | ||||
|         </item> | ||||
|         <item> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:20:00+00:00]]></title> | ||||
|             <description><![CDATA[Ipsum velit cillum ad laborum sit nulla exercitation consequat sint veniam culpa veniam voluptate incididunt.]]></description> | ||||
|             <link>http://example.com/test/1540941600</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941600</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:20:00 GMT</pubDate> | ||||
|         </item> | ||||
|         <item> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:19:00+00:00]]></title> | ||||
|             <description><![CDATA[Ullamco pariatur aliqua consequat ea veniam id qui incididunt laborum.]]></description> | ||||
|             <link>http://example.com/test/1540941540</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941540</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:19:00 GMT</pubDate> | ||||
|         </item> | ||||
|         <item> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:18:00+00:00]]></title> | ||||
|             <description><![CDATA[Velit proident aliquip aliquip anim mollit voluptate laboris voluptate et occaecat occaecat laboris ea nulla.]]></description> | ||||
|             <link>http://example.com/test/1540941480</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941480</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:18:00 GMT</pubDate> | ||||
|         </item> | ||||
|         <item> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:17:00+00:00]]></title> | ||||
|             <description><![CDATA[Do in quis mollit consequat id in minim laborum sint exercitation laborum elit officia.]]></description> | ||||
|             <link>http://example.com/test/1540941420</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941420</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:17:00 GMT</pubDate> | ||||
|         </item> | ||||
|         <item> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:16:00+00:00]]></title> | ||||
|             <description><![CDATA[Irure id sint ullamco Lorem magna consectetur officia adipisicing duis incididunt.]]></description> | ||||
|             <link>http://example.com/test/1540941360</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941360</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:16:00 GMT</pubDate> | ||||
|         </item> | ||||
|         <item> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:15:00+00:00]]></title> | ||||
|             <description><![CDATA[Sunt anim excepteur esse nisi commodo culpa laborum exercitation ad anim ex elit.]]></description> | ||||
|             <link>http://example.com/test/1540941300</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941300</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:15:00 GMT</pubDate> | ||||
|         </item> | ||||
|         <item> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:14:00+00:00]]></title> | ||||
|             <description><![CDATA[Excepteur aliquip fugiat ex labore nisi.]]></description> | ||||
|             <link>http://example.com/test/1540941240</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941240</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:14:00 GMT</pubDate> | ||||
|         </item> | ||||
|         <item> | ||||
|             <title><![CDATA[Lorem ipsum 2018-10-30T23:13:00+00:00]]></title> | ||||
|             <description><![CDATA[Id proident adipisicing proident pariatur aute pariatur pariatur dolor dolor in voluptate dolor.]]></description> | ||||
|             <link>http://example.com/test/1540941180</link> | ||||
|             <guid isPermaLink="true">http://example.com/test/1540941180</guid> | ||||
|             <dc:creator><![CDATA[John Smith]]></dc:creator> | ||||
|             <pubDate>Tue, 30 Oct 2018 23:13:00 GMT</pubDate> | ||||
|         </item> | ||||
|     </channel> | ||||
| </rss> | ||||
							
								
								
									
										20
									
								
								vendor/github.com/gorilla/feeds/to-implement.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/gorilla/feeds/to-implement.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| [Full iTunes list](https://help.apple.com/itc/podcasts_connect/#/itcb54353390) | ||||
|  | ||||
| [Example of ideal iTunes RSS feed](https://help.apple.com/itc/podcasts_connect/#/itcbaf351599) | ||||
|  | ||||
| ``` | ||||
| <itunes:author> | ||||
| <itunes:block> | ||||
| <itunes:catergory> | ||||
| <itunes:image> | ||||
| <itunes:duration> | ||||
| <itunes:explicit> | ||||
| <itunes:isClosedCaptioned> | ||||
| <itunes:order> | ||||
| <itunes:complete> | ||||
| <itunes:new-feed-url> | ||||
| <itunes:owner> | ||||
| <itunes:subtitle> | ||||
| <itunes:summary> | ||||
| <language> | ||||
| ``` | ||||
							
								
								
									
										27
									
								
								vendor/github.com/gorilla/feeds/uuid.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/gorilla/feeds/uuid.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| package feeds | ||||
|  | ||||
| // relevant bits from https://github.com/abneptis/GoUUID/blob/master/uuid.go | ||||
|  | ||||
| import ( | ||||
| 	"crypto/rand" | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| type UUID [16]byte | ||||
|  | ||||
| // create a new uuid v4 | ||||
| func NewUUID() *UUID { | ||||
| 	u := &UUID{} | ||||
| 	_, err := rand.Read(u[:16]) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	u[8] = (u[8] | 0x80) & 0xBf | ||||
| 	u[6] = (u[6] | 0x40) & 0x4f | ||||
| 	return u | ||||
| } | ||||
|  | ||||
| func (u *UUID) String() string { | ||||
| 	return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], u[8:10], u[10:]) | ||||
| } | ||||
							
								
								
									
										3
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							| @@ -469,6 +469,9 @@ github.com/google/uuid | ||||
| github.com/gorilla/context | ||||
| # github.com/gorilla/css v1.0.0 | ||||
| github.com/gorilla/css/scanner | ||||
| # github.com/gorilla/feeds v1.1.1 | ||||
| ## explicit | ||||
| github.com/gorilla/feeds | ||||
| # github.com/gorilla/handlers v1.5.1 | ||||
| github.com/gorilla/handlers | ||||
| # github.com/gorilla/mux v1.8.0 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user