From 52fdb374b29a4b55db0e08e3aedf1da94d28df07 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Mon, 3 Oct 2022 18:24:09 +0300 Subject: [PATCH 1/2] server: use account/channel avatar in RSS feeds Fixes: #5320 --- server/controllers/feeds.ts | 15 ++++++++++++++- server/models/account/account.ts | 4 ++++ server/models/video/video-channel.ts | 4 ++++ server/tests/feeds/feeds.ts | 8 ++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index 241715fb908..c04f96dfbd8 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts @@ -84,20 +84,25 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res let name: string let description: string + let avatarUrl: string if (videoChannel) { name = videoChannel.getDisplayName() description = videoChannel.description + avatarUrl = videoChannel.getAvatar() } else if (account) { name = account.getDisplayName() description = account.description + avatarUrl = account.getAvatar() } else { name = video ? video.name : CONFIG.INSTANCE.NAME description = video ? video.description : CONFIG.INSTANCE.DESCRIPTION + avatarUrl = WEBSERVER.URL + '/client/assets/images/icons/icon-96x96.png', } const feed = initFeed({ name, description, + avatarUrl, resourceType: 'video-comments', queryString: new URL(WEBSERVER.URL + req.originalUrl).search }) @@ -139,21 +144,26 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { let name: string let description: string + let avatarUrl: string if (videoChannel) { name = videoChannel.getDisplayName() description = videoChannel.description + avatarUrl = videoChannel.getAvatar() } else if (account) { name = account.getDisplayName() description = account.description + avatarUrl = account.getAvatar() } else { name = CONFIG.INSTANCE.NAME description = CONFIG.INSTANCE.DESCRIPTION + avatarUrl = WEBSERVER.URL + '/client/assets/images/icons/icon-96x96.png', } const feed = initFeed({ name, description, + avatarUrl, resourceType: 'videos', queryString: new URL(WEBSERVER.URL + req.url).search }) @@ -192,10 +202,12 @@ async function generateVideoFeedForSubscriptions (req: express.Request, res: exp const nsfw = buildNSFWFilter(res, req.query.nsfw) const name = account.getDisplayName() const description = account.description + const avatarUrl = account.getAvatar() const feed = initFeed({ name, description, + avatarUrl, resourceType: 'videos', queryString: new URL(WEBSERVER.URL + req.url).search }) @@ -229,6 +241,7 @@ async function generateVideoFeedForSubscriptions (req: express.Request, res: exp function initFeed (parameters: { name: string description: string + avatarUrl: string resourceType?: 'videos' | 'video-comments' queryString?: string }) { @@ -241,7 +254,7 @@ function initFeed (parameters: { // updated: TODO: somehowGetLatestUpdate, // optional, default = today id: webserverUrl, link: webserverUrl, - image: webserverUrl + '/client/assets/images/icons/icon-96x96.png', + image: avatarUrl, 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.`, diff --git a/server/models/account/account.ts b/server/models/account/account.ts index 8a7dfba9454..6e36abdc533 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts @@ -455,6 +455,10 @@ export class AccountModel extends Model>> { return this.name } + getAvatar () { + return this.Actor.avatar + } + getLocalUrl (this: MAccountActor | MChannelActor) { return WEBSERVER.URL + `/accounts/` + this.Actor.preferredUsername } diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 91dafbcf1d9..4902658789d 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts @@ -827,6 +827,10 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` return this.name } + getAvatar () { + return this.Actor.avatar + } + isOutdated () { return this.Actor.isOutdated() } diff --git a/server/tests/feeds/feeds.ts b/server/tests/feeds/feeds.ts index 1d3c03d67fb..aff4a514c36 100644 --- a/server/tests/feeds/feeds.ts +++ b/server/tests/feeds/feeds.ts @@ -308,6 +308,14 @@ describe('Test syndication feeds', () => { }) }) + it('Should carry the channel avatar', async function () { + { + const json = await servers[0].feed.getJSON({ feed: 'videos', query: { videoChannelId: rootChannelId }, ignoreCache: true }) + const jsonObj = JSON.parse(json) + expect(jsonObj.icon).to.equal(servers[0].url + '/avatar.png') + } + }) + describe('Video comments feed', function () { it('Should contain valid comments (covers JSON feed 1.0 endpoint) and not from unlisted videos', async function () { From 737890fed812e79459b59aeeaf2952cbd9245d34 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 11 Oct 2022 16:45:52 +0200 Subject: [PATCH 2/2] Styling --- server/controllers/feeds.ts | 91 +++++++++++++++------------- server/models/account/account.ts | 4 -- server/models/video/video-channel.ts | 4 -- server/tests/feeds/feeds.ts | 12 ++-- 4 files changed, 55 insertions(+), 56 deletions(-) diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index c04f96dfbd8..772fe734ded 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts @@ -4,7 +4,8 @@ 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 { VideoInclude } from '@shared/models' +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 { MIMETYPES, PREVIEWS_SIZE, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants' @@ -82,27 +83,12 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res videoChannelId: videoChannel ? videoChannel.id : undefined }) - let name: string - let description: string - let avatarUrl: string + const { name, description, imageUrl } = buildFeedMetadata({ video, account, videoChannel }) - if (videoChannel) { - name = videoChannel.getDisplayName() - description = videoChannel.description - avatarUrl = videoChannel.getAvatar() - } else if (account) { - name = account.getDisplayName() - description = account.description - avatarUrl = account.getAvatar() - } else { - name = video ? video.name : CONFIG.INSTANCE.NAME - description = video ? video.description : CONFIG.INSTANCE.DESCRIPTION - avatarUrl = WEBSERVER.URL + '/client/assets/images/icons/icon-96x96.png', - } const feed = initFeed({ name, description, - avatarUrl, + imageUrl, resourceType: 'video-comments', queryString: new URL(WEBSERVER.URL + req.originalUrl).search }) @@ -142,28 +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 - let avatarUrl: string - - if (videoChannel) { - name = videoChannel.getDisplayName() - description = videoChannel.description - avatarUrl = videoChannel.getAvatar() - } else if (account) { - name = account.getDisplayName() - description = account.description - avatarUrl = account.getAvatar() - } else { - name = CONFIG.INSTANCE.NAME - description = CONFIG.INSTANCE.DESCRIPTION - avatarUrl = WEBSERVER.URL + '/client/assets/images/icons/icon-96x96.png', - } + const { name, description, imageUrl } = buildFeedMetadata({ videoChannel, account }) const feed = initFeed({ name, description, - avatarUrl, + imageUrl, resourceType: 'videos', queryString: new URL(WEBSERVER.URL + req.url).search }) @@ -200,14 +170,13 @@ 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 avatarUrl = account.getAvatar() + + const { name, description, imageUrl } = buildFeedMetadata({ account }) const feed = initFeed({ name, description, - avatarUrl, + imageUrl, resourceType: 'videos', queryString: new URL(WEBSERVER.URL + req.url).search }) @@ -241,12 +210,12 @@ async function generateVideoFeedForSubscriptions (req: express.Request, res: exp function initFeed (parameters: { name: string description: string - avatarUrl: 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, @@ -254,7 +223,7 @@ function initFeed (parameters: { // updated: TODO: somehowGetLatestUpdate, // optional, default = today id: webserverUrl, link: webserverUrl, - image: avatarUrl, + 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.`, @@ -382,3 +351,39 @@ function sendFeed (feed: 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 } +} diff --git a/server/models/account/account.ts b/server/models/account/account.ts index 6e36abdc533..8a7dfba9454 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts @@ -455,10 +455,6 @@ export class AccountModel extends Model>> { return this.name } - getAvatar () { - return this.Actor.avatar - } - getLocalUrl (this: MAccountActor | MChannelActor) { return WEBSERVER.URL + `/accounts/` + this.Actor.preferredUsername } diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 4902658789d..91dafbcf1d9 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts @@ -827,10 +827,6 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` return this.name } - getAvatar () { - return this.Actor.avatar - } - isOutdated () { return this.Actor.isOutdated() } diff --git a/server/tests/feeds/feeds.ts b/server/tests/feeds/feeds.ts index aff4a514c36..0ddb641e6f9 100644 --- a/server/tests/feeds/feeds.ts +++ b/server/tests/feeds/feeds.ts @@ -9,6 +9,7 @@ import { createSingleServer, doubleFollow, makeGetRequest, + makeRawRequest, PeerTubeServer, setAccessTokensToServers, setDefaultChannelAvatar, @@ -306,14 +307,15 @@ describe('Test syndication feeds', () => { await stopFfmpeg(ffmpeg) }) - }) - it('Should carry the channel avatar', async function () { - { + it('Should have the channel avatar as feed icon', async function () { const json = await servers[0].feed.getJSON({ feed: 'videos', query: { videoChannelId: rootChannelId }, ignoreCache: true }) + const jsonObj = JSON.parse(json) - expect(jsonObj.icon).to.equal(servers[0].url + '/avatar.png') - } + const imageUrl = jsonObj.icon + expect(imageUrl).to.include('/lazy-static/avatars/') + await makeRawRequest(imageUrl) + }) }) describe('Video comments feed', function () {