X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;ds=sidebyside;f=server%2Fcontrollers%2Ffeeds.ts;h=772fe734deda1ebcfb8b7639ab45cf23695010ba;hb=bd09dfaf8dcb0ca4cd5dac9f13e3117486f3bcce;hp=9fa70a7c8ca89e7f9cc9983f422252a909801517;hpb=ac27887774e63d99f4e227fbe18846f143cc4b3c;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index 9fa70a7c8..772fe734d 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts @@ -1,10 +1,14 @@ -import * as express from 'express' -import * as Feed from 'pfeed' +import express from 'express' +import { extname } from 'path' +import { Feed } from '@peertube/feed' +import { mdToOneLinePlainText, toSafeHtml } from '@server/helpers/markdown' +import { getServerActor } from '@server/models/application/application' import { getCategoryLabel } from '@server/models/video/formatter/video-format-utils' -import { VideoFilter } from '../../shared/models/videos/video-query.type' +import { MAccountDefault, MChannelBannerAccountDefault, MVideoFullLight } from '@server/types/models' +import { ActorImageType, VideoInclude } from '@shared/models' import { buildNSFWFilter } from '../helpers/express-utils' import { CONFIG } from '../initializers/config' -import { FEEDS, PREVIEWS_SIZE, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants' +import { MIMETYPES, PREVIEWS_SIZE, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants' import { asyncMiddleware, commonVideosFiltersValidator, @@ -73,35 +77,25 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res const comments = await VideoCommentModel.listForFeed({ start, - count: FEEDS.COUNT, + count: CONFIG.FEEDS.COMMENTS.COUNT, videoId: video ? video.id : undefined, accountId: account ? account.id : undefined, videoChannelId: videoChannel ? videoChannel.id : undefined }) - let name: string - let description: string + const { name, description, imageUrl } = buildFeedMetadata({ video, account, videoChannel }) - if (videoChannel) { - name = videoChannel.getDisplayName() - description = videoChannel.description - } else if (account) { - name = account.getDisplayName() - description = account.description - } else { - name = video ? video.name : CONFIG.INSTANCE.NAME - description = video ? video.description : CONFIG.INSTANCE.DESCRIPTION - } const feed = initFeed({ name, description, + imageUrl, resourceType: 'video-comments', queryString: new URL(WEBSERVER.URL + req.originalUrl).search }) // Adding video items to the feed, one at a time for (const comment of comments) { - const link = WEBSERVER.URL + comment.getCommentStaticPath() + const localLink = WEBSERVER.URL + comment.getCommentStaticPath() let title = comment.Video.name const author: { name: string, link: string }[] = [] @@ -116,9 +110,9 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res feed.addItem({ title, - id: comment.url, - link, - content: comment.text, + id: localLink, + link: localLink, + content: toSafeHtml(comment.text), author, date: comment.createdAt }) @@ -134,23 +128,12 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { const videoChannel = res.locals.videoChannel const nsfw = buildNSFWFilter(res, req.query.nsfw) - let name: string - let description: string - - if (videoChannel) { - name = videoChannel.getDisplayName() - description = videoChannel.description - } else if (account) { - name = account.getDisplayName() - description = account.description - } else { - name = CONFIG.INSTANCE.NAME - description = CONFIG.INSTANCE.DESCRIPTION - } + const { name, description, imageUrl } = buildFeedMetadata({ videoChannel, account }) const feed = initFeed({ name, description, + imageUrl, resourceType: 'videos', queryString: new URL(WEBSERVER.URL + req.url).search }) @@ -160,14 +143,19 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { videoChannelId: videoChannel ? videoChannel.id : null } + const server = await getServerActor() const { data } = await VideoModel.listForApi({ start, - count: FEEDS.COUNT, + count: CONFIG.FEEDS.VIDEOS.COUNT, sort: req.query.sort, - includeLocalVideos: true, + displayOnlyForFollower: { + actorId: server.id, + orLocalVideos: true + }, nsfw, - filter: req.query.filter as VideoFilter, - withFiles: true, + isLocal: req.query.isLocal, + include: req.query.include | VideoInclude.FILES, + hasFiles: true, countVideos: false, ...options }) @@ -182,28 +170,34 @@ async function generateVideoFeedForSubscriptions (req: express.Request, res: exp const start = 0 const account = res.locals.account const nsfw = buildNSFWFilter(res, req.query.nsfw) - const name = account.getDisplayName() - const description = account.description + + const { name, description, imageUrl } = buildFeedMetadata({ account }) const feed = initFeed({ name, description, + imageUrl, resourceType: 'videos', queryString: new URL(WEBSERVER.URL + req.url).search }) const { data } = await VideoModel.listForApi({ start, - count: FEEDS.COUNT, + count: CONFIG.FEEDS.VIDEOS.COUNT, sort: req.query.sort, - includeLocalVideos: false, nsfw, - filter: req.query.filter as VideoFilter, - withFiles: true, + isLocal: req.query.isLocal, + + hasFiles: true, + include: req.query.include | VideoInclude.FILES, + countVideos: false, - followerActorId: res.locals.user.Account.Actor.id, + displayOnlyForFollower: { + actorId: res.locals.user.Account.Actor.id, + orLocalVideos: false + }, user: res.locals.user }) @@ -216,19 +210,20 @@ async function generateVideoFeedForSubscriptions (req: express.Request, res: exp function initFeed (parameters: { name: string description: string + imageUrl: string resourceType?: 'videos' | 'video-comments' queryString?: string }) { const webserverUrl = WEBSERVER.URL - const { name, description, resourceType, queryString } = parameters + const { name, description, resourceType, queryString, imageUrl } = parameters return new Feed({ title: name, - description, + description: mdToOneLinePlainText(description), // updated: TODO: somehowGetLatestUpdate, // optional, default = today id: webserverUrl, link: webserverUrl, - image: webserverUrl + '/client/assets/images/icons/icon-96x96.png', + image: imageUrl, 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.`, @@ -246,10 +241,7 @@ function initFeed (parameters: { }) } -function addVideosToFeed (feed, videos: VideoModel[]) { - /** - * Adding video items to the feed object, one at a time - */ +function addVideosToFeed (feed: Feed, videos: VideoModel[]) { for (const video of videos) { const formattedVideoFiles = video.getFormattedVideoFilesJSON(false) @@ -259,11 +251,11 @@ function addVideosToFeed (feed, videos: VideoModel[]) { size_in_bytes: videoFile.size })) - const videos = formattedVideoFiles.map(videoFile => { + const videoFiles = formattedVideoFiles.map(videoFile => { const result = { - type: 'video/mp4', + type: MIMETYPES.VIDEO.EXT_MIMETYPE[extname(videoFile.fileUrl)], medium: 'video', - height: videoFile.resolution.label.replace('p', ''), + height: videoFile.resolution.id, fileSize: videoFile.size, url: videoFile.fileUrl, framerate: videoFile.fps, @@ -283,12 +275,14 @@ function addVideosToFeed (feed, videos: VideoModel[]) { }) } + const localLink = WEBSERVER.URL + video.getWatchStaticPath() + feed.addItem({ title: video.name, - id: video.url, - link: WEBSERVER.URL + video.getWatchStaticPath(), - description: video.getTruncatedDescription(), - content: video.description, + id: localLink, + link: localLink, + description: mdToOneLinePlainText(video.getTruncatedDescription()), + content: toSafeHtml(video.description), author: [ { name: video.VideoChannel.Account.getDisplayName(), @@ -297,14 +291,26 @@ function addVideosToFeed (feed, videos: VideoModel[]) { ], date: video.publishedAt, nsfw: video.nsfw, - torrent: torrents, - videos, + torrents, + + // Enclosure + video: videoFiles.length !== 0 + ? { + url: videoFiles[0].url, + length: videoFiles[0].fileSize, + type: videoFiles[0].type + } + : undefined, + + // Media RSS + videos: videoFiles, + embed: { - url: video.getEmbedStaticPath(), + url: WEBSERVER.URL + video.getEmbedStaticPath(), allowFullscreen: true }, player: { - url: video.getWatchStaticPath() + url: WEBSERVER.URL + video.getWatchStaticPath() }, categories, community: { @@ -312,7 +318,7 @@ function addVideosToFeed (feed, videos: VideoModel[]) { views: video.views } }, - thumbnail: [ + thumbnails: [ { url: WEBSERVER.URL + video.getPreviewStaticPath(), height: PREVIEWS_SIZE.height, @@ -323,7 +329,7 @@ function addVideosToFeed (feed, videos: VideoModel[]) { } } -function sendFeed (feed, req: express.Request, res: express.Response) { +function sendFeed (feed: Feed, req: express.Request, res: express.Response) { const format = req.params.format if (format === 'atom' || format === 'atom1') { @@ -345,3 +351,39 @@ function sendFeed (feed, req: express.Request, res: express.Response) { return res.send(feed.rss2()).end() } + +function buildFeedMetadata (options: { + videoChannel?: MChannelBannerAccountDefault + account?: MAccountDefault + video?: MVideoFullLight +}) { + const { video, videoChannel, account } = options + + let imageUrl = WEBSERVER.URL + '/client/assets/images/icons/icon-96x96.png' + let name: string + let description: string + + if (videoChannel) { + name = videoChannel.getDisplayName() + description = videoChannel.description + + if (videoChannel.Actor.hasImage(ActorImageType.AVATAR)) { + imageUrl = WEBSERVER.URL + videoChannel.Actor.Avatars[0].getStaticPath() + } + } else if (account) { + name = account.getDisplayName() + description = account.description + + if (account.Actor.hasImage(ActorImageType.AVATAR)) { + imageUrl = WEBSERVER.URL + account.Actor.Avatars[0].getStaticPath() + } + } else if (video) { + name = video.name + description = video.description + } else { + name = CONFIG.INSTANCE.NAME + description = CONFIG.INSTANCE.DESCRIPTION + } + + return { name, description, imageUrl } +}