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