]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/controllers/feeds.ts
Serve audit logs to client
[github/Chocobozzz/PeerTube.git] / server / controllers / feeds.ts
1 import * as express from 'express'
2 import { FEEDS, ROUTE_CACHE_LIFETIME, THUMBNAILS_SIZE, WEBSERVER } from '../initializers/constants'
3 import {
4 asyncMiddleware,
5 commonVideosFiltersValidator,
6 setDefaultSort,
7 videoCommentsFeedsValidator,
8 videoFeedsValidator,
9 videosSortValidator
10 } from '../middlewares'
11 import { VideoModel } from '../models/video/video'
12 import * as Feed from 'pfeed'
13 import { cacheRoute } from '../middlewares/cache'
14 import { VideoCommentModel } from '../models/video/video-comment'
15 import { buildNSFWFilter } from '../helpers/express-utils'
16 import { CONFIG } from '../initializers/config'
17
18 const feedsRouter = express.Router()
19
20 feedsRouter.get('/feeds/video-comments.:format',
21 asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)),
22 asyncMiddleware(videoCommentsFeedsValidator),
23 asyncMiddleware(generateVideoCommentsFeed)
24 )
25
26 feedsRouter.get('/feeds/videos.:format',
27 videosSortValidator,
28 setDefaultSort,
29 asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)),
30 commonVideosFiltersValidator,
31 asyncMiddleware(videoFeedsValidator),
32 asyncMiddleware(generateVideoFeed)
33 )
34
35 // ---------------------------------------------------------------------------
36
37 export {
38 feedsRouter
39 }
40
41 // ---------------------------------------------------------------------------
42
43 async function generateVideoCommentsFeed (req: express.Request, res: express.Response) {
44 const start = 0
45
46 const video = res.locals.videoAll
47 const videoId: number = video ? video.id : undefined
48
49 const comments = await VideoCommentModel.listForFeed(start, FEEDS.COUNT, videoId)
50
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
55 // Adding video items to the feed, one at a time
56 comments.forEach(comment => {
57 const link = WEBSERVER.URL + comment.getCommentStaticPath()
58
59 let title = comment.Video.name
60 if (comment.Account) title += ` - ${comment.Account.getDisplayName()}`
61
62 feed.addItem({
63 title,
64 id: comment.url,
65 link,
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
81 async function generateVideoFeed (req: express.Request, res: express.Response) {
82 const start = 0
83
84 const account = res.locals.account
85 const videoChannel = res.locals.videoChannel
86 const nsfw = buildNSFWFilter(res, req.query.nsfw)
87
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
104 const resultList = await VideoModel.listForApi({
105 start,
106 count: FEEDS.COUNT,
107 sort: req.query.sort,
108 includeLocalVideos: true,
109 nsfw,
110 filter: req.query.filter,
111 withFiles: true,
112 accountId: account ? account.id : null,
113 videoChannelId: videoChannel ? videoChannel.id : null
114 })
115
116 // Adding video items to the feed, one at a time
117 resultList.data.forEach(video => {
118 const formattedVideoFiles = video.getFormattedVideoFilesJSON()
119
120 const torrents = formattedVideoFiles.map(videoFile => ({
121 title: video.name,
122 url: videoFile.torrentUrl,
123 size_in_bytes: videoFile.size
124 }))
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 }
149
150 feed.addItem({
151 title: video.name,
152 id: video.url,
153 link: WEBSERVER.URL + '/videos/watch/' + video.uuid,
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,
163 nsfw: video.nsfw,
164 torrent: torrents,
165 videos,
166 embed: {
167 url: video.getEmbedStaticPath(),
168 allowFullscreen: true
169 },
170 player: {
171 url: video.getWatchStaticPath()
172 },
173 categories,
174 community: {
175 statistics: {
176 views: video.views
177 }
178 },
179 thumbnail: [
180 {
181 url: WEBSERVER.URL + video.getMiniatureStaticPath(),
182 height: THUMBNAILS_SIZE.height,
183 width: THUMBNAILS_SIZE.width
184 }
185 ]
186 })
187 })
188
189 // Now the feed generation is done, let's send it!
190 return sendFeed(feed, req, res)
191 }
192
193 function initFeed (name: string, description: string) {
194 const webserverUrl = WEBSERVER.URL
195
196 return new Feed({
197 title: name,
198 description,
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: {
213 name: 'Instance admin of ' + CONFIG.INSTANCE.NAME,
214 email: CONFIG.ADMIN.EMAIL,
215 link: `${webserverUrl}/about`
216 }
217 })
218 }
219
220 function 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 }