]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/controllers/feeds.ts
Serve audit logs to client
[github/Chocobozzz/PeerTube.git] / server / controllers / feeds.ts
CommitLineData
244e76a5 1import * as express from 'express'
74dc3bca 2import { FEEDS, ROUTE_CACHE_LIFETIME, THUMBNAILS_SIZE, WEBSERVER } from '../initializers/constants'
1cd3facc
C
3import {
4 asyncMiddleware,
5 commonVideosFiltersValidator,
6 setDefaultSort,
7 videoCommentsFeedsValidator,
8 videoFeedsValidator,
9 videosSortValidator
10} from '../middlewares'
244e76a5
RK
11import { VideoModel } from '../models/video/video'
12import * as Feed from 'pfeed'
98d3324d 13import { cacheRoute } from '../middlewares/cache'
fe3a55b0 14import { VideoCommentModel } from '../models/video/video-comment'
d525fc39 15import { buildNSFWFilter } from '../helpers/express-utils'
6dd9de95 16import { CONFIG } from '../initializers/config'
244e76a5
RK
17
18const feedsRouter = express.Router()
19
fe3a55b0 20feedsRouter.get('/feeds/video-comments.:format',
98d3324d 21 asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)),
fe3a55b0
C
22 asyncMiddleware(videoCommentsFeedsValidator),
23 asyncMiddleware(generateVideoCommentsFeed)
24)
25
244e76a5 26feedsRouter.get('/feeds/videos.:format',
7b87d2d5
C
27 videosSortValidator,
28 setDefaultSort,
98d3324d 29 asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)),
1cd3facc 30 commonVideosFiltersValidator,
fe3a55b0
C
31 asyncMiddleware(videoFeedsValidator),
32 asyncMiddleware(generateVideoFeed)
244e76a5
RK
33)
34
35// ---------------------------------------------------------------------------
36
37export {
38 feedsRouter
39}
40
41// ---------------------------------------------------------------------------
42
dae86118 43async function generateVideoCommentsFeed (req: express.Request, res: express.Response) {
fe3a55b0
C
44 const start = 0
45
453e83ea 46 const video = res.locals.videoAll
749c7247 47 const videoId: number = video ? video.id : undefined
fe3a55b0
C
48
49 const comments = await VideoCommentModel.listForFeed(start, FEEDS.COUNT, videoId)
50
749c7247
C
51 const name = video ? video.name : CONFIG.INSTANCE.NAME
52 const description = video ? video.description : CONFIG.INSTANCE.DESCRIPTION
53 const feed = initFeed(name, description)
54
fe3a55b0
C
55 // Adding video items to the feed, one at a time
56 comments.forEach(comment => {
6dd9de95 57 const link = WEBSERVER.URL + comment.getCommentStaticPath()
4dae00e6 58
65d2ae2a
C
59 let title = comment.Video.name
60 if (comment.Account) title += ` - ${comment.Account.getDisplayName()}`
61
fe3a55b0 62 feed.addItem({
65d2ae2a 63 title,
fe3a55b0 64 id: comment.url,
4dae00e6 65 link,
fe3a55b0
C
66 content: comment.text,
67 author: [
68 {
69 name: comment.Account.getDisplayName(),
70 link: comment.Account.Actor.url
71 }
72 ],
73 date: comment.createdAt
74 })
75 })
76
77 // Now the feed generation is done, let's send it!
78 return sendFeed(feed, req, res)
79}
80
dae86118 81async function generateVideoFeed (req: express.Request, res: express.Response) {
4195cd2b 82 const start = 0
244e76a5 83
dae86118
C
84 const account = res.locals.account
85 const videoChannel = res.locals.videoChannel
d525fc39 86 const nsfw = buildNSFWFilter(res, req.query.nsfw)
244e76a5 87
749c7247
C
88 let name: string
89 let description: string
90
91 if (videoChannel) {
92 name = videoChannel.getDisplayName()
93 description = videoChannel.description
94 } else if (account) {
95 name = account.getDisplayName()
96 description = account.description
97 } else {
98 name = CONFIG.INSTANCE.NAME
99 description = CONFIG.INSTANCE.DESCRIPTION
100 }
101
102 const feed = initFeed(name, description)
103
48dce1c9 104 const resultList = await VideoModel.listForApi({
0626e7af 105 start,
48dce1c9
C
106 count: FEEDS.COUNT,
107 sort: req.query.sort,
06a05d5f 108 includeLocalVideos: true,
d525fc39 109 nsfw,
48dce1c9
C
110 filter: req.query.filter,
111 withFiles: true,
e0ea4b1d
C
112 accountId: account ? account.id : null,
113 videoChannelId: videoChannel ? videoChannel.id : null
48dce1c9 114 })
244e76a5
RK
115
116 // Adding video items to the feed, one at a time
117 resultList.data.forEach(video => {
118 const formattedVideoFiles = video.getFormattedVideoFilesJSON()
c4b4ab71 119
244e76a5
RK
120 const torrents = formattedVideoFiles.map(videoFile => ({
121 title: video.name,
122 url: videoFile.torrentUrl,
123 size_in_bytes: videoFile.size
124 }))
c4b4ab71
C
125
126 const videos = formattedVideoFiles.map(videoFile => {
127 const result = {
128 type: 'video/mp4',
129 medium: 'video',
130 height: videoFile.resolution.label.replace('p', ''),
131 fileSize: videoFile.size,
132 url: videoFile.fileUrl,
133 framerate: videoFile.fps,
134 duration: video.duration
135 }
136
137 if (video.language) Object.assign(result, { lang: video.language })
138
139 return result
140 })
141
142 const categories: { value: number, label: string }[] = []
143 if (video.category) {
144 categories.push({
145 value: video.category,
146 label: VideoModel.getCategoryLabel(video.category)
147 })
148 }
244e76a5
RK
149
150 feed.addItem({
151 title: video.name,
152 id: video.url,
6dd9de95 153 link: WEBSERVER.URL + '/videos/watch/' + video.uuid,
244e76a5
RK
154 description: video.getTruncatedDescription(),
155 content: video.description,
156 author: [
157 {
158 name: video.VideoChannel.Account.getDisplayName(),
159 link: video.VideoChannel.Account.Actor.url
160 }
161 ],
162 date: video.publishedAt,
244e76a5 163 nsfw: video.nsfw,
b81eb8fd 164 torrent: torrents,
16d9224a
RK
165 videos,
166 embed: {
167 url: video.getEmbedStaticPath(),
168 allowFullscreen: true
169 },
170 player: {
171 url: video.getWatchStaticPath()
172 },
c4b4ab71 173 categories,
16d9224a
RK
174 community: {
175 statistics: {
176 views: video.views
177 }
178 },
b81eb8fd
RK
179 thumbnail: [
180 {
3acc5084 181 url: WEBSERVER.URL + video.getMiniatureStaticPath(),
b81eb8fd
RK
182 height: THUMBNAILS_SIZE.height,
183 width: THUMBNAILS_SIZE.width
184 }
185 ]
244e76a5
RK
186 })
187 })
188
189 // Now the feed generation is done, let's send it!
190 return sendFeed(feed, req, res)
191}
192
749c7247 193function initFeed (name: string, description: string) {
6dd9de95 194 const webserverUrl = WEBSERVER.URL
244e76a5
RK
195
196 return new Feed({
749c7247
C
197 title: name,
198 description,
244e76a5
RK
199 // updated: TODO: somehowGetLatestUpdate, // optional, default = today
200 id: webserverUrl,
201 link: webserverUrl,
202 image: webserverUrl + '/client/assets/images/icons/icon-96x96.png',
203 favicon: webserverUrl + '/client/assets/images/favicon.png',
204 copyright: `All rights reserved, unless otherwise specified in the terms specified at ${webserverUrl}/about` +
205 ` and potential licenses granted by each content's rightholder.`,
206 generator: `Toraifōsu`, // ^.~
207 feedLinks: {
208 json: `${webserverUrl}/feeds/videos.json`,
209 atom: `${webserverUrl}/feeds/videos.atom`,
210 rss: `${webserverUrl}/feeds/videos.xml`
211 },
212 author: {
7b87d2d5 213 name: 'Instance admin of ' + CONFIG.INSTANCE.NAME,
244e76a5
RK
214 email: CONFIG.ADMIN.EMAIL,
215 link: `${webserverUrl}/about`
216 }
217 })
218}
219
220function sendFeed (feed, req: express.Request, res: express.Response) {
221 const format = req.params.format
222
223 if (format === 'atom' || format === 'atom1') {
224 res.set('Content-Type', 'application/atom+xml')
225 return res.send(feed.atom1()).end()
226 }
227
228 if (format === 'json' || format === 'json1') {
229 res.set('Content-Type', 'application/json')
230 return res.send(feed.json1()).end()
231 }
232
233 if (format === 'rss' || format === 'rss2') {
234 res.set('Content-Type', 'application/rss+xml')
235 return res.send(feed.rss2()).end()
236 }
237
238 // We're in the ambiguous '.xml' case and we look at the format query parameter
239 if (req.query.format === 'atom' || req.query.format === 'atom1') {
240 res.set('Content-Type', 'application/atom+xml')
241 return res.send(feed.atom1()).end()
242 }
243
244 res.set('Content-Type', 'application/rss+xml')
245 return res.send(feed.rss2()).end()
246}