diff options
author | Rigel Kent <sendmemail@rigelk.eu> | 2018-04-17 00:49:04 +0200 |
---|---|---|
committer | Rigel <sendmemail@rigelk.eu> | 2018-04-17 01:09:06 +0200 |
commit | 244e76a552ef05a5067134b1065d26dd89246d8c (patch) | |
tree | a15fcd52bce99797fc9366572fea62a7a44aaabe /server/controllers/feeds.ts | |
parent | c36d5a6b98056ef7fec3db43fbee880ee7332dcf (diff) | |
download | PeerTube-244e76a552ef05a5067134b1065d26dd89246d8c.tar.gz PeerTube-244e76a552ef05a5067134b1065d26dd89246d8c.tar.zst PeerTube-244e76a552ef05a5067134b1065d26dd89246d8c.zip |
feature: initial syndication feeds support
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.
Diffstat (limited to 'server/controllers/feeds.ts')
-rw-r--r-- | server/controllers/feeds.ts | 136 |
1 files changed, 136 insertions, 0 deletions
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 @@ | |||
1 | import * as express from 'express' | ||
2 | import { CONFIG } from '../initializers' | ||
3 | import { asyncMiddleware, feedsValidator } from '../middlewares' | ||
4 | import { VideoModel } from '../models/video/video' | ||
5 | import * as Feed from 'pfeed' | ||
6 | import { ResultList } from '../../shared/models' | ||
7 | import { AccountModel } from '../models/account/account' | ||
8 | |||
9 | const feedsRouter = express.Router() | ||
10 | |||
11 | feedsRouter.get('/feeds/videos.:format', | ||
12 | asyncMiddleware(feedsValidator), | ||
13 | asyncMiddleware(generateFeed) | ||
14 | ) | ||
15 | |||
16 | // --------------------------------------------------------------------------- | ||
17 | |||
18 | export { | ||
19 | feedsRouter | ||
20 | } | ||
21 | |||
22 | // --------------------------------------------------------------------------- | ||
23 | |||
24 | async function generateFeed (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
25 | let feed = initFeed() | ||
26 | let feedStart = 0 | ||
27 | let feedCount = 10 | ||
28 | let feedSort = '-createdAt' | ||
29 | |||
30 | let resultList: ResultList<VideoModel> | ||
31 | const account: AccountModel = res.locals.account | ||
32 | |||
33 | if (account) { | ||
34 | resultList = await VideoModel.listUserVideosForApi( | ||
35 | account.id, | ||
36 | feedStart, | ||
37 | feedCount, | ||
38 | feedSort, | ||
39 | true | ||
40 | ) | ||
41 | } else { | ||
42 | resultList = await VideoModel.listForApi( | ||
43 | feedStart, | ||
44 | feedCount, | ||
45 | feedSort, | ||
46 | req.query.filter, | ||
47 | true | ||
48 | ) | ||
49 | } | ||
50 | |||
51 | // Adding video items to the feed, one at a time | ||
52 | resultList.data.forEach(video => { | ||
53 | const formattedVideoFiles = video.getFormattedVideoFilesJSON() | ||
54 | const torrents = formattedVideoFiles.map(videoFile => ({ | ||
55 | title: video.name, | ||
56 | url: videoFile.torrentUrl, | ||
57 | size_in_bytes: videoFile.size | ||
58 | })) | ||
59 | |||
60 | feed.addItem({ | ||
61 | title: video.name, | ||
62 | id: video.url, | ||
63 | link: video.url, | ||
64 | description: video.getTruncatedDescription(), | ||
65 | content: video.description, | ||
66 | author: [ | ||
67 | { | ||
68 | name: video.VideoChannel.Account.getDisplayName(), | ||
69 | link: video.VideoChannel.Account.Actor.url | ||
70 | } | ||
71 | ], | ||
72 | date: video.publishedAt, | ||
73 | language: video.language, | ||
74 | nsfw: video.nsfw, | ||
75 | torrent: torrents | ||
76 | }) | ||
77 | }) | ||
78 | |||
79 | // Now the feed generation is done, let's send it! | ||
80 | return sendFeed(feed, req, res) | ||
81 | } | ||
82 | |||
83 | function initFeed () { | ||
84 | const webserverUrl = CONFIG.WEBSERVER.URL | ||
85 | |||
86 | return new Feed({ | ||
87 | title: CONFIG.INSTANCE.NAME, | ||
88 | description: CONFIG.INSTANCE.SHORT_DESCRIPTION, | ||
89 | // updated: TODO: somehowGetLatestUpdate, // optional, default = today | ||
90 | id: webserverUrl, | ||
91 | link: webserverUrl, | ||
92 | image: webserverUrl + '/client/assets/images/icons/icon-96x96.png', | ||
93 | favicon: webserverUrl + '/client/assets/images/favicon.png', | ||
94 | copyright: `All rights reserved, unless otherwise specified in the terms specified at ${webserverUrl}/about` + | ||
95 | ` and potential licenses granted by each content's rightholder.`, | ||
96 | generator: `Toraifōsu`, // ^.~ | ||
97 | feedLinks: { | ||
98 | json: `${webserverUrl}/feeds/videos.json`, | ||
99 | atom: `${webserverUrl}/feeds/videos.atom`, | ||
100 | rss: `${webserverUrl}/feeds/videos.xml` | ||
101 | }, | ||
102 | author: { | ||
103 | name: 'instance admin of ' + CONFIG.INSTANCE.NAME, | ||
104 | email: CONFIG.ADMIN.EMAIL, | ||
105 | link: `${webserverUrl}/about` | ||
106 | } | ||
107 | }) | ||
108 | } | ||
109 | |||
110 | function sendFeed (feed, req: express.Request, res: express.Response) { | ||
111 | const format = req.params.format | ||
112 | |||
113 | if (format === 'atom' || format === 'atom1') { | ||
114 | res.set('Content-Type', 'application/atom+xml') | ||
115 | return res.send(feed.atom1()).end() | ||
116 | } | ||
117 | |||
118 | if (format === 'json' || format === 'json1') { | ||
119 | res.set('Content-Type', 'application/json') | ||
120 | return res.send(feed.json1()).end() | ||
121 | } | ||
122 | |||
123 | if (format === 'rss' || format === 'rss2') { | ||
124 | res.set('Content-Type', 'application/rss+xml') | ||
125 | return res.send(feed.rss2()).end() | ||
126 | } | ||
127 | |||
128 | // We're in the ambiguous '.xml' case and we look at the format query parameter | ||
129 | if (req.query.format === 'atom' || req.query.format === 'atom1') { | ||
130 | res.set('Content-Type', 'application/atom+xml') | ||
131 | return res.send(feed.atom1()).end() | ||
132 | } | ||
133 | |||
134 | res.set('Content-Type', 'application/rss+xml') | ||
135 | return res.send(feed.rss2()).end() | ||
136 | } | ||