From 244e76a552ef05a5067134b1065d26dd89246d8c Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Tue, 17 Apr 2018 00:49:04 +0200 Subject: feature: initial syndication feeds support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provides rss 2.0, atom 1.0 and json 1.0 feeds for videos (instance and account-wide) on listings and video-watch views. * still lacks redis caching * still lacks lastBuildDate support * still lacks channel-wide support * still lacks semantic annotation (for licenses, NSFW warnings, etc.) * still lacks love ( ˘ ³˘) * RSS: has MRSS support for torrent lists! * RSS: includes the first torrent in an enclosure * JSON: lists all torrents in the 'attachments' object * ATOM: lacking torrent listing support Advances #23 Partial implementation for the accountId generation in the client, which will need a hotfix to add a way to get the proper account id. --- server/controllers/feeds.ts | 136 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 server/controllers/feeds.ts (limited to 'server/controllers/feeds.ts') diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts new file mode 100644 index 000000000..b9d4c5d50 --- /dev/null +++ b/server/controllers/feeds.ts @@ -0,0 +1,136 @@ +import * as express from 'express' +import { CONFIG } from '../initializers' +import { asyncMiddleware, feedsValidator } from '../middlewares' +import { VideoModel } from '../models/video/video' +import * as Feed from 'pfeed' +import { ResultList } from '../../shared/models' +import { AccountModel } from '../models/account/account' + +const feedsRouter = express.Router() + +feedsRouter.get('/feeds/videos.:format', + asyncMiddleware(feedsValidator), + asyncMiddleware(generateFeed) +) + +// --------------------------------------------------------------------------- + +export { + feedsRouter +} + +// --------------------------------------------------------------------------- + +async function generateFeed (req: express.Request, res: express.Response, next: express.NextFunction) { + let feed = initFeed() + let feedStart = 0 + let feedCount = 10 + let feedSort = '-createdAt' + + let resultList: ResultList + const account: AccountModel = res.locals.account + + if (account) { + resultList = await VideoModel.listUserVideosForApi( + account.id, + feedStart, + feedCount, + feedSort, + true + ) + } else { + resultList = await VideoModel.listForApi( + feedStart, + feedCount, + feedSort, + req.query.filter, + true + ) + } + + // Adding video items to the feed, one at a time + resultList.data.forEach(video => { + const formattedVideoFiles = video.getFormattedVideoFilesJSON() + const torrents = formattedVideoFiles.map(videoFile => ({ + title: video.name, + url: videoFile.torrentUrl, + size_in_bytes: videoFile.size + })) + + feed.addItem({ + title: video.name, + id: video.url, + link: video.url, + description: video.getTruncatedDescription(), + content: video.description, + author: [ + { + name: video.VideoChannel.Account.getDisplayName(), + link: video.VideoChannel.Account.Actor.url + } + ], + date: video.publishedAt, + language: video.language, + nsfw: video.nsfw, + torrent: torrents + }) + }) + + // Now the feed generation is done, let's send it! + return sendFeed(feed, req, res) +} + +function initFeed () { + const webserverUrl = CONFIG.WEBSERVER.URL + + return new Feed({ + title: CONFIG.INSTANCE.NAME, + description: CONFIG.INSTANCE.SHORT_DESCRIPTION, + // updated: TODO: somehowGetLatestUpdate, // optional, default = today + id: webserverUrl, + link: webserverUrl, + image: webserverUrl + '/client/assets/images/icons/icon-96x96.png', + favicon: webserverUrl + '/client/assets/images/favicon.png', + copyright: `All rights reserved, unless otherwise specified in the terms specified at ${webserverUrl}/about` + + ` and potential licenses granted by each content's rightholder.`, + generator: `Toraifōsu`, // ^.~ + feedLinks: { + json: `${webserverUrl}/feeds/videos.json`, + atom: `${webserverUrl}/feeds/videos.atom`, + rss: `${webserverUrl}/feeds/videos.xml` + }, + author: { + name: 'instance admin of ' + CONFIG.INSTANCE.NAME, + email: CONFIG.ADMIN.EMAIL, + link: `${webserverUrl}/about` + } + }) +} + +function sendFeed (feed, req: express.Request, res: express.Response) { + const format = req.params.format + + if (format === 'atom' || format === 'atom1') { + res.set('Content-Type', 'application/atom+xml') + return res.send(feed.atom1()).end() + } + + if (format === 'json' || format === 'json1') { + res.set('Content-Type', 'application/json') + return res.send(feed.json1()).end() + } + + if (format === 'rss' || format === 'rss2') { + res.set('Content-Type', 'application/rss+xml') + return res.send(feed.rss2()).end() + } + + // We're in the ambiguous '.xml' case and we look at the format query parameter + if (req.query.format === 'atom' || req.query.format === 'atom1') { + res.set('Content-Type', 'application/atom+xml') + return res.send(feed.atom1()).end() + } + + res.set('Content-Type', 'application/rss+xml') + return res.send(feed.rss2()).end() +} -- cgit v1.2.3