aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html7
-rw-r--r--client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss6
-rw-r--r--client/src/app/header/header.component.html2
-rw-r--r--client/src/app/header/header.component.scss4
-rw-r--r--client/src/app/header/search-typeahead.component.scss4
-rw-r--r--client/src/app/menu/notification.component.scss6
-rw-r--r--client/src/app/shared/shared-main/buttons/action-dropdown.component.html2
-rw-r--r--client/src/app/shared/shared-main/buttons/action-dropdown.component.scss3
-rw-r--r--client/src/sass/include/_mixins.scss32
-rw-r--r--client/src/sass/include/_variables.scss1
-rw-r--r--client/src/sass/ng-select.scss3
-rw-r--r--server/lib/activitypub/videos/shared/creator.ts3
-rw-r--r--server/lib/activitypub/videos/updater.ts3
-rw-r--r--server/tests/fixtures/peertube-plugin-test/main.js71
-rw-r--r--server/tests/plugins/action-hooks.ts24
-rw-r--r--shared/models/plugins/server/server-hook.model.ts8
-rw-r--r--support/doc/plugins/guide.md44
17 files changed, 158 insertions, 65 deletions
diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html
index b98cd1156..e3b226768 100644
--- a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html
+++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html
@@ -1,8 +1,9 @@
1<h1 class="visually-hidden" i18n>Notifications</h1> 1<h1 class="visually-hidden" i18n>Notifications</h1>
2
2<div class="header"> 3<div class="header">
3 <a routerLink="/my-account/settings" fragment="notifications" i18n> 4 <a class="peertube-button-link grey-button" routerLink="/my-account/settings" fragment="notifications">
4 <my-global-icon iconName="cog" aria-hidden="true"></my-global-icon> 5 <my-global-icon iconName="cog" aria-hidden="true"></my-global-icon>
5 Notification preferences 6 <span i18n>Notification preferences</span>
6 </a> 7 </a>
7 8
8 <div class="peertube-select-container peertube-select-button ms-2 me-2"> 9 <div class="peertube-select-container peertube-select-button ms-2 me-2">
@@ -13,7 +14,7 @@
13 </select> 14 </select>
14 </div> 15 </div>
15 16
16 <button class="btn ms-auto" [disabled]="!hasUnreadNotifications()" (click)="markAllAsRead()"> 17 <button class="ms-auto peertube-button grey-button" [disabled]="!hasUnreadNotifications()" (click)="markAllAsRead()">
17 <ng-container *ngIf="hasUnreadNotifications()"> 18 <ng-container *ngIf="hasUnreadNotifications()">
18 <my-global-icon iconName="tick" aria-hidden="true"></my-global-icon> 19 <my-global-icon iconName="tick" aria-hidden="true"></my-global-icon>
19 20
diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss
index d412e568f..cee338991 100644
--- a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss
+++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss
@@ -3,17 +3,13 @@
3 3
4.header { 4.header {
5 display: flex; 5 display: flex;
6 margin-bottom: 20px; 6 margin-bottom: 1.25rem;
7 7
8 a { 8 a {
9 @include peertube-button-link;
10 @include grey-button;
11 @include button-with-icon(18px, 3px, -1px); 9 @include button-with-icon(18px, 3px, -1px);
12 } 10 }
13 11
14 button { 12 button {
15 @include peertube-button;
16 @include grey-button;
17 @include button-with-icon(20px, 3px, -1px); 13 @include button-with-icon(20px, 3px, -1px);
18 } 14 }
19 15
diff --git a/client/src/app/header/header.component.html b/client/src/app/header/header.component.html
index b5e9e3dd8..4e327769d 100644
--- a/client/src/app/header/header.component.html
+++ b/client/src/app/header/header.component.html
@@ -1,6 +1,6 @@
1<my-search-typeahead class="w-100 d-flex justify-content-center"></my-search-typeahead> 1<my-search-typeahead class="w-100 d-flex justify-content-center"></my-search-typeahead>
2 2
3<a class="publish-button" routerLink="/videos/upload"> 3<a class="peertube-button-link orange-button publish-button" routerLink="/videos/upload">
4 <my-global-icon iconName="upload" aria-hidden="true"></my-global-icon> 4 <my-global-icon iconName="upload" aria-hidden="true"></my-global-icon>
5 <span i18n class="publish-button-label">Publish</span> 5 <span i18n class="publish-button-label">Publish</span>
6</a> 6</a>
diff --git a/client/src/app/header/header.component.scss b/client/src/app/header/header.component.scss
index ef7749ced..37bee4645 100644
--- a/client/src/app/header/header.component.scss
+++ b/client/src/app/header/header.component.scss
@@ -2,9 +2,7 @@
2@use '_mixins' as *; 2@use '_mixins' as *;
3 3
4.publish-button { 4.publish-button {
5 @include peertube-button-link; 5 @include button-with-icon(21px, 3px, -1px);
6 @include orange-button;
7 @include button-with-icon(22px, 3px, -1px);
8 @include margin-right(25px); 6 @include margin-right(25px);
9 7
10 @media screen and (max-width: $mobile-view) { 8 @media screen and (max-width: $mobile-view) {
diff --git a/client/src/app/header/search-typeahead.component.scss b/client/src/app/header/search-typeahead.component.scss
index 48b61cf3f..b96853400 100644
--- a/client/src/app/header/search-typeahead.component.scss
+++ b/client/src/app/header/search-typeahead.component.scss
@@ -2,13 +2,11 @@
2@use '_mixins' as *; 2@use '_mixins' as *;
3 3
4#search-video { 4#search-video {
5 @include peertube-input-text($search-input-width); 5 @include peertube-input-text($search-input-width, 14px);
6 6
7 @include padding-left(10px); 7 @include padding-left(10px);
8 @include padding-right(40px); // For the search icon 8 @include padding-right(40px); // For the search icon
9 9
10 font-size: 14px;
11
12 &::placeholder { 10 &::placeholder {
13 color: pvar(--inputPlaceholderColor); 11 color: pvar(--inputPlaceholderColor);
14 } 12 }
diff --git a/client/src/app/menu/notification.component.scss b/client/src/app/menu/notification.component.scss
index 256ed7328..7f70aeff6 100644
--- a/client/src/app/menu/notification.component.scss
+++ b/client/src/app/menu/notification.component.scss
@@ -120,9 +120,13 @@
120 } 120 }
121 } 121 }
122 122
123 my-global-icon { 123 my-global-icon[iconName=cog] {
124 width: 20px; 124 width: 20px;
125 } 125 }
126
127 my-global-icon[iconName=tick] {
128 width: 26px;
129 }
126 } 130 }
127 131
128 .all-notifications { 132 .all-notifications {
diff --git a/client/src/app/shared/shared-main/buttons/action-dropdown.component.html b/client/src/app/shared/shared-main/buttons/action-dropdown.component.html
index 17acb3eab..bbfab7b37 100644
--- a/client/src/app/shared/shared-main/buttons/action-dropdown.component.html
+++ b/client/src/app/shared/shared-main/buttons/action-dropdown.component.html
@@ -1,7 +1,7 @@
1<div class="dropdown-root" ngbDropdown [placement]="placement" [container]="container" *ngIf="areActionsDisplayed(actions, entry)"> 1<div class="dropdown-root" ngbDropdown [placement]="placement" [container]="container" *ngIf="areActionsDisplayed(actions, entry)">
2 <button 2 <button
3 class="action-button" [ngClass]="{ small: buttonSize === 'small', grey: theme === 'grey', orange: theme === 'orange', 'button-styled': buttonStyled }" 3 class="action-button" [ngClass]="{ small: buttonSize === 'small', grey: theme === 'grey', orange: theme === 'orange', 'button-styled': buttonStyled }"
4 ngbDropdownToggle role="button" aria-label="Open actions" i18n-aria-label 4 ngbDropdownToggle aria-label="Open actions" i18n-aria-label
5 > 5 >
6 <my-global-icon *ngIf="!label && buttonDirection === 'horizontal'" class="more-icon" iconName="more-horizontal"></my-global-icon> 6 <my-global-icon *ngIf="!label && buttonDirection === 'horizontal'" class="more-icon" iconName="more-horizontal"></my-global-icon>
7 <my-global-icon *ngIf="!label && buttonDirection === 'vertical'" class="more-icon" iconName="more-vertical"></my-global-icon> 7 <my-global-icon *ngIf="!label && buttonDirection === 'vertical'" class="more-icon" iconName="more-vertical"></my-global-icon>
diff --git a/client/src/app/shared/shared-main/buttons/action-dropdown.component.scss b/client/src/app/shared/shared-main/buttons/action-dropdown.component.scss
index 5d400c0f7..8e5bb266f 100644
--- a/client/src/app/shared/shared-main/buttons/action-dropdown.component.scss
+++ b/client/src/app/shared/shared-main/buttons/action-dropdown.component.scss
@@ -41,8 +41,7 @@
41 41
42 &.small { 42 &.small {
43 font-size: 14px; 43 font-size: 14px;
44 height: 20px; 44 padding: 0 10px;
45 line-height: 20px;
46 } 45 }
47} 46}
48 47
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss
index 004f49262..1ce584f9b 100644
--- a/client/src/sass/include/_mixins.scss
+++ b/client/src/sass/include/_mixins.scss
@@ -80,8 +80,16 @@
80 } 80 }
81} 81}
82 82
83@mixin peertube-input-text($width) { 83@mixin rounded-line-height-1-5 ($font-size) {
84 padding: 4px 15px; 84 line-height: $font-size + math.round(math.div($font-size, 2));
85}
86
87@mixin peertube-input-text($width, $font-size: $form-input-font-size) {
88 @include rounded-line-height-1-5($font-size);
89
90 font-size: $font-size;
91
92 padding: 3px 15px;
85 display: inline-block; 93 display: inline-block;
86 width: $width; 94 width: $width;
87 max-width: $width; 95 max-width: $width;
@@ -89,8 +97,6 @@
89 background-color: pvar(--inputBackgroundColor); 97 background-color: pvar(--inputBackgroundColor);
90 border: 1px solid pvar(--inputBorderColor); 98 border: 1px solid pvar(--inputBorderColor);
91 border-radius: 3px; 99 border-radius: 3px;
92 font-size: $form-input-font-size;
93 line-height: $form-input-line-height;
94 100
95 &::placeholder { 101 &::placeholder {
96 color: pvar(--inputPlaceholderColor); 102 color: pvar(--inputPlaceholderColor);
@@ -241,6 +247,8 @@
241} 247}
242 248
243@mixin peertube-button { 249@mixin peertube-button {
250 @include rounded-line-height-1-5($button-font-size);
251
244 padding: 4px 13px; 252 padding: 4px 13px;
245 253
246 border: 0; 254 border: 0;
@@ -253,7 +261,6 @@
253 cursor: pointer; 261 cursor: pointer;
254 262
255 font-size: $button-font-size; 263 font-size: $button-font-size;
256 line-height: $button-font-size + math.round(math.div($button-font-size, 2));
257 264
258 my-global-icon + * { 265 my-global-icon + * {
259 @include margin-right(4px); 266 @include margin-right(4px);
@@ -303,10 +310,6 @@
303 width: $width; 310 width: $width;
304 top: $top; 311 top: $top;
305 } 312 }
306
307 span {
308 vertical-align: middle;
309 }
310} 313}
311 314
312@mixin peertube-file { 315@mixin peertube-file {
@@ -397,15 +400,17 @@
397 } 400 }
398 401
399 select { 402 select {
400 padding: 4px 35px 4px 12px; 403 @include rounded-line-height-1-5($form-input-font-size);
404
405 font-size: $form-input-font-size;
406
407 padding: 3px 35px 3px 12px;
401 position: relative; 408 position: relative;
402 border: 1px solid pvar(--inputBorderColor); 409 border: 1px solid pvar(--inputBorderColor);
403 background: transparent none; 410 background: transparent none;
404 appearance: none; 411 appearance: none;
405 text-overflow: ellipsis; 412 text-overflow: ellipsis;
406 color: pvar(--mainForegroundColor); 413 color: pvar(--mainForegroundColor);
407 font-size: $form-input-font-size;
408 line-height: $form-input-line-height;
409 414
410 &:focus { 415 &:focus {
411 outline: none; 416 outline: none;
@@ -432,6 +437,9 @@
432 font-weight: $font-semibold; 437 font-weight: $font-semibold;
433 color: pvar(--greyForegroundColor); 438 color: pvar(--greyForegroundColor);
434 border: 0; 439 border: 0;
440
441 // No border, add +1 to vertical padding
442 padding: 4px 35px 4px 12px;
435 } 443 }
436 } 444 }
437} 445}
diff --git a/client/src/sass/include/_variables.scss b/client/src/sass/include/_variables.scss
index 8358270da..1eb3135f4 100644
--- a/client/src/sass/include/_variables.scss
+++ b/client/src/sass/include/_variables.scss
@@ -96,7 +96,6 @@ $activated-action-button-color: #212529;
96 96
97$focus-box-shadow-form: 0 0 0 .2rem; 97$focus-box-shadow-form: 0 0 0 .2rem;
98$form-input-font-size: 15px; 98$form-input-font-size: 15px;
99$form-input-line-height: 1.4;
100 99
101$video-watch-player-factor: math.div(16, 9); 100$video-watch-player-factor: math.div(16, 9);
102$video-watch-info-margin-left: 44px; 101$video-watch-info-margin-left: 44px;
diff --git a/client/src/sass/ng-select.scss b/client/src/sass/ng-select.scss
index dfe1f6f0d..4c7258232 100644
--- a/client/src/sass/ng-select.scss
+++ b/client/src/sass/ng-select.scss
@@ -35,8 +35,9 @@ $ng-select-input-text: pvar(--mainForegroundColor);
35@import '@ng-select/ng-select/scss/default.theme'; 35@import '@ng-select/ng-select/scss/default.theme';
36 36
37.ng-select { 37.ng-select {
38 @include rounded-line-height-1-5($ng-select-value-font-size);
39
38 font-size: $ng-select-value-font-size; 40 font-size: $ng-select-value-font-size;
39 line-height: $form-input-line-height;
40 41
41 &.ng-select-focused { 42 &.ng-select-focused {
42 &:not(.ng-select-opened) > .ng-select-container { 43 &:not(.ng-select-opened) > .ng-select-container {
diff --git a/server/lib/activitypub/videos/shared/creator.ts b/server/lib/activitypub/videos/shared/creator.ts
index 07252fea2..77321d8a5 100644
--- a/server/lib/activitypub/videos/shared/creator.ts
+++ b/server/lib/activitypub/videos/shared/creator.ts
@@ -1,6 +1,7 @@
1 1
2import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger' 2import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger'
3import { sequelizeTypescript } from '@server/initializers/database' 3import { sequelizeTypescript } from '@server/initializers/database'
4import { Hooks } from '@server/lib/plugins/hooks'
4import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist' 5import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist'
5import { VideoModel } from '@server/models/video/video' 6import { VideoModel } from '@server/models/video/video'
6import { MThumbnail, MVideoFullLight, MVideoThumbnail } from '@server/types/models' 7import { MThumbnail, MVideoFullLight, MVideoThumbnail } from '@server/types/models'
@@ -61,6 +62,8 @@ export class APVideoCreator extends APVideoAbstractBuilder {
61 62
62 logger.info('Remote video with uuid %s inserted.', this.videoObject.uuid, this.lTags()) 63 logger.info('Remote video with uuid %s inserted.', this.videoObject.uuid, this.lTags())
63 64
65 Hooks.runAction('action:activity-pub.remote-video.created', { video: videoCreated, videoAPObject: this.videoObject })
66
64 return { autoBlacklisted, videoCreated } 67 return { autoBlacklisted, videoCreated }
65 } catch (err) { 68 } catch (err) {
66 // FIXME: Use rollback hook when https://github.com/sequelize/sequelize/pull/13038 is released 69 // FIXME: Use rollback hook when https://github.com/sequelize/sequelize/pull/13038 is released
diff --git a/server/lib/activitypub/videos/updater.ts b/server/lib/activitypub/videos/updater.ts
index 0bf32f440..3677dc3bb 100644
--- a/server/lib/activitypub/videos/updater.ts
+++ b/server/lib/activitypub/videos/updater.ts
@@ -3,6 +3,7 @@ import { resetSequelizeInstance, runInReadCommittedTransaction } from '@server/h
3import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger' 3import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger'
4import { Notifier } from '@server/lib/notifier' 4import { Notifier } from '@server/lib/notifier'
5import { PeerTubeSocket } from '@server/lib/peertube-socket' 5import { PeerTubeSocket } from '@server/lib/peertube-socket'
6import { Hooks } from '@server/lib/plugins/hooks'
6import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist' 7import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist'
7import { VideoLiveModel } from '@server/models/video/video-live' 8import { VideoLiveModel } from '@server/models/video/video-live'
8import { MActor, MChannelAccountLight, MChannelId, MVideoAccountLightBlacklistAllFiles, MVideoFullLight } from '@server/types/models' 9import { MActor, MChannelAccountLight, MChannelId, MVideoAccountLightBlacklistAllFiles, MVideoFullLight } from '@server/types/models'
@@ -81,6 +82,8 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
81 PeerTubeSocket.Instance.sendVideoLiveNewState(videoUpdated) 82 PeerTubeSocket.Instance.sendVideoLiveNewState(videoUpdated)
82 } 83 }
83 84
85 Hooks.runAction('action:activity-pub.remote-video.updated', { video: videoUpdated, videoAPObject: this.videoObject })
86
84 logger.info('Remote video with uuid %s updated', this.videoObject.uuid, this.lTags()) 87 logger.info('Remote video with uuid %s updated', this.videoObject.uuid, this.lTags())
85 88
86 return videoUpdated 89 return videoUpdated
diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js
index 5325e14c4..84b479548 100644
--- a/server/tests/fixtures/peertube-plugin-test/main.js
+++ b/server/tests/fixtures/peertube-plugin-test/main.js
@@ -1,42 +1,53 @@
1async function register ({ registerHook, registerSetting, settingsManager, storageManager, peertubeHelpers }) { 1async function register ({ registerHook, registerSetting, settingsManager, storageManager, peertubeHelpers }) {
2 const actionHooks = [ 2 {
3 'action:application.listening', 3 const actionHooks = [
4 'action:notifier.notification.created', 4 'action:application.listening',
5 'action:notifier.notification.created',
5 6
6 'action:api.video.updated', 7 'action:api.video.updated',
7 'action:api.video.deleted', 8 'action:api.video.deleted',
8 'action:api.video.uploaded', 9 'action:api.video.uploaded',
9 'action:api.video.viewed', 10 'action:api.video.viewed',
10 11
11 'action:api.video-channel.created', 12 'action:api.video-channel.created',
12 'action:api.video-channel.updated', 13 'action:api.video-channel.updated',
13 'action:api.video-channel.deleted', 14 'action:api.video-channel.deleted',
14 15
15 'action:api.live-video.created', 16 'action:api.live-video.created',
16 17
17 'action:api.video-thread.created', 18 'action:api.video-thread.created',
18 'action:api.video-comment-reply.created', 19 'action:api.video-comment-reply.created',
19 'action:api.video-comment.deleted', 20 'action:api.video-comment.deleted',
20 21
21 'action:api.video-caption.created', 22 'action:api.video-caption.created',
22 'action:api.video-caption.deleted', 23 'action:api.video-caption.deleted',
23 24
24 'action:api.user.blocked', 25 'action:api.user.blocked',
25 'action:api.user.unblocked', 26 'action:api.user.unblocked',
26 'action:api.user.registered', 27 'action:api.user.registered',
27 'action:api.user.created', 28 'action:api.user.created',
28 'action:api.user.deleted', 29 'action:api.user.deleted',
29 'action:api.user.updated', 30 'action:api.user.updated',
30 'action:api.user.oauth2-got-token', 31 'action:api.user.oauth2-got-token',
31 32
32 'action:api.video-playlist-element.created' 33 'action:api.video-playlist-element.created'
33 ] 34 ]
34 35
35 for (const h of actionHooks) { 36 for (const h of actionHooks) {
36 registerHook({ 37 registerHook({
37 target: h, 38 target: h,
38 handler: () => peertubeHelpers.logger.debug('Run hook %s.', h) 39 handler: () => peertubeHelpers.logger.debug('Run hook %s.', h)
39 }) 40 })
41 }
42
43 for (const h of [ 'action:activity-pub.remote-video.created', 'action:activity-pub.remote-video.updated' ]) {
44 registerHook({
45 target: h,
46 handler: ({ video, videoAPObject }) => {
47 peertubeHelpers.logger.debug('Run hook %s - AP %s - video %s.', h, video.name, videoAPObject.name )
48 }
49 })
50 }
40 } 51 }
41 52
42 registerHook({ 53 registerHook({
diff --git a/server/tests/plugins/action-hooks.ts b/server/tests/plugins/action-hooks.ts
index a266ae7f1..98228f79d 100644
--- a/server/tests/plugins/action-hooks.ts
+++ b/server/tests/plugins/action-hooks.ts
@@ -4,6 +4,7 @@ import { ServerHookName, VideoPlaylistPrivacy, VideoPrivacy } from '@shared/mode
4import { 4import {
5 cleanupTests, 5 cleanupTests,
6 createMultipleServers, 6 createMultipleServers,
7 doubleFollow,
7 killallServers, 8 killallServers,
8 PeerTubeServer, 9 PeerTubeServer,
9 PluginsCommand, 10 PluginsCommand,
@@ -36,6 +37,8 @@ describe('Test plugin action hooks', function () {
36 enabled: true 37 enabled: true
37 } 38 }
38 }) 39 })
40
41 await doubleFollow(servers[0], servers[1])
39 }) 42 })
40 43
41 describe('Application hooks', function () { 44 describe('Application hooks', function () {
@@ -231,6 +234,27 @@ describe('Test plugin action hooks', function () {
231 }) 234 })
232 }) 235 })
233 236
237 describe('Activity Pub hooks', function () {
238 let videoUUID: string
239
240 it('Should run action:activity-pub.remote-video.created', async function () {
241 this.timeout(30000)
242
243 const { uuid } = await servers[1].videos.quickUpload({ name: 'remote video' })
244 videoUUID = uuid
245
246 await servers[0].servers.waitUntilLog('action:activity-pub.remote-video.created - AP remote video - video remote video')
247 })
248
249 it('Should run action:activity-pub.remote-video.updated', async function () {
250 this.timeout(30000)
251
252 await servers[1].videos.update({ id: videoUUID, attributes: { name: 'remote video updated' } })
253
254 await servers[0].servers.waitUntilLog('action:activity-pub.remote-video.updated - AP remote video - video remote video')
255 })
256 })
257
234 after(async function () { 258 after(async function () {
235 await cleanupTests(servers) 259 await cleanupTests(servers)
236 }) 260 })
diff --git a/shared/models/plugins/server/server-hook.model.ts b/shared/models/plugins/server/server-hook.model.ts
index ca83672d0..d2ebe936e 100644
--- a/shared/models/plugins/server/server-hook.model.ts
+++ b/shared/models/plugins/server/server-hook.model.ts
@@ -1,4 +1,4 @@
1// {hookType}:{api?}.{location}.{subLocation?}.{actionType}.{target} 1// {hookType}:{root}.{location}.{subLocation?}.{actionType}.{target}
2 2
3export const serverFilterHookObject = { 3export const serverFilterHookObject = {
4 // Filter params/result used to list videos for the REST API 4 // Filter params/result used to list videos for the REST API
@@ -184,7 +184,11 @@ export const serverActionHookObject = {
184 'action:api.user.oauth2-got-token': true, 184 'action:api.user.oauth2-got-token': true,
185 185
186 // Fired when a video is added to a playlist 186 // Fired when a video is added to a playlist
187 'action:api.video-playlist-element.created': true 187 'action:api.video-playlist-element.created': true,
188
189 // Fired when a remote video has been created/updated
190 'action:activity-pub.remote-video.created': true,
191 'action:activity-pub.remote-video.updated': true
188} 192}
189 193
190export type ServerActionHookName = keyof typeof serverActionHookObject 194export type ServerActionHookName = keyof typeof serverActionHookObject
diff --git a/support/doc/plugins/guide.md b/support/doc/plugins/guide.md
index 337f3d97f..c5e3236ca 100644
--- a/support/doc/plugins/guide.md
+++ b/support/doc/plugins/guide.md
@@ -16,6 +16,7 @@
16 - [Add external auth methods](#add-external-auth-methods) 16 - [Add external auth methods](#add-external-auth-methods)
17 - [Add new transcoding profiles](#add-new-transcoding-profiles) 17 - [Add new transcoding profiles](#add-new-transcoding-profiles)
18 - [Server helpers](#server-helpers) 18 - [Server helpers](#server-helpers)
19 - [Federation](#federation)
19 - [Client API (themes & plugins)](#client-api-themes--plugins) 20 - [Client API (themes & plugins)](#client-api-themes--plugins)
20 - [Get plugin static and router routes](#get-plugin-static-and-router-routes) 21 - [Get plugin static and router routes](#get-plugin-static-and-router-routes)
21 - [Notifier](#notifier) 22 - [Notifier](#notifier)
@@ -587,6 +588,49 @@ async function register ({
587 588
588See the [plugin API reference](https://docs.joinpeertube.org/api/plugins) to see the complete helpers list. 589See the [plugin API reference](https://docs.joinpeertube.org/api/plugins) to see the complete helpers list.
589 590
591#### Federation
592
593You can use some server hooks to federate plugin data to other PeerTube instances that may have installed your plugin.
594
595For example to federate additional video metadata:
596
597```js
598async function register ({ registerHook }) {
599
600 // Send plugin metadata to remote instances
601 // We also update the JSON LD context because we added a new field
602 {
603 registerHook({
604 target: 'filter:activity-pub.video.json-ld.build.result',
605 handler: async (jsonld, { video }) => {
606 return Object.assign(jsonld, { recordedAt: 'https://example.com/event' })
607 }
608 })
609
610 registerHook({
611 target: 'filter:activity-pub.activity.context.build.result',
612 handler: jsonld => {
613 return jsonld.concat([ { recordedAt: 'https://schema.org/recordedAt' } ])
614 }
615 })
616 }
617
618 // Save remote video metadata
619 {
620 for (const h of [ 'action:activity-pub.remote-video.created', 'action:activity-pub.remote-video.updated' ]) {
621 registerHook({
622 target: h,
623 handler: ({ video, videoAPObject }) => {
624 if (videoAPObject.recordedAt) {
625 // Save information about the video
626 }
627 }
628 })
629 }
630 }
631```
632
633
590### Client API (themes & plugins) 634### Client API (themes & plugins)
591 635
592#### Get plugin static and router routes 636#### Get plugin static and router routes