peertube-plugin-peerhub/peertube-patches/6.3.0_peerhub-v3.patch

457 lines
17 KiB
Diff

diff --git a/server/core/controllers/feeds/comment-feeds.ts b/server/core/controllers/feeds/comment-feeds.ts
index 105ae27..d4ae72d 100644
--- a/server/core/controllers/feeds/comment-feeds.ts
+++ b/server/core/controllers/feeds/comment-feeds.ts
@@ -56,7 +56,7 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res
const { name, description, imageUrl, link } = await buildFeedMetadata({ video, account, videoChannel })
- const feed = initFeed({
+ const feed = await initFeed({
name,
description,
imageUrl,
diff --git a/server/core/controllers/feeds/shared/common-feed-utils.ts b/server/core/controllers/feeds/shared/common-feed-utils.ts
index 7196364..2076317 100644
--- a/server/core/controllers/feeds/shared/common-feed-utils.ts
+++ b/server/core/controllers/feeds/shared/common-feed-utils.ts
@@ -8,8 +8,9 @@ import { WEBSERVER } from '@server/initializers/constants.js'
import { UserModel } from '@server/models/user/user.js'
import { MAccountDefault, MChannelBannerAccountDefault, MUser, MVideoFullLight } from '@server/types/models/index.js'
import express from 'express'
+import { PluginModel } from '@server/models/server/plugin.js'
-export function initFeed (parameters: {
+export async function initFeed (parameters: {
name: string
description: string
imageUrl: string
@@ -33,6 +34,13 @@ export function initFeed (parameters: {
const webserverUrl = WEBSERVER.URL
const { name, description, link, imageUrl, isPodcast, resourceType, queryString, medium } = parameters
+ let generator = `Toraifōsu`; // ^.~
+
+ const peerhubPlugin = await PluginModel.loadByNpmName('peertube-plugin-peerhub');
+ if (peerhubPlugin && peerhubPlugin.enabled && !peerhubPlugin.uninstalled) {
+ generator = `Peerhub v${peerhubPlugin.version}`
+ }
+
return new Feed({
title: name,
description: mdToOneLinePlainText(description),
@@ -43,7 +51,7 @@ export function initFeed (parameters: {
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.`,
- generator: `PeerTube - ${webserverUrl}`,
+ generator,
medium: medium || 'video',
feedLinks: {
json: `${webserverUrl}/feeds/${resourceType}.json${queryString}`,
diff --git a/server/core/controllers/feeds/shared/video-feed-utils.ts b/server/core/controllers/feeds/shared/video-feed-utils.ts
index 260dac3..75902c9 100644
--- a/server/core/controllers/feeds/shared/video-feed-utils.ts
+++ b/server/core/controllers/feeds/shared/video-feed-utils.ts
@@ -1,5 +1,5 @@
import { VideoIncludeType } from '@peertube/peertube-models'
-import { mdToOneLinePlainText, toSafeHtml } from '@server/helpers/markdown.js'
+import { toSafeHtml } from '@server/helpers/markdown.js'
import { CONFIG } from '@server/initializers/config.js'
import { WEBSERVER } from '@server/initializers/constants.js'
import { getServerActor } from '@server/models/application/application.js'
@@ -47,7 +47,9 @@ export function getCommonVideoFeedAttributes (video: VideoModel) {
return {
title: video.name,
link: localLink,
- description: mdToOneLinePlainText(video.getTruncatedDescription()),
+ description: toSafeHtml(video.description), // peerhub >= 9.7.22
+ // description: mdToOneLinePlainText(video.getTruncatedDescription()), // original peertube
+
content: toSafeHtml(video.description),
date: video.publishedAt,
diff --git a/server/core/controllers/feeds/video-feeds.ts b/server/core/controllers/feeds/video-feeds.ts
index 679d73f..ae7133a 100644
--- a/server/core/controllers/feeds/video-feeds.ts
+++ b/server/core/controllers/feeds/video-feeds.ts
@@ -19,6 +19,12 @@ import {
import { buildFeedMetadata, getCommonVideoFeedAttributes, getVideosForFeeds, initFeed, sendFeed } from './shared/index.js'
import { getVideoFileMimeType } from '@server/lib/video-file.js'
+import { PluginModel } from '@server/models/server/plugin.js'
+import { PluginType } from '@peertube/peertube-models'
+
+const rssDisabledStorageFieldName = 'htrssdis'; // localStorage
+
+
const videoFeedsRouter = express.Router()
const { middleware: cacheRouteMiddleware } = cacheRouteFactory({
@@ -63,7 +69,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response) {
const { name, description, imageUrl, accountImageUrl, link, accountLink } = await buildFeedMetadata({ videoChannel, account })
- const feed = initFeed({
+ const feed = await initFeed({
name,
description,
link,
@@ -85,15 +91,32 @@ async function generateVideoFeed (req: express.Request, res: express.Response) {
addVideosToFeed(feed, data)
- // Now the feed generation is done, let's send it!
- return sendFeed(feed, req, res)
+ // Now the feed generation is done, let's send it, if not disabled (peerhub only)!
+
+ const peerhubPlugin = await PluginModel.loadByNpmName('peertube-plugin-peerhub');
+
+ if (videoChannel && peerhubPlugin && peerhubPlugin.enabled && !peerhubPlugin.uninstalled) {
+ const rssDisabled = await PluginModel.getData(
+ 'hive-tube', // use retro comp storage
+ PluginType.PLUGIN,
+ rssDisabledStorageFieldName + '-' + videoChannel.id
+ )
+
+ if (rssDisabled == 1) return res.redirect('/404');
+ else return res.redirect('/feeds/podcast/videos.xml?videoChannelId='+videoChannel.id); // redirect to richer podcast feed
+
+ } else {
+ // no hive tube, standard peertube or it is not channel rss but account one
+
+ return sendFeed(feed, req, res) // peertube native
+ }
}
async function generateVideoFeedForSubscriptions (req: express.Request, res: express.Response) {
const account = res.locals.account
const { name, description, imageUrl, link } = await buildFeedMetadata({ account })
- const feed = initFeed({
+ const feed = await initFeed({
name,
description,
link,
diff --git a/server/core/controllers/feeds/video-podcast-feeds.ts b/server/core/controllers/feeds/video-podcast-feeds.ts
index 7e0dca2..ab4c7ec 100644
--- a/server/core/controllers/feeds/video-podcast-feeds.ts
+++ b/server/core/controllers/feeds/video-podcast-feeds.ts
@@ -16,8 +16,17 @@ import { VideoCaptionModel } from '../../models/video/video-caption.js'
import { VideoModel } from '../../models/video/video.js'
import { buildFeedMetadata, getCommonVideoFeedAttributes, getVideosForFeeds, initFeed } from './shared/index.js'
+import { PluginModel } from '@server/models/server/plugin.js'
+import { PluginType } from '@peertube/peertube-models'
+
+import { RegisterServerSettingOptions} from '@peertube/peertube-models'
+
const videoPodcastFeedsRouter = express.Router()
+const sriFieldName = 'htsri'; // localStorage
+const rssDisabledStorageFieldName = 'htrssdis'; // localStorage
+
+
// ---------------------------------------------------------------------------
const { middleware: podcastCacheRouteMiddleware, instance: podcastApiCache } = cacheRouteFactory({
@@ -82,7 +91,7 @@ async function generateVideoPodcastFeed (req: express.Request, res: express.Resp
'filter:feed.podcast.rss.create-custom-xmlns.result'
)
- const feed = initFeed({
+ const feed = await initFeed({
name,
description,
link,
@@ -103,8 +112,28 @@ async function generateVideoPodcastFeed (req: express.Request, res: express.Resp
await addVideosToPodcastFeed(feed, data)
- // Now the feed generation is done, let's send it!
- return res.send(feed.podcast()).end()
+ let rssDisabled = 0; // default
+ const peerhubPlugin = await PluginModel.loadByNpmName('peertube-plugin-peerhub');
+ if (peerhubPlugin && peerhubPlugin.enabled && !peerhubPlugin.uninstalled) {
+ // only if hive tube installed
+ rssDisabled = await PluginModel.getData(
+ 'hive-tube', // use retro comp storage
+ PluginType.PLUGIN,
+ rssDisabledStorageFieldName + '-' + videoChannel.id
+ )
+
+ }
+
+ let resultPodcast = feed.podcast();
+
+ // fix itunes explicit field format directly into string, cause it's simpler than changing imported lib
+ // https://github.com/Podcast-Standards-Project/PSP-1-Podcast-RSS-Specification?tab=readme-ov-file#channel-itunes-explicit
+ let fixedResult = resultPodcast.replace(/itunes:explicit>no</g, "itunes:explicit>false<").replace(/itunes:explicit>yes</g, "itunes:explicit>true<");
+
+
+ // Now the feed generation is done, let's send it, if not disabled (hive tube only)!
+ if (rssDisabled == 1) return res.redirect('/404');
+ else return res.send(fixedResult).end()
}
type PodcastMedia =
@@ -153,7 +182,36 @@ async function generatePodcastItem (options: {
const avatar = maxBy(account.Actor.Avatars, 'width')
personImage = WEBSERVER.URL + avatar.getStaticPath()
}
-
+
+ // default peertube
+ let accountUrl = account.getClientUrl();
+ let socialInteract: any[] = [
+ {
+ uri: video.url,
+ protocol: 'activitypub',
+ accountUrl
+ }
+ ]
+ try {
+ const peerhubResponse = await fetch(`${WEBSERVER.URL}/plugins/peerhub/router/jsci?v=${video.id}`);
+ const comments = await peerhubResponse.json();
+ if (comments && (comments.length > 0)) {
+ socialInteract = []; // let's us our own correct format
+ comments.forEach((comment) => {
+ socialInteract.push({
+ priority: comment.priority,
+ protocol: 'activitypub',
+ uri: `${video.url}#${comment.commentId}`,
+ accountId: `@${comment.accountId}`,
+ accountUrl: comment.accountUrl
+ });
+ });
+ }
+
+ } catch(err) {
+ console.log(err); // safe
+ }
+
return {
guid,
...commonAttributes,
@@ -171,13 +229,7 @@ async function generatePodcastItem (options: {
media,
- socialInteract: [
- {
- uri: video.url,
- protocol: 'activitypub',
- accountUrl: account.getClientUrl()
- }
- ],
+ socialInteract,
customTags
}
@@ -206,13 +258,22 @@ async function addVODPodcastItem (options: {
.map(f => buildVODWebVideoFile(video, f))
.sort(sortObjectComparator('bitrate', 'desc'))
- const streamingPlaylistFiles = buildVODStreamingPlaylists(video)
+ const streamingPlaylistFiles = await buildVODStreamingPlaylists(video)
// Order matters here, the first media URI will be the "default"
// So web videos are default if enabled
const media = [ ...webVideos, ...streamingPlaylistFiles ]
- const videoCaptions = buildVODCaptions(video, captionsGroup[video.id])
+ let videoCaptions = buildVODCaptions(video, captionsGroup[video.id]) // original peertube
+ if (!videoCaptions || videoCaptions.length == 0) {
+ // always generate videoCaptions field even if not present, using video description
+ videoCaptions = [{
+ url: `${WEBSERVER.URL}/api/v1/videos/${video.id}/description`,
+ type: 'application/json',
+ language: video.language || 'en',
+ rel: 'captions'
+ }];
+ }
const item = await generatePodcastItem({ video, liveItem: false, media })
feed.addPodcastItem({ ...item, subTitle: videoCaptions })
@@ -262,20 +323,139 @@ function buildVODWebVideoFile (video: MVideo, videoFile: VideoFile) {
}
}
-function buildVODStreamingPlaylists (video: MVideoFullLight) {
+async function buildVODStreamingPlaylists (video: MVideoFullLight) {
const hls = video.getHLSPlaylist()
if (!hls) return []
- return [
- {
- type: 'application/x-mpegURL',
- title: 'HLS',
- sources: [
- { uri: hls.getMasterPlaylistUrl(video) }
- ],
- language: video.language
+ let calculatedUri = hls.getMasterPlaylistUrl(video); // original peertube one, safe default
+ let torrentUri = '';
+ let integrity = '';
+
+ let magnetHash = '';
+ let torrentFileName = '';
+ let size = 0;
+ let resolution = 0;
+ let extname = '';
+
+ let calculatedType = 'application/x-mpegURL'; // original peertube one, safe default
+ let length = 0; // safe default
+ if (hls.VideoFiles && hls.VideoFiles.length > 0) {
+ let selected = 0; // choose highest resolution one, default first one
+ for (let i=0; i<hls.VideoFiles.length; i++) {
+ if (hls.VideoFiles[i].resolution > resolution) {
+ resolution = hls.VideoFiles[i].resolution;
+ selected = i;
+ }
+ }
+
+ if (hls.VideoFiles[selected].resolution > 0) {
+ // video
+ calculatedType = 'video/mp4';
+ } else {
+ // audio
+ calculatedType = 'audio/mpeg';
+ }
+ length = hls.VideoFiles[selected].size;
+ calculatedUri = WEBSERVER.URL + `/static/streaming-playlists/hls/${video.uuid}/${hls.VideoFiles[selected].filename}`;
+ torrentUri = WEBSERVER.URL + `/download/torrents/${hls.VideoFiles[selected].torrentFilename}`;
+
+ integrity = await PluginModel.getData(
+ 'hive-tube', // use retro comp storage
+ PluginType.PLUGIN,
+ sriFieldName + '-' + video.id
+ )
+
+ magnetHash = hls.VideoFiles[selected].infoHash;
+ torrentFileName = hls.VideoFiles[selected].torrentFilename;
+ size = hls.VideoFiles[selected].size;
+ extname = hls.VideoFiles[selected].extname;
+
+ }
+
+ let sourcesUri = [
+ { uri: calculatedUri } // default source, standard peertube
+ ];
+
+ if (torrentUri) { // torrent source, if found
+ sourcesUri.push({
+ uri: torrentUri
+ })
+ }
+
+ // this will work even if peerhub is not installed, in that case properties will be empty
+ const additionalUriSettings = await PluginModel.getSettings(
+ 'peerhub', PluginType.PLUGIN, ['podcast-onion', 'podcast-loki', 'podcast-i2p'], [<RegisterServerSettingOptions><unknown>'podcast-onion', <RegisterServerSettingOptions><unknown>'podcast-loki', <RegisterServerSettingOptions><unknown>'podcast-i2p']
+ )
+
+ // 3 optional additional sources if peerhub installed and fields have values
+ if (additionalUriSettings) {
+
+ if (additionalUriSettings['podcast-onion']) {
+ let onionUri = (additionalUriSettings['podcast-onion']).toString().trim();
+ if (onionUri.substr(-1) != '/') onionUri += '/'; // auto add trailing slash if missing
+ sourcesUri.push({
+ uri: onionUri+`static/streaming-playlists/hls/${video.uuid}/${hls.VideoFiles[0].filename}`
+ })
}
- ]
+
+ if (additionalUriSettings['podcast-loki']) {
+ let lokiUri = (additionalUriSettings['podcast-loki']).toString().trim();
+ if (lokiUri.substr(-1) != '/') lokiUri += '/'; // auto add trailing slash if missing
+ sourcesUri.push({
+ uri: lokiUri+`static/streaming-playlists/hls/${video.uuid}/${hls.VideoFiles[0].filename}`
+ })
+ }
+
+ if (additionalUriSettings['podcast-i2p']) {
+ let itwopUri = (additionalUriSettings['podcast-i2p']).toString().trim();
+ if (itwopUri.substr(-1) != '/') itwopUri += '/'; // auto add trailing slash if missing
+ sourcesUri.push({
+ uri: itwopUri+`static/streaming-playlists/hls/${video.uuid}/${hls.VideoFiles[0].filename}`
+ })
+ }
+
+
+ }
+
+ const result: any = {
+ type: calculatedType,
+ title: 'HLS',
+ sources: sourcesUri,
+ language: video.language,
+ length
+ }
+
+ if (integrity) result.integrity = [{type:"sri", value:integrity}]; // add only if found
+
+ const results = [];
+
+ results.push(result);
+
+ // now let's add optional magnet link
+ if (magnetHash) {
+
+ const xs = `${WEBSERVER.URL}/lazy-static/torrents/${torrentFileName}`;
+ const name = video.name;
+ const announce = `${WEBSERVER.WS}://${WEBSERVER.HOSTNAME}:${WEBSERVER.PORT}/tracker/socket`;
+ const videoName = name.replace(/[/\\?%*:|"<>]/g, '-');
+
+ const infoName = `${videoName} ${resolution}p${extname}`;
+
+ let magnet = `magnet:?xs=${encodeURIComponent(xs)}&xt=urn:btih:${magnetHash}&dn=${encodeURIComponent(infoName)}&xl=${size}&tr=${encodeURIComponent(announce)}`;
+
+ const magnetResult = {
+ type:"application/x-bittorrent",
+ title: "MAGNET",
+ sources: [
+ { uri: magnet }
+ ]
+ // length: 1024 // we don't have it easily at this step, this is the torrent size, not file one, it is optional
+ }
+ results.push(magnetResult);
+
+ }
+
+ return results
}
function buildLiveStreamingPlaylists (video: MVideoFullLight) {
diff --git a/server/server.ts b/server/server.ts
index 603e35e..65a044f 100644
--- a/server/server.ts
+++ b/server/server.ts
@@ -11,6 +11,10 @@ import { CONFIG } from './core/initializers/config.js'
import { API_VERSION, WEBSERVER, loadLanguages } from './core/initializers/constants.js'
import { logger } from './core/helpers/logger.js'
+import { PluginModel } from '@server/models/server/plugin.js'
+import { PluginType } from '@peertube/peertube-models'
+
+
const missed = checkMissedConfig()
if (missed.length !== 0) {
logger.error('Your configuration files miss keys: ' + missed)
@@ -342,6 +346,16 @@ async function startApplication () {
// Before PeerTubeSocket init
PluginManager.Instance.registerWebSocketRouter()
+ // save patch version, to use from plugin, silent errors, failsafe
+ try {
+ await PluginModel.storeData(
+ 'hive-tube', // use retro comp storage
+ PluginType.PLUGIN,
+ 'peerhub-patch-version',
+ '6.3.0_peerhub-v3.patch'
+ )
+ } catch(e) {}
+
PeerTubeSocket.Instance.init(server)
VideoViewsManager.Instance.init()