From 4857f887f73328f2d4492eb04a8126cb7e446668 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 10 Mar 2023 10:06:29 +0100 Subject: Fix plugin guide --- support/doc/plugins/guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support/doc/plugins/guide.md b/support/doc/plugins/guide.md index 2cc544f1c..337f3d97f 100644 --- a/support/doc/plugins/guide.md +++ b/support/doc/plugins/guide.md @@ -290,7 +290,7 @@ You can create custom routes using an [express Router](https://expressjs.com/en/ ```js function register ({ - router + getRouter }) { const router = getRouter() router.get('/ping', (req, res) => res.json({ message: 'pong' })) -- cgit v1.2.3 From c1b3f2e05d11dc8b452869582dcb1b926b306d79 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 10 Mar 2023 10:34:25 +0100 Subject: Add action:video-edit.form.updated hook --- .../src/app/+videos/+video-edit/shared/video-edit.component.ts | 10 +++++++++- shared/models/plugins/client/client-hook.model.ts | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts index 13359a4d1..e3bd5fe71 100644 --- a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts +++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts @@ -240,7 +240,15 @@ export class VideoEditComponent implements OnInit, OnDestroy { this.schedulerInterval = setInterval(() => this.minScheduledDate = new Date(), 1000 * 60) // Update every minute }) - this.hooks.runAction('action:video-edit.init', 'video-edit', { type: this.type }) + const updateForm = (values: any) => { + this.form.patchValue(values) + this.cd.detectChanges() + } + this.hooks.runAction('action:video-edit.init', 'video-edit', { type: this.type, updateForm }) + + this.form.valueChanges.subscribe(() => { + this.hooks.runAction('action:video-edit.form.updated', 'video-edit', { type: this.type, formValues: this.form.value }) + }) } ngOnDestroy () { diff --git a/shared/models/plugins/client/client-hook.model.ts b/shared/models/plugins/client/client-hook.model.ts index 20e019304..ec1d2b5e5 100644 --- a/shared/models/plugins/client/client-hook.model.ts +++ b/shared/models/plugins/client/client-hook.model.ts @@ -129,8 +129,12 @@ export const clientActionHookObject = { 'action:video-channel-playlists.playlists.loaded': true, // Fired when the video edit page (upload, URL/torrent import, update) is being initialized + // Contains a `type` and `updateForm` object attributes 'action:video-edit.init': true, + // Fired when values of the video edit form changed + 'action:video-edit.form.updated': true, + // Fired when the login page is being initialized 'action:login.init': true, -- cgit v1.2.3 From 4265d90b0061399e23b816e3880ee1be47ead96f Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 10 Mar 2023 11:10:16 +0100 Subject: Add ability for plugins to add metadata --- .../metadata/video-attributes.component.html | 7 +++++ .../shared/metadata/video-attributes.component.ts | 32 ++++++++++++++++++++-- client/src/app/core/plugins/hooks.service.ts | 9 ++++++ shared/models/plugins/client/client-hook.model.ts | 2 ++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.html b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.html index 52ad1999d..0aa707666 100644 --- a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.html +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.html @@ -62,3 +62,10 @@ Duration {{ video.duration | myDurationFormatter }} + +
+ {{ metadata.label }} + + {{ metadata.value }} + +
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.ts b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.ts index b8f564c4c..ebfb42711 100644 --- a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.ts +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.ts @@ -1,14 +1,35 @@ -import { Component, Input } from '@angular/core' +import { Component, Input, OnInit } from '@angular/core' +import { HooksService } from '@app/core' import { VideoDetails } from '@app/shared/shared-main' +type PluginMetadata = { + label: string + + value?: string + safeHTML?: string +} + @Component({ selector: 'my-video-attributes', templateUrl: './video-attributes.component.html', styleUrls: [ './video-attributes.component.scss' ] }) -export class VideoAttributesComponent { +export class VideoAttributesComponent implements OnInit { @Input() video: VideoDetails + pluginMetadata: PluginMetadata[] = [] + + constructor (private hooks: HooksService) { } + + async ngOnInit () { + this.pluginMetadata = await this.hooks.wrapFunResult( + this.buildPluginMetadata.bind(this), + { video: this.video }, + 'video-watch', + 'filter:video-watch.video-plugin-metadata.result' + ) + } + getVideoHost () { return this.video.channel.host } @@ -18,4 +39,11 @@ export class VideoAttributesComponent { return this.video.tags } + + // Used for plugin hooks + private buildPluginMetadata (_options: { + video: VideoDetails + }): PluginMetadata[] { + return [] + } } diff --git a/client/src/app/core/plugins/hooks.service.ts b/client/src/app/core/plugins/hooks.service.ts index 29db75d89..f325605e9 100644 --- a/client/src/app/core/plugins/hooks.service.ts +++ b/client/src/app/core/plugins/hooks.service.ts @@ -48,6 +48,15 @@ export class HooksService { return this.pluginService.runHook(hookResultName, result, params) } + async wrapFunResult + (fun: RawFunction, params: P, scope: PluginClientScope, hookResultName: H) { + await this.pluginService.ensurePluginsAreLoaded(scope) + + const result = fun(params) + + return this.pluginService.runHook(hookResultName, result, params) + } + runAction (hookName: U, scope: PluginClientScope, params?: T) { // Use setTimeout to give priority to Angular change detector setTimeout(() => { diff --git a/shared/models/plugins/client/client-hook.model.ts b/shared/models/plugins/client/client-hook.model.ts index ec1d2b5e5..bc3f5dd9f 100644 --- a/shared/models/plugins/client/client-hook.model.ts +++ b/shared/models/plugins/client/client-hook.model.ts @@ -87,6 +87,8 @@ export const clientFilterHookObject = { 'filter:share.video-playlist-url.build.params': true, 'filter:share.video-playlist-url.build.result': true, + 'filter:video-watch.video-plugin-metadata.result': true, + // Filter videojs options built for PeerTube player 'filter:internal.player.videojs.options.result': true, -- cgit v1.2.3 From 866b5d3f5230204d611a556260102996c1aefe10 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 10 Mar 2023 12:01:21 +0100 Subject: Add ability for plugins to alter video jsonld --- .../shared/metadata/video-attributes.component.ts | 15 ++------ client/src/app/core/plugins/hooks.service.ts | 17 ++------- server/controllers/activitypub/client.ts | 22 +++++------ server/controllers/activitypub/outbox.ts | 2 +- server/controllers/activitypub/utils.ts | 4 +- server/lib/activitypub/context.ts | 16 +++++--- server/lib/activitypub/send/http.ts | 4 +- server/lib/activitypub/send/send-create.ts | 2 +- server/lib/activitypub/send/send-update.ts | 2 +- server/models/account/account.ts | 4 +- server/models/video/video-channel.ts | 4 +- server/models/video/video.ts | 9 ++++- server/tests/api/activitypub/security.ts | 44 +++++++++++++++++----- server/tests/shared/requests.ts | 29 -------------- shared/core-utils/i18n/i18n.ts | 3 +- shared/models/plugins/server/server-hook.model.ts | 8 +++- 16 files changed, 91 insertions(+), 94 deletions(-) diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.ts b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.ts index ebfb42711..1834f7be5 100644 --- a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.ts +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.ts @@ -22,11 +22,11 @@ export class VideoAttributesComponent implements OnInit { constructor (private hooks: HooksService) { } async ngOnInit () { - this.pluginMetadata = await this.hooks.wrapFunResult( - this.buildPluginMetadata.bind(this), - { video: this.video }, + this.pluginMetadata = await this.hooks.wrapObject( + this.pluginMetadata, 'video-watch', - 'filter:video-watch.video-plugin-metadata.result' + 'filter:video-watch.video-plugin-metadata.result', + { video: this.video } ) } @@ -39,11 +39,4 @@ export class VideoAttributesComponent implements OnInit { return this.video.tags } - - // Used for plugin hooks - private buildPluginMetadata (_options: { - video: VideoDetails - }): PluginMetadata[] { - return [] - } } diff --git a/client/src/app/core/plugins/hooks.service.ts b/client/src/app/core/plugins/hooks.service.ts index f325605e9..d9fef8389 100644 --- a/client/src/app/core/plugins/hooks.service.ts +++ b/client/src/app/core/plugins/hooks.service.ts @@ -48,15 +48,6 @@ export class HooksService { return this.pluginService.runHook(hookResultName, result, params) } - async wrapFunResult - (fun: RawFunction, params: P, scope: PluginClientScope, hookResultName: H) { - await this.pluginService.ensurePluginsAreLoaded(scope) - - const result = fun(params) - - return this.pluginService.runHook(hookResultName, result, params) - } - runAction (hookName: U, scope: PluginClientScope, params?: T) { // Use setTimeout to give priority to Angular change detector setTimeout(() => { @@ -66,13 +57,13 @@ export class HooksService { }) } - async wrapObject (result: T, scope: PluginClientScope, hookName: U) { + async wrapObject (result: T, scope: PluginClientScope, hookName: U, context?: any) { await this.pluginService.ensurePluginsAreLoaded(scope) - return this.wrapObjectWithoutScopeLoad(result, hookName) + return this.wrapObjectWithoutScopeLoad(result, hookName, context) } - private wrapObjectWithoutScopeLoad (result: T, hookName: U) { - return this.pluginService.runHook(hookName, result) + private wrapObjectWithoutScopeLoad (result: T, hookName: U, context?: any) { + return this.pluginService.runHook(hookName, result, context) } } diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts index def320730..750e3091c 100644 --- a/server/controllers/activitypub/client.ts +++ b/server/controllers/activitypub/client.ts @@ -48,7 +48,7 @@ activityPubClientRouter.get( [ '/accounts?/:name', '/accounts?/:name/video-channels', '/a/:name', '/a/:name/video-channels' ], executeIfActivityPub, asyncMiddleware(localAccountValidator), - accountController + asyncMiddleware(accountController) ) activityPubClientRouter.get('/accounts?/:name/followers', executeIfActivityPub, @@ -69,13 +69,13 @@ activityPubClientRouter.get('/accounts?/:name/likes/:videoId', executeIfActivityPub, cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS), asyncMiddleware(getAccountVideoRateValidatorFactory('like')), - getAccountVideoRateFactory('like') + asyncMiddleware(getAccountVideoRateFactory('like')) ) activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId', executeIfActivityPub, cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS), asyncMiddleware(getAccountVideoRateValidatorFactory('dislike')), - getAccountVideoRateFactory('dislike') + asyncMiddleware(getAccountVideoRateFactory('dislike')) ) activityPubClientRouter.get( @@ -131,7 +131,7 @@ activityPubClientRouter.get( executeIfActivityPub, asyncMiddleware(videoChannelsNameWithHostValidator), ensureIsLocalChannel, - videoChannelController + asyncMiddleware(videoChannelController) ) activityPubClientRouter.get('/video-channels/:nameWithHost/followers', executeIfActivityPub, @@ -172,13 +172,13 @@ activityPubClientRouter.get( activityPubClientRouter.get('/video-playlists/:playlistId/videos/:playlistElementId', executeIfActivityPub, asyncMiddleware(videoPlaylistElementAPGetValidator), - videoPlaylistElementController + asyncMiddleware(videoPlaylistElementController) ) activityPubClientRouter.get('/videos/local-viewer/:localViewerId', executeIfActivityPub, asyncMiddleware(getVideoLocalViewerValidator), - getVideoLocalViewerController + asyncMiddleware(getVideoLocalViewerController) ) // --------------------------------------------------------------------------- @@ -189,10 +189,10 @@ export { // --------------------------------------------------------------------------- -function accountController (req: express.Request, res: express.Response) { +async function accountController (req: express.Request, res: express.Response) { const account = res.locals.account - return activityPubResponse(activityPubContextify(account.toActivityPubObject(), 'Actor'), res) + return activityPubResponse(activityPubContextify(await account.toActivityPubObject(), 'Actor'), res) } async function accountFollowersController (req: express.Request, res: express.Response) { @@ -246,7 +246,7 @@ async function videoController (req: express.Request, res: express.Response) { const videoWithCaptions = Object.assign(video, { VideoCaptions: captions }) const audience = getAudience(videoWithCaptions.VideoChannel.Account.Actor, videoWithCaptions.privacy === VideoPrivacy.PUBLIC) - const videoObject = audiencify(videoWithCaptions.toActivityPubObject(), audience) + const videoObject = audiencify(await videoWithCaptions.toActivityPubObject(), audience) if (req.path.endsWith('/activity')) { const data = buildCreateActivity(videoWithCaptions.url, video.VideoChannel.Account.Actor, videoObject, audience) @@ -321,10 +321,10 @@ async function videoCommentsController (req: express.Request, res: express.Respo return activityPubResponse(activityPubContextify(json, 'Collection'), res) } -function videoChannelController (req: express.Request, res: express.Response) { +async function videoChannelController (req: express.Request, res: express.Response) { const videoChannel = res.locals.videoChannel - return activityPubResponse(activityPubContextify(videoChannel.toActivityPubObject(), 'Actor'), res) + return activityPubResponse(activityPubContextify(await videoChannel.toActivityPubObject(), 'Actor'), res) } async function videoChannelFollowersController (req: express.Request, res: express.Response) { diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts index f385c9927..681a5660c 100644 --- a/server/controllers/activitypub/outbox.ts +++ b/server/controllers/activitypub/outbox.ts @@ -63,7 +63,7 @@ async function buildActivities (actor: MActorLight, start: number, count: number activities.push(announceActivity) } else { - const videoObject = video.toActivityPubObject() + const videoObject = await video.toActivityPubObject() const createActivity = buildCreateActivity(video.url, byActor, videoObject, createActivityAudience) activities.push(createActivity) diff --git a/server/controllers/activitypub/utils.ts b/server/controllers/activitypub/utils.ts index f851ef652..5de38eb43 100644 --- a/server/controllers/activitypub/utils.ts +++ b/server/controllers/activitypub/utils.ts @@ -1,6 +1,8 @@ import express from 'express' -function activityPubResponse (data: any, res: express.Response) { +async function activityPubResponse (promise: Promise, res: express.Response) { + const data = await promise + return res.type('application/activity+json; charset=utf-8') .json(data) } diff --git a/server/lib/activitypub/context.ts b/server/lib/activitypub/context.ts index 349c4d227..a3ca52a31 100644 --- a/server/lib/activitypub/context.ts +++ b/server/lib/activitypub/context.ts @@ -1,7 +1,8 @@ import { ContextType } from '@shared/models' +import { Hooks } from '../plugins/hooks' -function activityPubContextify (data: T, type: ContextType) { - return { ...getContextData(type), ...data } +async function activityPubContextify (data: T, type: ContextType) { + return { ...await getContextData(type), ...data } } // --------------------------------------------------------------------------- @@ -165,10 +166,13 @@ const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string Rate: buildContext() } -function getContextData (type: ContextType) { - return { - '@context': contextStore[type] - } +async function getContextData (type: ContextType) { + const contextData = await Hooks.wrapObject( + contextStore[type], + 'filter:activity-pub.activity.context.build.result' + ) + + return { '@context': contextData } } function buildContext (contextValue?: ContextValue) { diff --git a/server/lib/activitypub/send/http.ts b/server/lib/activitypub/send/http.ts index d8d0b8542..ad7869853 100644 --- a/server/lib/activitypub/send/http.ts +++ b/server/lib/activitypub/send/http.ts @@ -52,9 +52,9 @@ function buildGlobalHeaders (body: any) { } } -function signAndContextify (byActor: MActor, data: T, contextType: ContextType | null) { +async function signAndContextify (byActor: MActor, data: T, contextType: ContextType | null) { const activity = contextType - ? activityPubContextify(data, contextType) + ? await activityPubContextify(data, contextType) : data return signJsonLDObject(byActor, activity) diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts index 7c3a6bdd0..0e996ab80 100644 --- a/server/lib/activitypub/send/send-create.ts +++ b/server/lib/activitypub/send/send-create.ts @@ -33,7 +33,7 @@ async function sendCreateVideo (video: MVideoAP, transaction: Transaction) { logger.info('Creating job to send video creation of %s.', video.url, lTags(video.uuid)) const byActor = video.VideoChannel.Account.Actor - const videoObject = video.toActivityPubObject() + const videoObject = await video.toActivityPubObject() const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC) const createActivity = buildCreateActivity(video.url, byActor, videoObject, audience) diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts index 24983dd19..5a66294e6 100644 --- a/server/lib/activitypub/send/send-update.ts +++ b/server/lib/activitypub/send/send-update.ts @@ -36,7 +36,7 @@ async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, transaction: T video.VideoCaptions = await video.$get('VideoCaptions', { transaction }) } - const videoObject = video.toActivityPubObject() + const videoObject = await video.toActivityPubObject() const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC) const updateActivity = buildUpdateActivity(url, byActor, videoObject, audience) diff --git a/server/models/account/account.ts b/server/models/account/account.ts index dc989417b..5bf29f45a 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts @@ -447,8 +447,8 @@ export class AccountModel extends Model>> { } } - toActivityPubObject (this: MAccountAP) { - const obj = this.Actor.toActivityPubObject(this.name) + async toActivityPubObject (this: MAccountAP) { + const obj = await this.Actor.toActivityPubObject(this.name) return Object.assign(obj, { summary: this.description diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index b71f5a197..67fccab68 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts @@ -809,8 +809,8 @@ export class VideoChannelModel extends Model { + const obj = await this.Actor.toActivityPubObject(this.name) return Object.assign(obj, { summary: this.description, diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 1a10d2da2..7fe2ec293 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -137,6 +137,7 @@ import { VideoShareModel } from './video-share' import { VideoSourceModel } from './video-source' import { VideoStreamingPlaylistModel } from './video-streaming-playlist' import { VideoTagModel } from './video-tag' +import { Hooks } from '@server/lib/plugins/hooks' export enum ScopeNames { FOR_API = 'FOR_API', @@ -1713,8 +1714,12 @@ export class VideoModel extends Model>> { return files } - toActivityPubObject (this: MVideoAP): VideoObject { - return videoModelToActivityPubObject(this) + toActivityPubObject (this: MVideoAP): Promise { + return Hooks.wrapObject( + videoModelToActivityPubObject(this), + 'filter:activity-pub.video.jsonld.build.result', + { video: this } + ) } getTruncatedDescription () { diff --git a/server/tests/api/activitypub/security.ts b/server/tests/api/activitypub/security.ts index 22fae8331..c6f171633 100644 --- a/server/tests/api/activitypub/security.ts +++ b/server/tests/api/activitypub/security.ts @@ -2,10 +2,10 @@ import { expect } from 'chai' import { buildDigest } from '@server/helpers/peertube-crypto' -import { HTTP_SIGNATURE } from '@server/initializers/constants' +import { ACTIVITY_PUB, HTTP_SIGNATURE } from '@server/initializers/constants' import { activityPubContextify } from '@server/lib/activitypub/context' import { buildGlobalHeaders, signAndContextify } from '@server/lib/activitypub/send' -import { makeFollowRequest, makePOSTAPRequest } from '@server/tests/shared' +import { makePOSTAPRequest } from '@server/tests/shared' import { buildAbsoluteFixturePath, wait } from '@shared/core-utils' import { HttpStatusCode } from '@shared/models' import { cleanupTests, createMultipleServers, killallServers, PeerTubeServer } from '@shared/server-commands' @@ -43,6 +43,32 @@ function getAnnounceWithoutContext (server: PeerTubeServer) { return result } +async function makeFollowRequest (to: { url: string }, by: { url: string, privateKey }) { + const follow = { + type: 'Follow', + id: by.url + '/' + new Date().getTime(), + actor: by.url, + object: to.url + } + + const body = await activityPubContextify(follow, 'Follow') + + const httpSignature = { + algorithm: HTTP_SIGNATURE.ALGORITHM, + authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, + keyId: by.url, + key: by.privateKey, + headers: HTTP_SIGNATURE.HEADERS_TO_SIGN + } + const headers = { + 'digest': buildDigest(body), + 'content-type': 'application/activity+json', + 'accept': ACTIVITY_PUB.ACCEPT_HEADER + } + + return makePOSTAPRequest(to.url + '/inbox', body, httpSignature, headers) +} + describe('Test ActivityPub security', function () { let servers: PeerTubeServer[] let url: string @@ -77,7 +103,7 @@ describe('Test ActivityPub security', function () { describe('When checking HTTP signature', function () { it('Should fail with an invalid digest', async function () { - const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce') + const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce') const headers = { Digest: buildDigest({ hello: 'coucou' }) } @@ -91,7 +117,7 @@ describe('Test ActivityPub security', function () { }) it('Should fail with an invalid date', async function () { - const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce') + const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce') const headers = buildGlobalHeaders(body) headers['date'] = 'Wed, 21 Oct 2015 07:28:00 GMT' @@ -107,7 +133,7 @@ describe('Test ActivityPub security', function () { await setKeysOfServer(servers[0], servers[1], invalidKeys.publicKey, invalidKeys.privateKey) await setKeysOfServer(servers[1], servers[1], invalidKeys.publicKey, invalidKeys.privateKey) - const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce') + const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce') const headers = buildGlobalHeaders(body) try { @@ -122,7 +148,7 @@ describe('Test ActivityPub security', function () { await setKeysOfServer(servers[0], servers[1], keys.publicKey, keys.privateKey) await setKeysOfServer(servers[1], servers[1], keys.publicKey, keys.privateKey) - const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce') + const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce') const headers = buildGlobalHeaders(body) const signatureOptions = baseHttpSignature() @@ -145,7 +171,7 @@ describe('Test ActivityPub security', function () { }) it('Should succeed with a valid HTTP signature draft 11 (without date but with (created))', async function () { - const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce') + const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce') const headers = buildGlobalHeaders(body) const signatureOptions = baseHttpSignature() @@ -156,7 +182,7 @@ describe('Test ActivityPub security', function () { }) it('Should succeed with a valid HTTP signature', async function () { - const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce') + const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce') const headers = buildGlobalHeaders(body) const { statusCode } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers) @@ -175,7 +201,7 @@ describe('Test ActivityPub security', function () { await killallServers([ servers[1] ]) await servers[1].run() - const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce') + const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce') const headers = buildGlobalHeaders(body) try { diff --git a/server/tests/shared/requests.ts b/server/tests/shared/requests.ts index 57120caca..0cfeab7b2 100644 --- a/server/tests/shared/requests.ts +++ b/server/tests/shared/requests.ts @@ -1,7 +1,4 @@ -import { buildDigest } from '@server/helpers/peertube-crypto' import { doRequest } from '@server/helpers/requests' -import { ACTIVITY_PUB, HTTP_SIGNATURE } from '@server/initializers/constants' -import { activityPubContextify } from '@server/lib/activitypub/context' export function makePOSTAPRequest (url: string, body: any, httpSignature: any, headers: any) { const options = { @@ -13,29 +10,3 @@ export function makePOSTAPRequest (url: string, body: any, httpSignature: any, h return doRequest(url, options) } - -export async function makeFollowRequest (to: { url: string }, by: { url: string, privateKey }) { - const follow = { - type: 'Follow', - id: by.url + '/' + new Date().getTime(), - actor: by.url, - object: to.url - } - - const body = activityPubContextify(follow, 'Follow') - - const httpSignature = { - algorithm: HTTP_SIGNATURE.ALGORITHM, - authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, - keyId: by.url, - key: by.privateKey, - headers: HTTP_SIGNATURE.HEADERS_TO_SIGN - } - const headers = { - 'digest': buildDigest(body), - 'content-type': 'application/activity+json', - 'accept': ACTIVITY_PUB.ACCEPT_HEADER - } - - return makePOSTAPRequest(to.url + '/inbox', body, httpSignature, headers) -} diff --git a/shared/core-utils/i18n/i18n.ts b/shared/core-utils/i18n/i18n.ts index c43e249a6..38c1b0cc9 100644 --- a/shared/core-utils/i18n/i18n.ts +++ b/shared/core-utils/i18n/i18n.ts @@ -75,8 +75,7 @@ const I18N_LOCALE_ALIAS = { 'zh': 'zh-Hans-CN' } -export const POSSIBLE_LOCALES = Object.keys(I18N_LOCALES) - .concat(Object.keys(I18N_LOCALE_ALIAS)) +export const POSSIBLE_LOCALES = (Object.keys(I18N_LOCALES) as string[]).concat(Object.keys(I18N_LOCALE_ALIAS)) export function getDefaultLocale () { return 'en-US' diff --git a/shared/models/plugins/server/server-hook.model.ts b/shared/models/plugins/server/server-hook.model.ts index bbd08365c..7881beaa2 100644 --- a/shared/models/plugins/server/server-hook.model.ts +++ b/shared/models/plugins/server/server-hook.model.ts @@ -113,7 +113,13 @@ export const serverFilterHookObject = { 'filter:transcoding.manual.resolutions-to-transcode.result': true, 'filter:transcoding.auto.resolutions-to-transcode.result': true, - 'filter:activity-pub.remote-video-comment.create.accept.result': true + 'filter:activity-pub.remote-video-comment.create.accept.result': true, + + 'filter:activity-pub.activity.context.build.result': true, + + // Filter the result of video JSON LD builder + // You may also need to use filter:activity-pub.activity.context.build.result to also update JSON LD context + 'filter:activity-pub.video.jsonld.build.result': true } export type ServerFilterHookName = keyof typeof serverFilterHookObject -- cgit v1.2.3 From 96d00a997b9847f6796c02dbff01bca2c99385c6 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 10 Mar 2023 15:08:56 +0100 Subject: Add test on AP hooks --- .../+videos/+video-edit/shared/video-edit.component.ts | 4 ++-- server/lib/activitypub/send/send-update.ts | 2 +- server/models/video/video.ts | 2 +- server/tests/fixtures/peertube-plugin-test/main.js | 12 ++++++++++++ server/tests/plugins/filter-hooks.ts | 17 +++++++++++++++++ shared/models/plugins/server/server-hook.model.ts | 2 +- 6 files changed, 34 insertions(+), 5 deletions(-) diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts index e3bd5fe71..2bc0e6bb4 100644 --- a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts +++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts @@ -240,11 +240,11 @@ export class VideoEditComponent implements OnInit, OnDestroy { this.schedulerInterval = setInterval(() => this.minScheduledDate = new Date(), 1000 * 60) // Update every minute }) - const updateForm = (values: any) => { + const updateFormForPlugins = (values: any) => { this.form.patchValue(values) this.cd.detectChanges() } - this.hooks.runAction('action:video-edit.init', 'video-edit', { type: this.type, updateForm }) + this.hooks.runAction('action:video-edit.init', 'video-edit', { type: this.type, updateForm: updateFormForPlugins }) this.form.valueChanges.subscribe(() => { this.hooks.runAction('action:video-edit.form.updated', 'video-edit', { type: this.type, formValues: this.form.value }) diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts index 5a66294e6..379e2d9d8 100644 --- a/server/lib/activitypub/send/send-update.ts +++ b/server/lib/activitypub/send/send-update.ts @@ -59,7 +59,7 @@ async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefa logger.info('Creating job to update actor %s.', byActor.url) const url = getUpdateActivityPubUrl(byActor.url, byActor.updatedAt.toISOString()) - const accountOrChannelObject = (accountOrChannel as any).toActivityPubObject() // FIXME: typescript bug? + const accountOrChannelObject = await (accountOrChannel as any).toActivityPubObject() // FIXME: typescript bug? const audience = getAudience(byActor) const updateActivity = buildUpdateActivity(url, byActor, accountOrChannelObject, audience) diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 7fe2ec293..aa9c62e36 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -1717,7 +1717,7 @@ export class VideoModel extends Model>> { toActivityPubObject (this: MVideoAP): Promise { return Hooks.wrapObject( videoModelToActivityPubObject(this), - 'filter:activity-pub.video.jsonld.build.result', + 'filter:activity-pub.video.json-ld.build.result', { video: this } ) } diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js index 8b918b634..89ca52625 100644 --- a/server/tests/fixtures/peertube-plugin-test/main.js +++ b/server/tests/fixtures/peertube-plugin-test/main.js @@ -207,6 +207,18 @@ async function register ({ registerHook, registerSetting, settingsManager, stora // --------------------------------------------------------------------------- + registerHook({ + target: 'filter:activity-pub.activity.context.build.result', + handler: context => context.concat([ 'https://example.com/new-context' ]) + }) + + registerHook({ + target: 'filter:activity-pub.video.json-ld.build.result', + handler: (jsonld, { video }) => ({ ...jsonld, videoName: video.name }) + }) + + // --------------------------------------------------------------------------- + registerHook({ target: 'filter:api.video-threads.list.params', handler: obj => addToCount(obj) diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts index 6bd38cf65..3577fc022 100644 --- a/server/tests/plugins/filter-hooks.ts +++ b/server/tests/plugins/filter-hooks.ts @@ -14,6 +14,7 @@ import { cleanupTests, createMultipleServers, doubleFollow, + makeActivityPubGetRequest, makeGetRequest, makeRawRequest, PeerTubeServer, @@ -846,6 +847,22 @@ describe('Test plugin filter hooks', function () { }) }) + describe('Activity Pub', function () { + + it('Should run filter:activity-pub.activity.context.build.result', async function () { + const { body } = await makeActivityPubGetRequest(servers[0].url, '/w/' + videoUUID) + expect(body.type).to.equal('Video') + + expect(body['@context'].some(c => c === 'https://example.com/new-context')).to.be.true + }) + + it('Should run filter:activity-pub.video.json-ld.build.result', async function () { + const { body } = await makeActivityPubGetRequest(servers[0].url, '/w/' + videoUUID) + expect(body.name).to.equal('default video 0') + expect(body.videoName).to.equal('default video 0') + }) + }) + after(async function () { await cleanupTests(servers) }) diff --git a/shared/models/plugins/server/server-hook.model.ts b/shared/models/plugins/server/server-hook.model.ts index 7881beaa2..ca83672d0 100644 --- a/shared/models/plugins/server/server-hook.model.ts +++ b/shared/models/plugins/server/server-hook.model.ts @@ -119,7 +119,7 @@ export const serverFilterHookObject = { // Filter the result of video JSON LD builder // You may also need to use filter:activity-pub.activity.context.build.result to also update JSON LD context - 'filter:activity-pub.video.jsonld.build.result': true + 'filter:activity-pub.video.json-ld.build.result': true } export type ServerFilterHookName = keyof typeof serverFilterHookObject -- cgit v1.2.3 From 04852e14b4290b51e929d0f3019f9663ccf3ac24 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 10 Mar 2023 15:23:00 +0100 Subject: Don't send undefined with HTML input --- client/src/app/+videos/+video-edit/shared/video-edit.component.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts index 2bc0e6bb4..89687f35e 100644 --- a/client/src/app/+videos/+video-edit/shared/video-edit.component.ts +++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.ts @@ -355,6 +355,9 @@ export class VideoEditComponent implements OnInit, OnDestroy { for (const setting of this.pluginFields) { await this.pluginService.translateSetting(setting.pluginInfo.plugin.npmName, setting.commonOptions) + // Not a form input, just a HTML tag + if (setting.commonOptions.type === 'html') continue + const validator = async (control: AbstractControl) => { if (!setting.commonOptions.error) return null -- cgit v1.2.3 From 2dbcc3741b8b8ec20651e53965063eac1feb4ece Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 10 Mar 2023 15:45:31 +0100 Subject: Fix tests --- server/tests/fixtures/peertube-plugin-test/main.js | 2 +- server/tests/plugins/filter-hooks.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js index 89ca52625..60b8b3ccd 100644 --- a/server/tests/fixtures/peertube-plugin-test/main.js +++ b/server/tests/fixtures/peertube-plugin-test/main.js @@ -209,7 +209,7 @@ async function register ({ registerHook, registerSetting, settingsManager, stora registerHook({ target: 'filter:activity-pub.activity.context.build.result', - handler: context => context.concat([ 'https://example.com/new-context' ]) + handler: context => context.concat([ { recordedAt: 'https://schema.org/recordedAt' } ]) }) registerHook({ diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts index 3577fc022..7e0be4d4e 100644 --- a/server/tests/plugins/filter-hooks.ts +++ b/server/tests/plugins/filter-hooks.ts @@ -853,7 +853,9 @@ describe('Test plugin filter hooks', function () { const { body } = await makeActivityPubGetRequest(servers[0].url, '/w/' + videoUUID) expect(body.type).to.equal('Video') - expect(body['@context'].some(c => c === 'https://example.com/new-context')).to.be.true + expect(body['@context'].some(c => { + return typeof c === 'object' && c.recordedAt === 'https://schema.org/recordedAt' + })).to.be.true }) it('Should run filter:activity-pub.video.json-ld.build.result', async function () { -- cgit v1.2.3 From c105000de3e3468a211bfc72acaf785e52488bcd Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 14 Mar 2023 11:29:56 +0100 Subject: Prevent layout shift on th hover --- client/src/sass/primeng-custom.scss | 7 ------- 1 file changed, 7 deletions(-) diff --git a/client/src/sass/primeng-custom.scss b/client/src/sass/primeng-custom.scss index 18a5bb1b6..7f65eded7 100644 --- a/client/src/sass/primeng-custom.scss +++ b/client/src/sass/primeng-custom.scss @@ -555,13 +555,6 @@ p-table { &.p-sortable-column:hover { background-color: pvar(--submenuBackgroundColor) !important; - border: 1px solid !important; - border-color: pvar(--submenuBackgroundColor) !important; - border-width: 0 1px !important; - - &:first-child { - border-width: 0 1px 0 0 !important; - } } &.p-highlight { -- cgit v1.2.3 From 2296dfd4dbdd6e00292ef441861908aacec4646f Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 14 Mar 2023 11:33:10 +0100 Subject: Fix button width --- client/src/app/+admin/overview/users/user-list/user-list.component.html | 2 +- client/src/app/+admin/overview/videos/video-list.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.html b/client/src/app/+admin/overview/users/user-list/user-list.component.html index 7eb5e0fc7..b7467d2cb 100644 --- a/client/src/app/+admin/overview/users/user-list/user-list.component.html +++ b/client/src/app/+admin/overview/users/user-list/user-list.component.html @@ -39,7 +39,7 @@ - +
diff --git a/client/src/app/+admin/overview/videos/video-list.component.html b/client/src/app/+admin/overview/videos/video-list.component.html index 5b8405ad9..35b1f9dbd 100644 --- a/client/src/app/+admin/overview/videos/video-list.component.html +++ b/client/src/app/+admin/overview/videos/video-list.component.html @@ -36,7 +36,7 @@ - + Video Info Files -- cgit v1.2.3