]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - support/doc/plugins/guide.md
d95b7649b462cbb3c5aa91989e20968f3e96fb9d
[github/Chocobozzz/PeerTube.git] / support / doc / plugins / guide.md
1 # Plugins & Themes
2
3 <!-- START doctoc generated TOC please keep comment here to allow auto update -->
4 <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
5
6
7 - [Concepts](#concepts)
8 - [Hooks](#hooks)
9 - [Static files](#static-files)
10 - [CSS](#css)
11 - [Server helpers (only for plugins)](#server-helpers-only-for-plugins)
12 - [Settings](#settings)
13 - [Storage](#storage)
14 - [Update video constants](#update-video-constants)
15 - [Add custom routes](#add-custom-routes)
16 - [Client helpers (themes & plugins)](#client-helpers-themes--plugins)
17 - [Plugin static route](#plugin-static-route)
18 - [Notifier](#notifier)
19 - [Markdown Renderer](#markdown-renderer)
20 - [Custom Modal](#custom-modal)
21 - [Translate](#translate)
22 - [Get public settings](#get-public-settings)
23 - [Publishing](#publishing)
24 - [Write a plugin/theme](#write-a-plugintheme)
25 - [Clone the quickstart repository](#clone-the-quickstart-repository)
26 - [Configure your repository](#configure-your-repository)
27 - [Update README](#update-readme)
28 - [Update package.json](#update-packagejson)
29 - [Write code](#write-code)
30 - [Add translations](#add-translations)
31 - [Test your plugin/theme](#test-your-plugintheme)
32 - [Publish](#publish)
33 - [Plugin & Theme hooks/helpers API](#plugin--theme-hookshelpers-api)
34 - [Tips](#tips)
35 - [Compatibility with PeerTube](#compatibility-with-peertube)
36 - [Spam/moderation plugin](#spammoderation-plugin)
37 - [Other plugin examples](#other-plugin-examples)
38
39 <!-- END doctoc generated TOC please keep comment here to allow auto update -->
40
41 ## Concepts
42
43 Themes are exactly the same as plugins, except that:
44 * Their name starts with `peertube-theme-` instead of `peertube-plugin-`
45 * They cannot declare server code (so they cannot register server hooks or settings)
46 * CSS files are loaded by client only if the theme is chosen by the administrator or the user
47
48 ### Hooks
49
50 A plugin registers functions in JavaScript to execute when PeerTube (server and client) fires events. There are 3 types of hooks:
51 * `filter`: used to filter functions parameters or return values.
52 For example to replace words in video comments, or change the videos list behaviour
53 * `action`: used to do something after a certain trigger. For example to send a hook every time a video is published
54 * `static`: same than `action` but PeerTube waits their execution
55
56 On server side, these hooks are registered by the `library` file defined in `package.json`.
57
58 ```json
59 {
60 ...,
61 "library": "./main.js",
62 ...,
63 }
64 ```
65
66 And `main.js` defines a `register` function:
67
68 Example:
69
70 ```js
71 async function register ({
72 registerHook,
73
74 registerSetting,
75 settingsManager,
76
77 storageManager,
78
79 videoCategoryManager,
80 videoLicenceManager,
81 videoLanguageManager,
82
83 peertubeHelpers,
84
85 getRouter,
86
87 registerExternalAuth,
88 unregisterExternalAuth,
89 registerIdAndPassAuth,
90 unregisterIdAndPassAuth
91 }) {
92 registerHook({
93 target: 'action:application.listening',
94 handler: () => displayHelloWorld()
95 })
96 }
97 ```
98
99
100 On client side, these hooks are registered by the `clientScripts` files defined in `package.json`.
101 All client scripts have scopes so PeerTube client only loads scripts it needs:
102
103 ```json
104 {
105 ...,
106 "clientScripts": [
107 {
108 "script": "client/common-client-plugin.js",
109 "scopes": [ "common" ]
110 },
111 {
112 "script": "client/video-watch-client-plugin.js",
113 "scopes": [ "video-watch" ]
114 }
115 ],
116 ...
117 }
118 ```
119
120 And these scripts also define a `register` function:
121
122 ```js
123 function register ({ registerHook, peertubeHelpers }) {
124 registerHook({
125 target: 'action:application.init',
126 handler: () => onApplicationInit(peertubeHelpers)
127 })
128 }
129 ```
130
131 ### Static files
132
133 Plugins can declare static directories that PeerTube will serve (images for example)
134 from `/plugins/{plugin-name}/{plugin-version}/static/`
135 or `/themes/{theme-name}/{theme-version}/static/` routes.
136
137 ### CSS
138
139 Plugins can declare CSS files that PeerTube will automatically inject in the client.
140 If you need to override existing style, you can use the `#custom-css` selector:
141
142 ```
143 body#custom-css {
144 color: red;
145 }
146
147 #custom-css .header {
148 background-color: red;
149 }
150 ```
151
152 ### Server helpers (only for plugins)
153
154 #### Settings
155
156 Plugins can register settings, that PeerTube will inject in the administration interface.
157
158 Example:
159
160 ```js
161 registerSetting({
162 name: 'admin-name',
163 label: 'Admin name',
164 type: 'input',
165 // type: input | input-checkbox | input-textarea | markdown-text | markdown-enhanced
166 default: 'my super name'
167 })
168
169 const adminName = await settingsManager.getSetting('admin-name')
170
171 const result = await settingsManager.getSettings([ 'admin-name', 'admin-password' ])
172 result['admin-name]
173
174 settingsManager.onSettingsChange(settings => {
175 settings['admin-name])
176 })
177 ```
178
179 #### Storage
180
181 Plugins can store/load JSON data, that PeerTube will store in its database (so don't put files in there).
182
183 Example:
184
185 ```js
186 const value = await storageManager.getData('mykey')
187 await storageManager.storeData('mykey', { subkey: 'value' })
188 ```
189
190 #### Update video constants
191
192 You can add/delete video categories, licences or languages using the appropriate managers:
193
194 ```js
195 videoLanguageManager.addLanguage('al_bhed', 'Al Bhed')
196 videoLanguageManager.deleteLanguage('fr')
197
198 videoCategoryManager.addCategory(42, 'Best category')
199 videoCategoryManager.deleteCategory(1) // Music
200
201 videoLicenceManager.addLicence(42, 'Best licence')
202 videoLicenceManager.deleteLicence(7) // Public domain
203
204 videoPrivacyManager.deletePrivacy(2) // Remove Unlisted video privacy
205 playlistPrivacyManager.deletePlaylistPrivacy(3) // Remove Private video playlist privacy
206 ```
207
208 #### Add custom routes
209
210 You can create custom routes using an [express Router](https://expressjs.com/en/4x/api.html#router) for your plugin:
211
212 ```js
213 const router = getRouter()
214 router.get('/ping', (req, res) => res.json({ message: 'pong' }))
215 ```
216
217 The `ping` route can be accessed using:
218 * `/plugins/:pluginName/:pluginVersion/router/ping`
219 * Or `/plugins/:pluginName/router/ping`
220
221
222 #### Add external auth methods
223
224 If you want to add a classic username/email and password auth method (like [LDAP](https://framagit.org/framasoft/peertube/official-plugins/-/tree/master/peertube-plugin-auth-ldap) for example):
225
226 ```js
227 registerIdAndPassAuth({
228 authName: 'my-auth-method',
229
230 // PeerTube will try all id and pass plugins in the weight DESC order
231 // Exposing this value in the plugin settings could be interesting
232 getWeight: () => 60,
233
234 // Optional function called by PeerTube when the user clicked on the logout button
235 onLogout: user => {
236 console.log('User %s logged out.', user.username')
237 },
238
239 // Optional function called by PeerTube when the access token or refresh token are generated/refreshed
240 hookTokenValidity: ({ token, type }) => {
241 if (type === 'access') return { valid: true }
242 if (type === 'refresh') return { valid: false }
243 },
244
245 // Used by PeerTube when the user tries to authenticate
246 login: ({ id, password }) => {
247 if (id === 'user' && password === 'super password') {
248 return {
249 username: 'user'
250 email: 'user@example.com'
251 role: 2
252 displayName: 'User display name'
253 }
254 }
255
256 // Auth failed
257 return null
258 }
259 })
260
261 // Unregister this auth method
262 unregisterIdAndPassAuth('my-auth-method')
263 ```
264
265 You can also add an external auth method (like [OpenID](https://framagit.org/framasoft/peertube/official-plugins/-/tree/master/peertube-plugin-auth-openid-connect), [SAML2](https://framagit.org/framasoft/peertube/official-plugins/-/tree/master/peertube-plugin-auth-saml2) etc):
266
267 ```js
268 // result contains the userAuthenticated auth method you can call to authenticate a user
269 const result = registerExternalAuth({
270 authName: 'my-auth-method',
271
272 // Will be displayed in a button next to the login form
273 authDisplayName: () => 'Auth method'
274
275 // If the user click on the auth button, PeerTube will forward the request in this function
276 onAuthRequest: (req, res) => {
277 res.redirect('https://external-auth.example.com/auth')
278 },
279
280 // Same than registerIdAndPassAuth option
281 // onLogout: ...
282
283 // Same than registerIdAndPassAuth option
284 // hookTokenValidity: ...
285 })
286
287 router.use('/external-auth-callback', (req, res) => {
288 // Forward the request to PeerTube
289 result.userAuthenticated({
290 req,
291 res,
292 username: 'user'
293 email: 'user@example.com'
294 role: 2
295 displayName: 'User display name'
296 })
297 })
298
299 // Unregister this external auth method
300 unregisterExternalAuth('my-auth-method)
301 ```
302
303 ### Client helpers (themes & plugins)
304
305 #### Plugin static route
306
307 To get your plugin static route:
308
309 ```js
310 const baseStaticUrl = peertubeHelpers.getBaseStaticRoute()
311 const imageUrl = baseStaticUrl + '/images/chocobo.png'
312 ```
313
314 #### Notifier
315
316 To notify the user with the PeerTube ToastModule:
317
318 ```js
319 const { notifier } = peertubeHelpers
320 notifier.success('Success message content.')
321 notifier.error('Error message content.')
322 ```
323
324 #### Markdown Renderer
325
326 To render a formatted markdown text to HTML:
327
328 ```js
329 const { markdownRenderer } = peertubeHelpers
330
331 await markdownRenderer.textMarkdownToHTML('**My Bold Text**')
332 // return <strong>My Bold Text</strong>
333
334 await markdownRenderer.enhancedMarkdownToHTML('![alt-img](http://.../my-image.jpg)')
335 // return <img alt=alt-img src=http://.../my-image.jpg />
336 ```
337
338 #### Custom Modal
339
340 To show a custom modal:
341
342 ```js
343 peertubeHelpers.showModal({
344 title: 'My custom modal title',
345 content: '<p>My custom modal content</p>',
346 // Optionals parameters :
347 // show close icon
348 close: true,
349 // show cancel button and call action() after hiding modal
350 cancel: { value: 'cancel', action: () => {} },
351 // show confirm button and call action() after hiding modal
352 confirm: { value: 'confirm', action: () => {} },
353 })
354 ```
355
356 #### Translate
357
358 You can translate some strings of your plugin (PeerTube will use your `translations` object of your `package.json` file):
359
360 ```js
361 peertubeHelpers.translate('User name')
362 .then(translation => console.log('Translated User name by ' + translation))
363 ```
364
365 #### Get public settings
366
367 To get your public plugin settings:
368
369 ```js
370 peertubeHelpers.getSettings()
371 .then(s => {
372 if (!s || !s['site-id'] || !s['url']) {
373 console.error('Matomo settings are not set.')
374 return
375 }
376
377 // ...
378 })
379 ```
380
381
382 ### Publishing
383
384 PeerTube plugins and themes should be published on [NPM](https://www.npmjs.com/) so that PeerTube indexes
385 take into account your plugin (after ~ 1 day). An official PeerTube index is available on https://packages.joinpeertube.org/ (it's just a REST API, so don't expect a beautiful website).
386
387 ## Write a plugin/theme
388
389 Steps:
390 * Find a name for your plugin or your theme (must not have spaces, it can only contain lowercase letters and `-`)
391 * Add the appropriate prefix:
392 * If you develop a plugin, add `peertube-plugin-` prefix to your plugin name (for example: `peertube-plugin-mysupername`)
393 * If you develop a theme, add `peertube-theme-` prefix to your theme name (for example: `peertube-theme-mysupertheme`)
394 * Clone the quickstart repository
395 * Configure your repository
396 * Update `README.md`
397 * Update `package.json`
398 * Register hooks, add CSS and static files
399 * Test your plugin/theme with a local PeerTube installation
400 * Publish your plugin/theme on NPM
401
402 ### Clone the quickstart repository
403
404 If you develop a plugin, clone the `peertube-plugin-quickstart` repository:
405
406 ```
407 $ git clone https://framagit.org/framasoft/peertube/peertube-plugin-quickstart.git peertube-plugin-mysupername
408 ```
409
410 If you develop a theme, clone the `peertube-theme-quickstart` repository:
411
412 ```
413 $ git clone https://framagit.org/framasoft/peertube/peertube-theme-quickstart.git peertube-theme-mysupername
414 ```
415
416 ### Configure your repository
417
418 Set your repository URL:
419
420 ```
421 $ cd peertube-plugin-mysupername # or cd peertube-theme-mysupername
422 $ git remote set-url origin https://your-git-repo
423 ```
424
425 ### Update README
426
427 Update `README.md` file:
428
429 ```
430 $ $EDITOR README.md
431 ```
432
433 ### Update package.json
434
435 Update the `package.json` fields:
436 * `name` (should start with `peertube-plugin-` or `peertube-theme-`)
437 * `description`
438 * `homepage`
439 * `author`
440 * `bugs`
441 * `engine.peertube` (the PeerTube version compatibility, must be `>=x.y.z` and nothing else)
442
443 **Caution:** Don't update or remove other keys, or PeerTube will not be able to index/install your plugin.
444 If you don't need static directories, use an empty `object`:
445
446 ```json
447 {
448 ...,
449 "staticDirs": {},
450 ...
451 }
452 ```
453
454 And if you don't need CSS or client script files, use an empty `array`:
455
456 ```json
457 {
458 ...,
459 "css": [],
460 "clientScripts": [],
461 ...
462 }
463 ```
464
465 ### Write code
466
467 Now you can register hooks or settings, write CSS and add static directories to your plugin or your theme :)
468
469 **Caution:** It's up to you to check the code you write will be compatible with the PeerTube NodeJS version,
470 and will be supported by web browsers.
471 If you want to write modern JavaScript, please use a transpiler like [Babel](https://babeljs.io/).
472
473 ### Add translations
474
475 If you want to translate strings of your plugin (like labels of your registered settings), create a file and add it to `package.json`:
476
477 ```json
478 {
479 ...,
480 "translations": {
481 "fr-FR": "./languages/fr.json",
482 "pt-BR": "./languages/pt-BR.json"
483 },
484 ...
485 }
486 ```
487
488 The key should be one of the locales defined in [i18n.ts](https://github.com/Chocobozzz/PeerTube/blob/develop/shared/models/i18n/i18n.ts).
489 You **must** use the complete locales (`fr-FR` instead of `fr`).
490
491 Translation files are just objects, with the english sentence as the key and the translation as the value.
492 `fr.json` could contain for example:
493
494 ```json
495 {
496 "Hello world": "Hello le monde"
497 }
498 ```
499
500 ### Test your plugin/theme
501
502 You'll need to have a local PeerTube instance:
503 * Follow the [dev prerequisites](https://github.com/Chocobozzz/PeerTube/blob/develop/.github/CONTRIBUTING.md#prerequisites)
504 (to clone the repository, install dependencies and prepare the database)
505 * Build PeerTube (`--light` to only build the english language):
506
507 ```
508 $ npm run build -- --light
509 ```
510
511 * Build the CLI:
512
513 ```
514 $ npm run setup:cli
515 ```
516
517 * Run PeerTube (you can access to your instance on http://localhost:9000):
518
519 ```
520 $ NODE_ENV=test npm start
521 ```
522
523 * Register the instance via the CLI:
524
525 ```
526 $ node ./dist/server/tools/peertube.js auth add -u 'http://localhost:9000' -U 'root' --password 'test'
527 ```
528
529 Then, you can install or reinstall your local plugin/theme by running:
530
531 ```
532 $ node ./dist/server/tools/peertube.js plugins install --path /your/absolute/plugin-or-theme/path
533 ```
534
535 ### Publish
536
537 Go in your plugin/theme directory, and run:
538
539 ```
540 $ npm publish
541 ```
542
543 Every time you want to publish another version of your plugin/theme, just update the `version` key from the `package.json`
544 and republish it on NPM. Remember that the PeerTube index will take into account your new plugin/theme version after ~24 hours.
545
546
547 ## Plugin & Theme hooks/helpers API
548
549 See the dedicated documentation: https://docs.joinpeertube.org/#/api-plugins
550
551
552 ## Tips
553
554 ### Compatibility with PeerTube
555
556 Unfortunately, we don't have enough resources to provide hook compatibility between minor releases of PeerTube (for example between `1.2.x` and `1.3.x`).
557 So please:
558 * Don't make assumptions and check every parameter you want to use. For example:
559
560 ```js
561 registerHook({
562 target: 'filter:api.video.get.result',
563 handler: video => {
564 // We check the parameter exists and the name field exists too, to avoid exceptions
565 if (video && video.name) video.name += ' <3'
566
567 return video
568 }
569 })
570 ```
571 * Don't try to require parent PeerTube modules, only use `peertubeHelpers`. If you need another helper or a specific hook, please [create an issue](https://github.com/Chocobozzz/PeerTube/issues/new)
572 * Don't use PeerTube dependencies. Use your own :)
573
574 If your plugin is broken with a new PeerTube release, update your code and the `peertubeEngine` field of your `package.json` field.
575 This way, older PeerTube versions will still use your old plugin, and new PeerTube versions will use your updated plugin.
576
577 ### Spam/moderation plugin
578
579 If you want to create an antispam/moderation plugin, you could use the following hooks:
580 * `filter:api.video.upload.accept.result`: to accept or not local uploads
581 * `filter:api.video-thread.create.accept.result`: to accept or not local thread
582 * `filter:api.video-comment-reply.create.accept.result`: to accept or not local replies
583 * `filter:api.video-threads.list.result`: to change/hide the text of threads
584 * `filter:api.video-thread-comments.list.result`: to change/hide the text of replies
585 * `filter:video.auto-blacklist.result`: to automatically blacklist local or remote videos
586
587 ### Other plugin examples
588
589 You can take a look to "official" PeerTube plugins if you want to take inspiration from them: https://framagit.org/framasoft/peertube/official-plugins