feeds.ts 6.0 KB

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