diff --git a/client/src/app/+video-channels/video-channels.component.ts b/client/src/app/+video-channels/video-channels.component.ts index 20d2d55a5..7ee44ccd6 100644 --- a/client/src/app/+video-channels/video-channels.component.ts +++ b/client/src/app/+video-channels/video-channels.component.ts @@ -80,8 +80,20 @@ export class VideoChannelsComponent implements OnInit, OnDestroy { ) ) .subscribe(async videoChannel => { + const instanceName = this.server.getHTMLConfig().instance.name + this.metaService.setTitle(videoChannel.displayName) - this.metaService.setRSSFeeds(getChannelRSSFeeds(getOriginUrl(), this.server.getHTMLConfig().instance.name, videoChannel)) + this.metaService.setRSSFeeds( + getChannelRSSFeeds({ + url: getOriginUrl(), + channel: videoChannel, + titles: { + instanceVideosFeed: `${instanceName} - Videos feed`, + channelVideosFeed: `${videoChannel.displayName} - Videos feed`, + channelPodcastFeed: `${videoChannel.displayName} - Podcast feed` + } + }) + ) this.channelDescriptionHTML = await this.markdown.textMarkdownToHTML({ markdown: videoChannel.description, diff --git a/client/src/app/+video-watch/video-watch.component.ts b/client/src/app/+video-watch/video-watch.component.ts index d57cf1c12..fa55b2d70 100644 --- a/client/src/app/+video-watch/video-watch.component.ts +++ b/client/src/app/+video-watch/video-watch.component.ts @@ -30,7 +30,7 @@ import { LiveVideoService } from '@app/shared/shared-video-live/live-video.servi import { VideoPlaylist } from '@app/shared/shared-video-playlist/video-playlist.model' import { VideoPlaylistService } from '@app/shared/shared-video-playlist/video-playlist.service' import { PlayerSettingsService } from '@app/shared/shared-video/player-settings.service' -import { getVideoWatchRSSFeeds, timeToInt } from '@peertube/peertube-core-utils' +import { getVideoRSSFeeds, timeToInt } from '@peertube/peertube-core-utils' import { HTMLServerConfig, HttpStatusCode, @@ -1036,7 +1036,14 @@ export class VideoWatchComponent implements OnInit, OnDestroy { this.metaService.setDescription(video.description) this.metaService.setRSSFeeds( - getVideoWatchRSSFeeds(getOriginUrl(), this.serverConfig.instance.name, { ...video, privacy: video.privacy.id }) + getVideoRSSFeeds({ + url: getOriginUrl(), + video: { ...video, privacy: video.privacy.id }, + titles: { + instanceVideosFeed: `${this.serverConfig.instance.name} - Videos feed`, + videoCommentsFeed: `${video.name} - Comments feed` + } + }) ) } diff --git a/client/src/app/core/routing/meta.service.ts b/client/src/app/core/routing/meta.service.ts index a5f890184..4a983c4d9 100644 --- a/client/src/app/core/routing/meta.service.ts +++ b/client/src/app/core/routing/meta.service.ts @@ -1,7 +1,7 @@ import { Injectable, inject } from '@angular/core' import { Meta, Title } from '@angular/platform-browser' import { getOriginUrl } from '@app/helpers' -import { getDefaultRSSFeeds } from '@peertube/peertube-core-utils' +import { getInstanceRSSFeed } from '@peertube/peertube-core-utils' import { HTMLServerConfig } from '@peertube/peertube-models' import { ServerService } from '../server' @@ -42,7 +42,12 @@ export class MetaService { this.setTitle() this.setDescription() - this.setRSSFeeds(getDefaultRSSFeeds(getOriginUrl(), this.config.instance.name)) + this.setRSSFeeds([ + getInstanceRSSFeed({ + url: getOriginUrl(), + title: `${this.config.instance.name} - Videos feed` + }) + ]) } setRSSFeeds (rssFeeds: { title: string, url: string }[]) { diff --git a/packages/core-utils/src/renderer/rss.ts b/packages/core-utils/src/renderer/rss.ts index d00230620..94ff51cd8 100644 --- a/packages/core-utils/src/renderer/rss.ts +++ b/packages/core-utils/src/renderer/rss.ts @@ -1,51 +1,64 @@ import { VideoPrivacy, VideoPrivacyType } from '@peertube/peertube-models' -export function getDefaultRSSFeeds (url: string, instanceName: string) { +export function getInstanceRSSFeed (options: { + url: string + title: string +}) { + const { url, title } = options + + return { url: `${url}/feeds/videos.xml`, title } +} + +export function getChannelRSSFeeds (options: { + url: string + channel: { name: string, id: number } + titles: { + instanceVideosFeed: string + channelVideosFeed: string + channelPodcastFeed: string + } +}) { + const { url, titles, channel } = options + return [ { - url: `${url}/feeds/videos.xml`, - // TODO: translate - title: `${instanceName} - Videos feed` - } + url: getChannelPodcastFeed(url, channel), + title: titles.channelPodcastFeed + }, + + { + url: `${url}/feeds/videos.xml?videoChannelId=${channel.id}`, + title: titles.channelVideosFeed + }, + + getInstanceRSSFeed({ url, title: titles.instanceVideosFeed }) + ] +} + +export function getVideoRSSFeeds (options: { + url: string + video: { name: string, uuid: string, privacy: VideoPrivacyType } + titles: { + instanceVideosFeed: string + videoCommentsFeed: string + } +}) { + const { url, titles, video } = options + + if (video.privacy !== VideoPrivacy.PUBLIC) { + return [ getInstanceRSSFeed({ url, title: titles.instanceVideosFeed }) ] + } + + return [ + { + url: `${url}/feeds/video-comments.xml?videoId=${video.uuid}`, + title: titles.videoCommentsFeed + }, + + getInstanceRSSFeed({ url, title: titles.instanceVideosFeed }) ] } export function getChannelPodcastFeed (url: string, channel: { id: number }) { return `${url}/feeds/podcast/videos.xml?videoChannelId=${channel.id}` } - -export function getChannelRSSFeeds (url: string, instanceName: string, channel: { name: string, id: number }) { - return [ - { - url: getChannelPodcastFeed(url, channel), - // TODO: translate - title: `${channel.name} podcast feed` - }, - - { - url: `${url}/feeds/videos.xml?videoChannelId=${channel.id}`, - // TODO: translate - title: `${channel.name} feed` - }, - - ...getDefaultRSSFeeds(url, instanceName) - ] -} - -export function getVideoWatchRSSFeeds ( - url: string, - instanceName: string, - video: { name: string, uuid: string, privacy: VideoPrivacyType } -) { - if (video.privacy !== VideoPrivacy.PUBLIC) return getDefaultRSSFeeds(url, instanceName) - - return [ - { - url: `${url}/feeds/video-comments.xml?videoId=${video.uuid}`, - // TODO: translate - title: `${video.name} - Comments feed` - }, - - ...getDefaultRSSFeeds(url, instanceName) - ] -} diff --git a/server/core/lib/html/shared/actor-html.ts b/server/core/lib/html/shared/actor-html.ts index 5c6fa47c9..06db06c36 100644 --- a/server/core/lib/html/shared/actor-html.ts +++ b/server/core/lib/html/shared/actor-html.ts @@ -1,6 +1,6 @@ -import { escapeHTML, getChannelRSSFeeds, getDefaultRSSFeed, maxBy } from '@peertube/peertube-core-utils' +import { escapeHTML, maxBy } from '@peertube/peertube-core-utils' import { HttpStatusCode } from '@peertube/peertube-models' -import { WEBSERVER } from '@server/initializers/constants.js' +import { getChannelRSSFeeds, getDefaultRSSFeeds } from '@server/lib/rss.js' import { AccountModel } from '@server/models/account/account.js' import { ActorImageModel } from '@server/models/actor/actor-image.js' import { VideoChannelModel } from '@server/models/video/video-channel.js' @@ -16,7 +16,7 @@ export class ActorHtml { return this.getAccountOrChannelHTMLPage({ loader: () => accountModelPromise, - getRSSFeeds: () => this.getDefaultRSSFeeds(req), + getRSSFeeds: () => getDefaultRSSFeeds(req), req, res }) @@ -27,7 +27,7 @@ export class ActorHtml { return this.getAccountOrChannelHTMLPage({ loader: () => Promise.resolve(videoChannel), - getRSSFeeds: () => this.getChannelRSSFeeds(videoChannel, req), + getRSSFeeds: () => getChannelRSSFeeds(videoChannel, req), req, res }) @@ -44,8 +44,8 @@ export class ActorHtml { getRSSFeeds: () => account - ? this.getDefaultRSSFeeds(req) - : this.getChannelRSSFeeds(channel, req), + ? getDefaultRSSFeeds(req) + : getChannelRSSFeeds(channel, req), req, res @@ -116,25 +116,4 @@ export class ActorHtml { return customHTML } - - private static getDefaultRSSFeeds (req: express.Request) { - return [ - getDefaultRSSFeed({ - url: WEBSERVER.URL, - title: req.t('{instanceName} videos feed', { instanceName: CONFIG.INSTANCE.NAME }) - }) - ] - } - - private static getChannelRSSFeeds (channel: MChannelDefault, req: express.Request) { - return getChannelRSSFeeds({ - url: WEBSERVER.URL, - channel, - titles: { - videosFeed: req.t('{instanceName} videos feed', { instanceName: CONFIG.INSTANCE.NAME }), - channelVideosFeed: req.t('{name} videos feed', { name: channel.getDisplayName() }), - channelPodcastFeed: req.t('{name} podcast feed', { name: channel.getDisplayName() }) - } - }) - } } diff --git a/server/core/lib/html/shared/page-html.ts b/server/core/lib/html/shared/page-html.ts index df3b9c11e..0b06e858b 100644 --- a/server/core/lib/html/shared/page-html.ts +++ b/server/core/lib/html/shared/page-html.ts @@ -1,15 +1,9 @@ -import { - AVAILABLE_LOCALES, - buildFileLocale, - escapeHTML, - getDefaultLocale, - getDefaultRSSFeeds, - is18nLocale -} from '@peertube/peertube-core-utils' +import { AVAILABLE_LOCALES, buildFileLocale, escapeHTML, getDefaultLocale, is18nLocale } from '@peertube/peertube-core-utils' import { HTMLServerConfig } from '@peertube/peertube-models' import { isTestOrDevInstance, root, sha256 } from '@peertube/peertube-node-utils' import { setClientLanguageCookie } from '@server/helpers/i18n.js' import { CONFIG } from '@server/initializers/config.js' +import { getDefaultRSSFeeds } from '@server/lib/rss.js' import { getServerActor } from '@server/models/application/application.js' import express from 'express' import { pathExists } from 'fs-extra/esm' @@ -61,7 +55,7 @@ export class PageHtml { twitterCard: 'summary_large_image', forbidIndexation: false, embedIndexation: false, - rssFeeds: getDefaultRSSFeeds(WEBSERVER.URL, CONFIG.INSTANCE.NAME) + rssFeeds: getDefaultRSSFeeds(req) }, {}) return customHTML diff --git a/server/core/lib/html/shared/playlist-html.ts b/server/core/lib/html/shared/playlist-html.ts index 35698c822..f633c97fc 100644 --- a/server/core/lib/html/shared/playlist-html.ts +++ b/server/core/lib/html/shared/playlist-html.ts @@ -1,6 +1,7 @@ -import { addQueryParams, escapeHTML, getDefaultRSSFeeds } from '@peertube/peertube-core-utils' +import { addQueryParams, escapeHTML } from '@peertube/peertube-core-utils' import { HttpStatusCode, VideoPlaylistPrivacy } from '@peertube/peertube-models' import { Memoize } from '@server/helpers/memoize.js' +import { getDefaultRSSFeeds } from '@server/lib/rss.js' import { VideoPlaylistModel } from '@server/models/video/video-playlist.js' import { MVideoPlaylist, MVideoPlaylistFull } from '@server/types/models/index.js' import express from 'express' @@ -31,6 +32,7 @@ export class PlaylistHtml { } return this.buildPlaylistHTML({ + req, html, playlist: videoPlaylist, addEmbedInfo: true, @@ -55,6 +57,8 @@ export class PlaylistHtml { } return this.buildPlaylistHTML({ + req: null, + html, playlist, addEmbedInfo: true, @@ -72,6 +76,8 @@ export class PlaylistHtml { // --------------------------------------------------------------------------- private static buildPlaylistHTML (options: { + req: express.Request + html: string playlist: MVideoPlaylistFull @@ -83,7 +89,7 @@ export class PlaylistHtml { currentQuery: Record }) { - const { html, playlist, addEmbedInfo, addOG, addTwitterCard, isEmbed, currentQuery = {} } = options + const { req, html, playlist, addEmbedInfo, addOG, addTwitterCard, isEmbed, currentQuery = {} } = options const escapedTruncatedDescription = TagsHtml.buildEscapedTruncatedDescription(playlist.description) let htmlResult = TagsHtml.addTitleTag(html, playlist.name) @@ -130,7 +136,9 @@ export class PlaylistHtml { embed, oembedUrl: this.getOEmbedUrl(playlist, currentQuery), - rssFeeds: getDefaultRSSFeeds(WEBSERVER.URL, CONFIG.INSTANCE.NAME) + rssFeeds: req + ? getDefaultRSSFeeds(req) + : [] }, { playlist }) } diff --git a/server/core/lib/html/shared/video-html.ts b/server/core/lib/html/shared/video-html.ts index d2ce8d466..328d55c0e 100644 --- a/server/core/lib/html/shared/video-html.ts +++ b/server/core/lib/html/shared/video-html.ts @@ -1,6 +1,7 @@ -import { addQueryParams, escapeHTML, getVideoWatchRSSFeeds } from '@peertube/peertube-core-utils' +import { addQueryParams, escapeHTML } from '@peertube/peertube-core-utils' import { HttpStatusCode, VideoPrivacy } from '@peertube/peertube-models' import { Memoize } from '@server/helpers/memoize.js' +import { getVideoRSSFeeds } from '@server/lib/rss.js' import express from 'express' import validator from 'validator' import { CONFIG } from '../../../initializers/config.js' @@ -33,6 +34,8 @@ export class VideoHtml { } return this.buildVideoHTML({ + req, + html, video, currentQuery: req.query, @@ -56,6 +59,8 @@ export class VideoHtml { } return this.buildVideoHTML({ + req: null, + html, video, addEmbedInfo: true, @@ -73,6 +78,8 @@ export class VideoHtml { // --------------------------------------------------------------------------- private static buildVideoHTML (options: { + req: express.Request + html: string video: MVideoThumbnail @@ -84,7 +91,7 @@ export class VideoHtml { currentQuery: Record }) { - const { html, video, addEmbedInfo, addOG, addTwitterCard, isEmbed, currentQuery = {} } = options + const { req, html, video, addEmbedInfo, addOG, addTwitterCard, isEmbed, currentQuery = {} } = options const escapedTruncatedDescription = TagsHtml.buildEscapedTruncatedDescription(video.description) let customHTML = TagsHtml.addTitleTag(html, video.name) @@ -135,7 +142,9 @@ export class VideoHtml { twitterCard, schemaType, - rssFeeds: getVideoWatchRSSFeeds(WEBSERVER.URL, CONFIG.INSTANCE.NAME, video) + rssFeeds: req + ? getVideoRSSFeeds(video, req) + : [] }, { video }) } diff --git a/server/core/lib/rss.ts b/server/core/lib/rss.ts new file mode 100644 index 000000000..f0ff2faab --- /dev/null +++ b/server/core/lib/rss.ts @@ -0,0 +1,41 @@ +import { + getChannelRSSFeeds as getChannelRSSFeedsCore, + getInstanceRSSFeed as getDefaultRSSFeedCore, + getVideoRSSFeeds as getVideoWatchRSSFeedsCore +} from '@peertube/peertube-core-utils' +import { CONFIG } from '@server/initializers/config.js' +import { WEBSERVER } from '@server/initializers/constants.js' +import { MChannel, MVideo } from '@server/types/models/index.js' +import express from 'express' + +export function getDefaultRSSFeeds (req: express.Request) { + return [ + getDefaultRSSFeedCore({ + url: WEBSERVER.URL, + title: req.t('{instanceName} - Videos feed', { instanceName: CONFIG.INSTANCE.NAME }) + }) + ] +} + +export function getChannelRSSFeeds (channel: MChannel, req: express.Request) { + return getChannelRSSFeedsCore({ + url: WEBSERVER.URL, + channel, + titles: { + instanceVideosFeed: req.t('{instanceName} - Videos feed', { instanceName: CONFIG.INSTANCE.NAME }), + channelVideosFeed: req.t('{name} - Videos feed', { name: channel.getDisplayName() }), + channelPodcastFeed: req.t('{name} - Podcast feed', { name: channel.getDisplayName() }) + } + }) +} + +export function getVideoRSSFeeds (video: MVideo, req: express.Request) { + return getVideoWatchRSSFeedsCore({ + url: WEBSERVER.URL, + video, + titles: { + instanceVideosFeed: req.t('{instanceName} - Videos feed', { instanceName: CONFIG.INSTANCE.NAME }), + videoCommentsFeed: req.t('{videoName} - Comments feed', { videoName: video.name }) + } + }) +}