From 995dfa4dffec92a580f393d1cbec64832bbeb06f Mon Sep 17 00:00:00 2001 From: John Livingston Date: Wed, 5 Jul 2023 18:33:30 +0200 Subject: [PATCH] Some refactoring. --- CHANGELOG.md | 1 + package-lock.json | 88 ++++++++++--------- package.json | 4 +- server/lib/rss/init.ts | 87 ++++++++++++++++++ server/lib/uri/webchat.ts | 5 +- server/main.ts | 33 +------ .../content/technical/thirdparty/_index.en.md | 2 +- 7 files changed, 141 insertions(+), 79 deletions(-) create mode 100644 server/lib/rss/init.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 07943869..7eb64835 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## New Features * Implementing the [FEP-1970](https://codeberg.org/fediverse/fep/src/branch/main/fep/1970/fep-1970.md) draft for ActivityPub chat declaration. +* Podcast RSS feed support (thanks to [Alecks Gates](https://github.com/agates)). ## 7.1.0 diff --git a/package-lock.json b/package-lock.json index 851da2d2..839ed707 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ }, "devDependencies": { "@peertube/feed": "^5.1.0", - "@peertube/peertube-types": "^5.1.0", + "@peertube/peertube-types": "^5.2.0", "@tsconfig/node12": "^1.0.9", "@types/async": "^3.2.9", "@types/express": "^4.17.13", @@ -2571,15 +2571,16 @@ } }, "node_modules/@peertube/peertube-types": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@peertube/peertube-types/-/peertube-types-5.1.0.tgz", - "integrity": "sha512-n0FMlKzHae/HuBXXeUd5nWUmBN+BMiyRkwftRquFqyQObwTwltqUooL+DBcjetFsRmPTObvF4kPccQ7LTLqkXQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@peertube/peertube-types/-/peertube-types-5.2.0.tgz", + "integrity": "sha512-t5o/W4cF+E8FJXvFKBuGuCGU01Ad7jodyO7go//UYzgTde4CpxVJIE4G/8fKB30Kl0GCtqm2gncj31gilxZJ0g==", "dev": true, "dependencies": { "@aws-sdk/client-s3": "^3.190.0", "@node-oauth/oauth2-server": "^4.2.0", "@opentelemetry/api": "^1.1.0", "@opentelemetry/sdk-metrics": "^1.8.0", + "@peertube/feed": "^5.1.0", "@types/bluebird": "^3.5.33", "@types/express": "4.17.9", "@types/fluent-ffmpeg": "^2.1.16", @@ -2594,25 +2595,25 @@ "bullmq": "^3.6.6", "execa": "^5.1.1", "express": "^4.18.1", - "express-validator": "^6.4.0", + "express-validator": "^7.0.1", "fluent-ffmpeg": "^2.1.0", "fs-extra": "^11.1.0", "got": "^11.8.2", "hpagent": "^1.0.0", "ioredis": "^5.2.3", - "lru-cache": "^7.13.0", + "lru-cache": "^9.1.1", "memoizee": "^0.4.14", "multer": "^1.4.5-lts.1", - "parse-torrent": "^9.1.0", + "parse-torrent": "^9", "reflect-metadata": "^0.1.12", - "sequelize": "6.29.0", + "sequelize": "6.31.1", "sequelize-typescript": "^2.0.0-beta.1", "short-uuid": "^4.2.0", "socket.io": "^4.5.4", "winston": "3.8.2" }, "engines": { - "node": ">=12.x", + "node": ">=16.x", "yarn": ">=1.x" } }, @@ -2635,12 +2636,12 @@ "dev": true }, "node_modules/@peertube/peertube-types/node_modules/lru-cache": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", - "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz", + "integrity": "sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==", "dev": true, "engines": { - "node": ">=12" + "node": "14 || >=16.14" } }, "node_modules/@sindresorhus/is": { @@ -5749,13 +5750,13 @@ } }, "node_modules/express-validator": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.14.0.tgz", - "integrity": "sha512-ZWHJfnRgePp3FKRSKMtnZVnD1s8ZchWD+jSl7UMseGIqhweCo1Z9916/xXBbJAa6PrA3pUZfkOvIsHZG4ZtIMw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.0.1.tgz", + "integrity": "sha512-oB+z9QOzQIE8FnlINqyIFA8eIckahC6qc8KtqLdLJcU3/phVyuhXH3bA4qzcrhme+1RYaCSwrq+TlZ/kAKIARA==", "dev": true, "dependencies": { "lodash": "^4.17.21", - "validator": "^13.7.0" + "validator": "^13.9.0" }, "engines": { "node": ">= 8.0.0" @@ -8863,9 +8864,9 @@ "dev": true }, "node_modules/sequelize": { - "version": "6.29.0", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.29.0.tgz", - "integrity": "sha512-m8Wi90rs3NZP9coXE52c7PL4Q078nwYZXqt1IxPvgki7nOFn0p/F0eKsYDBXCPw9G8/BCEa6zZNk0DQUAT4ypA==", + "version": "6.31.1", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.31.1.tgz", + "integrity": "sha512-cahWtRrYLjqoZP/aurGBoaxn29qQCF4bxkAUPEQ/ozjJjt6mtL4Q113S3N39mQRmX5fgxRbli+bzZARP/N51eg==", "dev": true, "funding": [ { @@ -10295,9 +10296,9 @@ } }, "node_modules/validator": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", - "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", + "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==", "dev": true, "engines": { "node": ">= 0.10" @@ -12652,15 +12653,16 @@ } }, "@peertube/peertube-types": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@peertube/peertube-types/-/peertube-types-5.1.0.tgz", - "integrity": "sha512-n0FMlKzHae/HuBXXeUd5nWUmBN+BMiyRkwftRquFqyQObwTwltqUooL+DBcjetFsRmPTObvF4kPccQ7LTLqkXQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@peertube/peertube-types/-/peertube-types-5.2.0.tgz", + "integrity": "sha512-t5o/W4cF+E8FJXvFKBuGuCGU01Ad7jodyO7go//UYzgTde4CpxVJIE4G/8fKB30Kl0GCtqm2gncj31gilxZJ0g==", "dev": true, "requires": { "@aws-sdk/client-s3": "^3.190.0", "@node-oauth/oauth2-server": "^4.2.0", "@opentelemetry/api": "^1.1.0", "@opentelemetry/sdk-metrics": "^1.8.0", + "@peertube/feed": "^5.1.0", "@types/bluebird": "^3.5.33", "@types/express": "4.17.9", "@types/fluent-ffmpeg": "^2.1.16", @@ -12675,18 +12677,18 @@ "bullmq": "^3.6.6", "execa": "^5.1.1", "express": "^4.18.1", - "express-validator": "^6.4.0", + "express-validator": "^7.0.1", "fluent-ffmpeg": "^2.1.0", "fs-extra": "^11.1.0", "got": "^11.8.2", "hpagent": "^1.0.0", "ioredis": "^5.2.3", - "lru-cache": "^7.13.0", + "lru-cache": "^9.1.1", "memoizee": "^0.4.14", "multer": "^1.4.5-lts.1", - "parse-torrent": "^9.1.0", + "parse-torrent": "^9", "reflect-metadata": "^0.1.12", - "sequelize": "6.29.0", + "sequelize": "6.31.1", "sequelize-typescript": "^2.0.0-beta.1", "short-uuid": "^4.2.0", "socket.io": "^4.5.4", @@ -12712,9 +12714,9 @@ "dev": true }, "lru-cache": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", - "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz", + "integrity": "sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==", "dev": true } } @@ -15093,13 +15095,13 @@ } }, "express-validator": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.14.0.tgz", - "integrity": "sha512-ZWHJfnRgePp3FKRSKMtnZVnD1s8ZchWD+jSl7UMseGIqhweCo1Z9916/xXBbJAa6PrA3pUZfkOvIsHZG4ZtIMw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.0.1.tgz", + "integrity": "sha512-oB+z9QOzQIE8FnlINqyIFA8eIckahC6qc8KtqLdLJcU3/phVyuhXH3bA4qzcrhme+1RYaCSwrq+TlZ/kAKIARA==", "dev": true, "requires": { "lodash": "^4.17.21", - "validator": "^13.7.0" + "validator": "^13.9.0" } }, "ext": { @@ -17367,9 +17369,9 @@ } }, "sequelize": { - "version": "6.29.0", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.29.0.tgz", - "integrity": "sha512-m8Wi90rs3NZP9coXE52c7PL4Q078nwYZXqt1IxPvgki7nOFn0p/F0eKsYDBXCPw9G8/BCEa6zZNk0DQUAT4ypA==", + "version": "6.31.1", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.31.1.tgz", + "integrity": "sha512-cahWtRrYLjqoZP/aurGBoaxn29qQCF4bxkAUPEQ/ozjJjt6mtL4Q113S3N39mQRmX5fgxRbli+bzZARP/N51eg==", "dev": true, "requires": { "@types/debug": "^4.1.7", @@ -18449,9 +18451,9 @@ } }, "validator": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", - "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", + "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==", "dev": true }, "vary": { diff --git a/package.json b/package.json index 80c78bc6..2cd2ec19 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ }, "devDependencies": { "@peertube/feed": "^5.1.0", - "@peertube/peertube-types": "^5.1.0", + "@peertube/peertube-types": "^5.2.0", "@tsconfig/node12": "^1.0.9", "@types/async": "^3.2.9", "@types/express": "^4.17.13", @@ -70,7 +70,7 @@ "yaml": "^2.2.1" }, "engine": { - "peertube": ">=5.2.0" + "peertube": ">=4.2.0" }, "engines": { "npm": ">=7" diff --git a/server/lib/rss/init.ts b/server/lib/rss/init.ts new file mode 100644 index 00000000..037b3fe1 --- /dev/null +++ b/server/lib/rss/init.ts @@ -0,0 +1,87 @@ +import type { RegisterServerOptions, Video } from '@peertube/peertube-types' +import type { CustomTag } from '@peertube/feed/lib/typings' +import { videoHasWebchat } from '../../../shared/lib/video' +import { fillVideoCustomFields } from '../custom-fields' +import { getProsodyDomain } from '../prosody/config/domain' +import { getPublicChatUri } from '../uri/webchat' + +async function initRSS (options: RegisterServerOptions): Promise { + const logger = options.peertubeHelpers.logger + const registerHook = options.registerHook + logger.info('Registring RSS hooks...') + + registerHook({ + target: 'filter:feed.podcast.video.create-custom-tags.result', + handler: async ( + result: CustomTag[], { video, liveItem }: { video: Video, liveItem: boolean } + ): Promise => { + if (!liveItem) { + // Note: the Podcast RSS feed specification does not handle chats for non-live. + // So we just return here. + return result + } + + // FIXME: calling getSettings for each RSS entry is not optimal. + // Settings should be cached somewhere on the plugin level. + // (i already have some plans to do something for this) + const settings = await options.settingsManager.getSettings([ + 'chat-per-live-video', + 'chat-all-lives', + 'chat-all-non-lives', + 'chat-videos-list', + 'prosody-room-type', + 'federation-dont-publish-remotely', + 'prosody-room-allow-s2s', + 'prosody-s2s-port' + ]) + + if (settings['federation-dont-publish-remotely']) { + // Chat must not be published to the outer world. + return result + } + + await fillVideoCustomFields(options, video) + const hasChat = await videoHasWebchat({ + 'chat-per-live-video': !!settings['chat-per-live-video'], + 'chat-all-lives': !!settings['chat-all-lives'], + 'chat-all-non-lives': !!settings['chat-all-non-lives'], + 'chat-videos-list': settings['chat-videos-list'] as string + }, video) + + if (!hasChat) { + logger.debug(`Video uuid=${video.uuid} has not livechat, no need to add podcast:chat tag.`) + return result + } + + const prosodyDomain = await getProsodyDomain(options) + const podcastChat: any = { + name: 'podcast:chat', + attributes: { + server: prosodyDomain, + protocol: 'xmpp', + // space: will be added only if external XMPP connections are available + embedUrl: getPublicChatUri(options, video) + } + } + + // In order to connect to the chat using standard xmpp, it requires these settings: + // - prosody-room-allow-s2s + // - prosody-s2s-port + if (settings['prosody-room-allow-s2s'] && settings['prosody-s2s-port']) { + let roomJID: string + if (settings['prosody-room-type'] === 'channel') { + roomJID = `channel.${video.channel.id}@room.${prosodyDomain}` + } else { + roomJID = `${video.uuid}@room.${prosodyDomain}` + } + podcastChat.attributes.space = roomJID + } + + return result.concat([podcastChat]) + } + }) +} + +export { + initRSS +} diff --git a/server/lib/uri/webchat.ts b/server/lib/uri/webchat.ts index 7121bc1a..6aebe66e 100644 --- a/server/lib/uri/webchat.ts +++ b/server/lib/uri/webchat.ts @@ -1,5 +1,4 @@ - -import type { RegisterServerOptions, VideoObject } from '@peertube/peertube-types' +import type { RegisterServerOptions, VideoObject, Video } from '@peertube/peertube-types' import { getBaseRouterRoute, getBaseWebSocketRoute } from '../helpers' import { canonicalizePluginUri } from './canonicalize' @@ -19,7 +18,7 @@ export function getWSS2SUri (options: RegisterServerOptions): string | undefined return base + 'xmpp-websocket-s2s' } -export function getPublicChatUri (options: RegisterServerOptions, video: VideoObject): string { +export function getPublicChatUri (options: RegisterServerOptions, video: VideoObject | Video): string { const url = getBaseRouterRoute(options) + 'webchat/room/' + encodeURIComponent(video.uuid) return canonicalizePluginUri(options, url, { removePluginVersion: true diff --git a/server/main.ts b/server/main.ts index 75aff65d..c044cfc7 100644 --- a/server/main.ts +++ b/server/main.ts @@ -1,15 +1,14 @@ -import type { RegisterServerOptions, Video } from '@peertube/peertube-types' +import type { RegisterServerOptions } from '@peertube/peertube-types' import { migrateSettings } from './lib/migration/settings' import { initSettings } from './lib/settings' import { initCustomFields } from './lib/custom-fields' import { initRouters } from './lib/routers/index' import { initFederation } from './lib/federation/init' +import { initRSS } from './lib/rss/init' import { prepareProsody, ensureProsodyRunning, ensureProsodyNotRunning } from './lib/prosody/ctl' import { unloadDebugMode } from './lib/debug' import { loadLoc } from './lib/loc' import decache from 'decache' -import { CustomTag } from '@peertube/feed/lib/typings' -import { URL } from 'url' // FIXME: Peertube unregister don't have any parameter. // Using this global variable to fix this, so we can use helpers to unregister. @@ -32,33 +31,7 @@ async function register (options: RegisterServerOptions): Promise { await initCustomFields(options) await initRouters(options) await initFederation(options) - - options.registerHook({ - // @ts-expect-error Type doesn't exist for peertube 5.1 yet - target: 'filter:feed.podcast.video.create-custom-tags.result', - handler: (result: CustomTag[], { video, liveItem }: { video: Video, liveItem: boolean }) => { - if (!liveItem) { - return result - } - - const webserverUrl = options.peertubeHelpers.config.getWebserverUrl() - const hostname = (new URL(webserverUrl)).hostname - const embedUrl = `${webserverUrl}/plugins/livechat/router/webchat/room/${encodeURIComponent(video.uuid)}` - const xmppRoom = `room.${hostname}` - - return result.concat([ - { - name: 'podcast:chat', - attributes: { - server: hostname, - protocol: 'xmpp', - space: `${video.uuid}@${xmppRoom}`, - embedUrl: embedUrl - } - } - ]) - } - }) + await initRSS(options) try { await prepareProsody(options) diff --git a/support/documentation/content/technical/thirdparty/_index.en.md b/support/documentation/content/technical/thirdparty/_index.en.md index 82fdaa41..5c35cd2b 100644 --- a/support/documentation/content/technical/thirdparty/_index.en.md +++ b/support/documentation/content/technical/thirdparty/_index.en.md @@ -159,7 +159,7 @@ In the ActivityPub object, there is also a `peertubeLiveChat` entry. Don't use the content of this entry. This is specific to the livechat plugin, and can be changed or removed in a near future. It is currently required for some endpoint discovery. -### Using RSS +### Using Podcast RSS feed {{% notice warning %}} This part is not implemented yet, but should be available for v7.2.0 release.