Add ability to list all local videos
[oweals/peertube.git] / server / controllers / feeds.ts
1 import * as express from 'express'
2 import { CONFIG, FEEDS, ROUTE_CACHE_LIFETIME } from '../initializers/constants'
3 import { THUMBNAILS_SIZE } from '../initializers'
4 import {
5   asyncMiddleware,
6   commonVideosFiltersValidator,
7   setDefaultSort,
8   videoCommentsFeedsValidator,
9   videoFeedsValidator,
10   videosSortValidator
11 } from '../middlewares'
12 import { VideoModel } from '../models/video/video'
13 import * as Feed from 'pfeed'
14 import { AccountModel } from '../models/account/account'
15 import { cacheRoute } from '../middlewares/cache'
16 import { VideoChannelModel } from '../models/video/video-channel'
17 import { VideoCommentModel } from '../models/video/video-comment'
18 import { buildNSFWFilter } from '../helpers/express-utils'
19
20 const feedsRouter = express.Router()
21
22 feedsRouter.get('/feeds/video-comments.:format',
23   asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)),
24   asyncMiddleware(videoCommentsFeedsValidator),
25   asyncMiddleware(generateVideoCommentsFeed)
26 )
27
28 feedsRouter.get('/feeds/videos.:format',
29   videosSortValidator,
30   setDefaultSort,
31   asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)),
32   commonVideosFiltersValidator,
33   asyncMiddleware(videoFeedsValidator),
34   asyncMiddleware(generateVideoFeed)
35 )
36
37 // ---------------------------------------------------------------------------
38
39 export {
40   feedsRouter
41 }
42
43 // ---------------------------------------------------------------------------
44
45 async function generateVideoCommentsFeed (req: express.Request, res: express.Response, next: express.NextFunction) {
46   const start = 0
47
48   const video = res.locals.video as VideoModel
49   const videoId: number = video ? video.id : undefined
50
51   const comments = await VideoCommentModel.listForFeed(start, FEEDS.COUNT, videoId)
52
53   const name = video ? video.name : CONFIG.INSTANCE.NAME
54   const description = video ? video.description : CONFIG.INSTANCE.DESCRIPTION
55   const feed = initFeed(name, description)
56
57   // Adding video items to the feed, one at a time
58   comments.forEach(comment => {
59     const link = CONFIG.WEBSERVER.URL + '/videos/watch/' + comment.Video.uuid + ';threadId=' + comment.getThreadId()
60
61     feed.addItem({
62       title: `${comment.Video.name} - ${comment.Account.getDisplayName()}`,
63       id: comment.url,
64       link,
65       content: comment.text,
66       author: [
67         {
68           name: comment.Account.getDisplayName(),
69           link: comment.Account.Actor.url
70         }
71       ],
72       date: comment.createdAt
73     })
74   })
75
76   // Now the feed generation is done, let's send it!
77   return sendFeed(feed, req, res)
78 }
79
80 async function generateVideoFeed (req: express.Request, res: express.Response, next: express.NextFunction) {
81   const start = 0
82
83   const account: AccountModel = res.locals.account
84   const videoChannel: VideoChannelModel = res.locals.videoChannel
85   const nsfw = buildNSFWFilter(res, req.query.nsfw)
86
87   let name: string
88   let description: string
89
90   if (videoChannel) {
91     name = videoChannel.getDisplayName()
92     description = videoChannel.description
93   } else if (account) {
94     name = account.getDisplayName()
95     description = account.description
96   } else {
97     name = CONFIG.INSTANCE.NAME
98     description = CONFIG.INSTANCE.DESCRIPTION
99   }
100
101   const feed = initFeed(name, description)
102
103   const resultList = await VideoModel.listForApi({
104     start,
105     count: FEEDS.COUNT,
106     sort: req.query.sort,
107     includeLocalVideos: true,
108     nsfw,
109     filter: req.query.filter,
110     withFiles: true,
111     accountId: account ? account.id : null,
112     videoChannelId: videoChannel ? videoChannel.id : null
113   })
114
115   // Adding video items to the feed, one at a time
116   resultList.data.forEach(video => {
117     const formattedVideoFiles = video.getFormattedVideoFilesJSON()
118     const torrents = formattedVideoFiles.map(videoFile => ({
119       title: video.name,
120       url: videoFile.torrentUrl,
121       size_in_bytes: videoFile.size
122     }))
123
124     feed.addItem({
125       title: video.name,
126       id: video.url,
127       link: CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid,
128       description: video.getTruncatedDescription(),
129       content: video.description,
130       author: [
131         {
132           name: video.VideoChannel.Account.getDisplayName(),
133           link: video.VideoChannel.Account.Actor.url
134         }
135       ],
136       date: video.publishedAt,
137       language: video.language,
138       nsfw: video.nsfw,
139       torrent: torrents,
140       thumbnail: [
141         {
142           url: CONFIG.WEBSERVER.URL + video.getThumbnailStaticPath(),
143           height: THUMBNAILS_SIZE.height,
144           width: THUMBNAILS_SIZE.width
145         }
146       ]
147     })
148   })
149
150   // Now the feed generation is done, let's send it!
151   return sendFeed(feed, req, res)
152 }
153
154 function initFeed (name: string, description: string) {
155   const webserverUrl = CONFIG.WEBSERVER.URL
156
157   return new Feed({
158     title: name,
159     description,
160     // updated: TODO: somehowGetLatestUpdate, // optional, default = today
161     id: webserverUrl,
162     link: webserverUrl,
163     image: webserverUrl + '/client/assets/images/icons/icon-96x96.png',
164     favicon: webserverUrl + '/client/assets/images/favicon.png',
165     copyright: `All rights reserved, unless otherwise specified in the terms specified at ${webserverUrl}/about` +
166     ` and potential licenses granted by each content's rightholder.`,
167     generator: `Toraifōsu`, // ^.~
168     feedLinks: {
169       json: `${webserverUrl}/feeds/videos.json`,
170       atom: `${webserverUrl}/feeds/videos.atom`,
171       rss: `${webserverUrl}/feeds/videos.xml`
172     },
173     author: {
174       name: 'Instance admin of ' + CONFIG.INSTANCE.NAME,
175       email: CONFIG.ADMIN.EMAIL,
176       link: `${webserverUrl}/about`
177     }
178   })
179 }
180
181 function sendFeed (feed, req: express.Request, res: express.Response) {
182   const format = req.params.format
183
184   if (format === 'atom' || format === 'atom1') {
185     res.set('Content-Type', 'application/atom+xml')
186     return res.send(feed.atom1()).end()
187   }
188
189   if (format === 'json' || format === 'json1') {
190     res.set('Content-Type', 'application/json')
191     return res.send(feed.json1()).end()
192   }
193
194   if (format === 'rss' || format === 'rss2') {
195     res.set('Content-Type', 'application/rss+xml')
196     return res.send(feed.rss2()).end()
197   }
198
199   // We're in the ambiguous '.xml' case and we look at the format query parameter
200   if (req.query.format === 'atom' || req.query.format === 'atom1') {
201     res.set('Content-Type', 'application/atom+xml')
202     return res.send(feed.atom1()).end()
203   }
204
205   res.set('Content-Type', 'application/rss+xml')
206   return res.send(feed.rss2()).end()
207 }