]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - support/doc/plugins/guide.md
Add data directory for plugins and some helpers
[github/Chocobozzz/PeerTube.git] / support / doc / plugins / guide.md
CommitLineData
662e5d4f
C
1# Plugins & Themes
2
d8e9a42c
C
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)
d2466f0a 11 - [Server API (only for plugins)](#server-api-only-for-plugins)
d8e9a42c
C
12 - [Settings](#settings)
13 - [Storage](#storage)
7545a094 14 - [Update video constants](#update-video-constants)
5e2b2e27 15 - [Add custom routes](#add-custom-routes)
1a2820e6 16 - [Add external auth methods](#add-external-auth-methods)
7aca6b24 17 - [Add new transcoding profiles](#add-new-transcoding-profiles)
62bc0352 18 - [Server helpers](#server-helpers)
d2466f0a 19 - [Client API (themes & plugins)](#client-api-themes--plugins)
b3af2601
C
20 - [Plugin static route](#plugin-static-route)
21 - [Notifier](#notifier)
22 - [Markdown Renderer](#markdown-renderer)
23 - [Custom Modal](#custom-modal)
7545a094
C
24 - [Translate](#translate)
25 - [Get public settings](#get-public-settings)
ec99e848 26 - [Get server config](#get-server-config)
8546fe87 27 - [Add custom fields to video form](#add-custom-fields-to-video-form)
d2466f0a 28 - [Register settings script](#register-settings-script)
62bc0352 29 - [HTML placeholder elements](#html-placeholder-elements)
d8e9a42c
C
30 - [Publishing](#publishing)
31- [Write a plugin/theme](#write-a-plugintheme)
32 - [Clone the quickstart repository](#clone-the-quickstart-repository)
33 - [Configure your repository](#configure-your-repository)
34 - [Update README](#update-readme)
35 - [Update package.json](#update-packagejson)
36 - [Write code](#write-code)
7545a094 37 - [Add translations](#add-translations)
8546fe87 38 - [Build your plugin](#build-your-plugin)
d8e9a42c
C
39 - [Test your plugin/theme](#test-your-plugintheme)
40 - [Publish](#publish)
7545a094 41- [Plugin & Theme hooks/helpers API](#plugin--theme-hookshelpers-api)
d8e9a42c
C
42- [Tips](#tips)
43 - [Compatibility with PeerTube](#compatibility-with-peertube)
44 - [Spam/moderation plugin](#spammoderation-plugin)
112be80e 45 - [Other plugin examples](#other-plugin-examples)
d8e9a42c
C
46
47<!-- END doctoc generated TOC please keep comment here to allow auto update -->
48
662e5d4f
C
49## Concepts
50
32d7f2b7 51Themes are exactly the same as plugins, except that:
662e5d4f
C
52 * Their name starts with `peertube-theme-` instead of `peertube-plugin-`
53 * They cannot declare server code (so they cannot register server hooks or settings)
54 * CSS files are loaded by client only if the theme is chosen by the administrator or the user
55
56### Hooks
57
58A plugin registers functions in JavaScript to execute when PeerTube (server and client) fires events. There are 3 types of hooks:
5831dbcb 59 * `filter`: used to filter functions parameters or return values.
662e5d4f
C
60 For example to replace words in video comments, or change the videos list behaviour
61 * `action`: used to do something after a certain trigger. For example to send a hook every time a video is published
62 * `static`: same than `action` but PeerTube waits their execution
662e5d4f
C
63
64On server side, these hooks are registered by the `library` file defined in `package.json`.
65
66```json
67{
68 ...,
69 "library": "./main.js",
70 ...,
71}
72```
73
7545a094
C
74And `main.js` defines a `register` function:
75
76Example:
77
78```js
79async function register ({
80 registerHook,
5831dbcb 81
7545a094
C
82 registerSetting,
83 settingsManager,
5831dbcb 84
7545a094 85 storageManager,
5831dbcb 86
7545a094
C
87 videoCategoryManager,
88 videoLicenceManager,
5e2b2e27 89 videoLanguageManager,
5831dbcb 90
5e2b2e27 91 peertubeHelpers,
5831dbcb
C
92
93 getRouter,
94
95 registerExternalAuth,
96 unregisterExternalAuth,
97 registerIdAndPassAuth,
98 unregisterIdAndPassAuth
7545a094
C
99}) {
100 registerHook({
101 target: 'action:application.listening',
102 handler: () => displayHelloWorld()
103 })
104}
105```
106
662e5d4f
C
107
108On client side, these hooks are registered by the `clientScripts` files defined in `package.json`.
109All client scripts have scopes so PeerTube client only loads scripts it needs:
110
111```json
112{
113 ...,
114 "clientScripts": [
115 {
116 "script": "client/common-client-plugin.js",
117 "scopes": [ "common" ]
118 },
119 {
120 "script": "client/video-watch-client-plugin.js",
121 "scopes": [ "video-watch" ]
122 }
123 ],
124 ...
125}
126```
127
7545a094
C
128And these scripts also define a `register` function:
129
130```js
131function register ({ registerHook, peertubeHelpers }) {
132 registerHook({
133 target: 'action:application.init',
134 handler: () => onApplicationInit(peertubeHelpers)
135 })
136}
137```
138
662e5d4f
C
139### Static files
140
5831dbcb
C
141Plugins can declare static directories that PeerTube will serve (images for example)
142from `/plugins/{plugin-name}/{plugin-version}/static/`
662e5d4f
C
143or `/themes/{theme-name}/{theme-version}/static/` routes.
144
145### CSS
146
147Plugins can declare CSS files that PeerTube will automatically inject in the client.
7545a094
C
148If you need to override existing style, you can use the `#custom-css` selector:
149
150```
151body#custom-css {
152 color: red;
153}
154
155#custom-css .header {
156 background-color: red;
157}
158```
662e5d4f 159
d2466f0a 160### Server API (only for plugins)
662e5d4f
C
161
162#### Settings
163
164Plugins can register settings, that PeerTube will inject in the administration interface.
d2466f0a
C
165The following fields will be automatically translated using the plugin translation files: `label`, `html`, `descriptionHTML`, `options.label`.
166**These fields are injected in the plugin settings page as HTML, so pay attention to your translation files.**
662e5d4f
C
167
168Example:
169
170```js
d2466f0a
C
171function register (...) {
172 registerSetting({
173 name: 'admin-name',
174 label: 'Admin name',
175 type: 'input',
176 // type: input | input-checkbox | input-password | input-textarea | markdown-text | markdown-enhanced | 'select' | 'html'
177 default: 'my super name'
178 })
179
180 const adminName = await settingsManager.getSetting('admin-name')
181
182 const result = await settingsManager.getSettings([ 'admin-name', 'admin-password' ])
183 result['admin-name]
184
185 settingsManager.onSettingsChange(settings => {
186 settings['admin-name])
187 })
188}
662e5d4f
C
189```
190
d8e9a42c 191#### Storage
662e5d4f
C
192
193Plugins can store/load JSON data, that PeerTube will store in its database (so don't put files in there).
194
195Example:
196
197```js
302eba0d
C
198function register ({
199 storageManager
200}) {
d2466f0a
C
201 const value = await storageManager.getData('mykey')
202 await storageManager.storeData('mykey', { subkey: 'value' })
203}
662e5d4f
C
204```
205
302eba0d
C
206You can also store files in the plugin data directory (`/{plugins-directory}/data/{npm-plugin-name}`).
207This directory and its content won't be deleted when your plugin is uninstalled/upgraded.
208
209```js
210function register ({
211 storageManager,
212 peertubeHelpers
213}) {
214 const basePath = peertubeHelpers.plugin.getDataDirectoryPath()
215
216 fs.writeFile(path.join(basePath, 'filename.txt'), 'content of my file', function (err) {
217 ...
218 })
219}
220```
221
7545a094
C
222#### Update video constants
223
224You can add/delete video categories, licences or languages using the appropriate managers:
225
226```js
d2466f0a
C
227function register (...) {
228 videoLanguageManager.addLanguage('al_bhed', 'Al Bhed')
229 videoLanguageManager.deleteLanguage('fr')
7545a094 230
d2466f0a
C
231 videoCategoryManager.addCategory(42, 'Best category')
232 videoCategoryManager.deleteCategory(1) // Music
7545a094 233
d2466f0a
C
234 videoLicenceManager.addLicence(42, 'Best licence')
235 videoLicenceManager.deleteLicence(7) // Public domain
b3af2601 236
d2466f0a
C
237 videoPrivacyManager.deletePrivacy(2) // Remove Unlisted video privacy
238 playlistPrivacyManager.deletePlaylistPrivacy(3) // Remove Private video playlist privacy
239}
7545a094
C
240```
241
5e2b2e27
C
242#### Add custom routes
243
244You can create custom routes using an [express Router](https://expressjs.com/en/4x/api.html#router) for your plugin:
245
246```js
302eba0d
C
247function register ({
248 router
249}) {
d2466f0a
C
250 const router = getRouter()
251 router.get('/ping', (req, res) => res.json({ message: 'pong' }))
302eba0d
C
252
253 // Users are automatically authenticated
254 router.get('/auth', (res, res) => {
255 const user = peertubeHelpers.user.getAuthUser(res)
256
257 const isAdmin = user.role === 0
258 const isModerator = user.role === 1
259 const isUser = user.role === 2
260
261 res.json({
262 username: user.username,
263 isAdmin,
264 isModerator,
265 isUser
266 })
267 })
d2466f0a 268}
5e2b2e27
C
269```
270
271The `ping` route can be accessed using:
272 * `/plugins/:pluginName/:pluginVersion/router/ping`
273 * Or `/plugins/:pluginName/router/ping`
274
275
5831dbcb
C
276#### Add external auth methods
277
278If 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):
279
280```js
d2466f0a
C
281function register (...) {
282
283 registerIdAndPassAuth({
284 authName: 'my-auth-method',
285
286 // PeerTube will try all id and pass plugins in the weight DESC order
287 // Exposing this value in the plugin settings could be interesting
288 getWeight: () => 60,
289
290 // Optional function called by PeerTube when the user clicked on the logout button
291 onLogout: user => {
292 console.log('User %s logged out.', user.username')
293 },
294
295 // Optional function called by PeerTube when the access token or refresh token are generated/refreshed
296 hookTokenValidity: ({ token, type }) => {
297 if (type === 'access') return { valid: true }
298 if (type === 'refresh') return { valid: false }
299 },
300
301 // Used by PeerTube when the user tries to authenticate
302 login: ({ id, password }) => {
303 if (id === 'user' && password === 'super password') {
304 return {
305 username: 'user'
306 email: 'user@example.com'
307 role: 2
308 displayName: 'User display name'
309 }
5831dbcb 310 }
5831dbcb 311
d2466f0a
C
312 // Auth failed
313 return null
314 }
315 })
5831dbcb 316
d2466f0a
C
317 // Unregister this auth method
318 unregisterIdAndPassAuth('my-auth-method')
319}
5831dbcb
C
320```
321
322You 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):
323
324```js
d2466f0a
C
325function register (...) {
326
327 // result contains the userAuthenticated auth method you can call to authenticate a user
328 const result = registerExternalAuth({
329 authName: 'my-auth-method',
330
331 // Will be displayed in a button next to the login form
332 authDisplayName: () => 'Auth method'
333
334 // If the user click on the auth button, PeerTube will forward the request in this function
335 onAuthRequest: (req, res) => {
336 res.redirect('https://external-auth.example.com/auth')
337 },
338
339 // Same than registerIdAndPassAuth option
340 // onLogout: ...
341
342 // Same than registerIdAndPassAuth option
343 // hookTokenValidity: ...
344 })
345
346 router.use('/external-auth-callback', (req, res) => {
347 // Forward the request to PeerTube
348 result.userAuthenticated({
349 req,
350 res,
351 username: 'user'
352 email: 'user@example.com'
353 role: 2
354 displayName: 'User display name'
355 })
5831dbcb 356 })
5831dbcb 357
d2466f0a
C
358 // Unregister this external auth method
359 unregisterExternalAuth('my-auth-method)
360}
5831dbcb
C
361```
362
7aca6b24
C
363#### Add new transcoding profiles
364
365Adding transcoding profiles allow admins to change ffmpeg encoding parameters and/or encoders.
366A transcoding profile has to be chosen by the admin of the instance using the admin configuration.
367
368```js
369async function register ({
370 transcodingManager
371}) {
372
373 // Adapt bitrate when using libx264 encoder
374 {
375 const builder = (options) => {
376 const { input, resolution, fps, streamNum } = options
377
378 const streamString = streamNum ? ':' + streamNum : ''
379
380 // You can also return a promise
a60696ab 381 // All these options are optional
7aca6b24 382 return {
a60696ab
C
383 scaleFilter: {
384 // Used to define an alternative scale filter, needed by some encoders
385 // Default to 'scale'
386 name: 'scale_vaapi'
387 },
388 // Default to []
5fb7cfba 389 inputOptions: [],
a60696ab 390 // Default to []
7aca6b24
C
391 outputOptions: [
392 // Use a custom bitrate
393 '-b' + streamString + ' 10K'
394 ]
395 }
396 }
397
398 const encoder = 'libx264'
399 const profileName = 'low-quality'
400
401 // Support this profile for VOD transcoding
402 transcodingManager.addVODProfile(encoder, profileName, builder)
403
404 // And/Or support this profile for live transcoding
405 transcodingManager.addLiveProfile(encoder, profileName, builder)
406 }
407
408 {
409 const builder = (options) => {
410 const { streamNum } = options
411
412 const streamString = streamNum ? ':' + streamNum : ''
413
414 // Always copy stream when PeerTube use libfdk_aac or aac encoders
415 return {
416 copy: true
417 }
418 }
419
420 const profileName = 'copy-audio'
421
422 for (const encoder of [ 'libfdk_aac', 'aac' ]) {
423 transcodingManager.addVODProfile(encoder, profileName, builder)
424 }
425 }
426```
427
428PeerTube will try different encoders depending on their priority.
429If the encoder is not available in the current transcoding profile or in ffmpeg, it tries the next one.
430Plugins can change the order of these encoders and add their custom encoders:
431
432```js
433async function register ({
434 transcodingManager
435}) {
436
437 // Adapt bitrate when using libx264 encoder
438 {
439 const builder = () => {
440 return {
5fb7cfba 441 inputOptions: [],
7aca6b24
C
442 outputOptions: []
443 }
444 }
445
446 // Support libopus and libvpx-vp9 encoders (these codecs could be incompatible with the player)
447 transcodingManager.addVODProfile('libopus', 'test-vod-profile', builder)
448
449 // Default priorities are ~100
450 // Lowest priority = 1
451 transcodingManager.addVODEncoderPriority('audio', 'libopus', 1000)
452
453 transcodingManager.addVODProfile('libvpx-vp9', 'test-vod-profile', builder)
454 transcodingManager.addVODEncoderPriority('video', 'libvpx-vp9', 1000)
455
456 transcodingManager.addLiveProfile('libopus', 'test-live-profile', builder)
457 transcodingManager.addLiveEncoderPriority('audio', 'libopus', 1000)
458 }
459```
460
d5fc35c2
TLC
461During live transcode input options are applied once for each target resolution.
462Plugins are responsible for detecting such situation and applying input options only once if necessary.
463
62bc0352 464#### Server helpers
d2466f0a
C
465
466PeerTube provides your plugin some helpers. For example:
467
468```js
469async function register ({
470 peertubeHelpers
471}) {
472 // Block a server
473 {
474 const serverActor = await peertubeHelpers.server.getServerActor()
475
476 await peertubeHelpers.moderation.blockServer({ byAccountId: serverActor.Account.id, hostToBlock: '...' })
477 }
478
479 // Load a video
480 {
481 const video = await peertubeHelpers.videos.loadByUrl('...')
482 }
483}
484```
485
486See the [plugin API reference](https://docs.joinpeertube.org/api-plugins) to see the complete helpers list.
487
488### Client API (themes & plugins)
7545a094 489
74c2dece 490#### Plugin static route
7545a094
C
491
492To get your plugin static route:
493
494```js
d2466f0a
C
495function register (...) {
496 const baseStaticUrl = peertubeHelpers.getBaseStaticRoute()
497 const imageUrl = baseStaticUrl + '/images/chocobo.png'
498}
7545a094
C
499```
500
74c2dece
K
501#### Notifier
502
503To notify the user with the PeerTube ToastModule:
504
505```js
d2466f0a
C
506function register (...) {
507 const { notifier } = peertubeHelpers
508 notifier.success('Success message content.')
509 notifier.error('Error message content.')
510}
74c2dece
K
511```
512
8c7725dc
K
513#### Markdown Renderer
514
515To render a formatted markdown text to HTML:
516
517```js
d2466f0a
C
518function register (...) {
519 const { markdownRenderer } = peertubeHelpers
8c7725dc 520
d2466f0a
C
521 await markdownRenderer.textMarkdownToHTML('**My Bold Text**')
522 // return <strong>My Bold Text</strong>
8c7725dc 523
d2466f0a
C
524 await markdownRenderer.enhancedMarkdownToHTML('![alt-img](http://.../my-image.jpg)')
525 // return <img alt=alt-img src=http://.../my-image.jpg />
526}
8c7725dc
K
527```
528
437e8e06
K
529#### Custom Modal
530
531To show a custom modal:
532
533```js
d2466f0a
C
534function register (...) {
535 peertubeHelpers.showModal({
536 title: 'My custom modal title',
537 content: '<p>My custom modal content</p>',
538 // Optionals parameters :
539 // show close icon
540 close: true,
541 // show cancel button and call action() after hiding modal
542 cancel: { value: 'cancel', action: () => {} },
543 // show confirm button and call action() after hiding modal
544 confirm: { value: 'confirm', action: () => {} },
545 })
546}
437e8e06
K
547```
548
7545a094
C
549#### Translate
550
551You can translate some strings of your plugin (PeerTube will use your `translations` object of your `package.json` file):
552
553```js
d2466f0a
C
554function register (...) {
555 peertubeHelpers.translate('User name')
556 .then(translation => console.log('Translated User name by ' + translation))
557}
7545a094
C
558```
559
560#### Get public settings
561
562To get your public plugin settings:
563
564```js
d2466f0a
C
565function register (...) {
566 peertubeHelpers.getSettings()
567 .then(s => {
568 if (!s || !s['site-id'] || !s['url']) {
569 console.error('Matomo settings are not set.')
570 return
571 }
5831dbcb 572
d2466f0a
C
573 // ...
574 })
575}
5831dbcb 576```
7545a094 577
ec99e848
C
578#### Get server config
579
580```js
d2466f0a
C
581function register (...) {
582 peertubeHelpers.getServerConfig()
583 .then(config => {
584 console.log('Fetched server config.', config)
585 })
586}
ec99e848
C
587```
588
8546fe87 589#### Add custom fields to video form
e08a26e2
C
590
591To add custom fields in the video form (in *Plugin settings* tab):
592
8546fe87 593```js
e08a26e2
C
594async function register ({ registerVideoField, peertubeHelpers }) {
595 const descriptionHTML = await peertubeHelpers.translate(descriptionSource)
596 const commonOptions = {
597 name: 'my-field-name,
598 label: 'My added field',
599 descriptionHTML: 'Optional description',
600 type: 'input-textarea',
601 default: ''
602 }
603
604 for (const type of [ 'upload', 'import-url', 'import-torrent', 'update' ]) {
605 registerVideoField(commonOptions, { type })
606 }
607}
608```
609
610PeerTube will send this field value in `body.pluginData['my-field-name']` and fetch it from `video.pluginData['my-field-name']`.
611
612So for example, if you want to store an additional metadata for videos, register the following hooks in **server**:
613
8546fe87 614```js
e08a26e2
C
615async function register ({
616 registerHook,
617 storageManager
618}) {
619 const fieldName = 'my-field-name'
620
621 // Store data associated to this video
622 registerHook({
623 target: 'action:api.video.updated',
624 handler: ({ video, body }) => {
625 if (!body.pluginData) return
626
627 const value = body.pluginData[fieldName]
628 if (!value) return
629
630 storageManager.storeData(fieldName + '-' + video.id, value)
631 }
632 })
633
634 // Add your custom value to the video, so the client autofill your field using the previously stored value
635 registerHook({
636 target: 'filter:api.video.get.result',
637 handler: async (video) => {
638 if (!video) return video
639 if (!video.pluginData) video.pluginData = {}
640
641 const result = await storageManager.getData(fieldName + '-' + video.id)
642 video.pluginData[fieldName] = result
643
644 return video
645 }
646 })
647}
2498aaea 648```
d2466f0a
C
649
650#### Register settings script
651
652To hide some fields in your settings plugin page depending on the form state:
653
654```js
655async function register ({ registerSettingsScript }) {
656 registerSettingsScript({
657 isSettingHidden: options => {
658 if (options.setting.name === 'my-setting' && options.formValues['field45'] === '2') {
659 return true
660 }
661
662 return false
663 }
664 })
665}
666```
667
62bc0352
C
668#### HTML placeholder elements
669
670PeerTube provides some HTML id so plugins can easily insert their own element:
671
b044cb18 672```js
62bc0352
C
673async function register (...) {
674 const elem = document.createElement('div')
675 elem.className = 'hello-world-h4'
676 elem.innerHTML = '<h4>Hello everybody! This is an element next to the player</h4>'
677
678 document.getElementById('plugin-placeholder-player-next').appendChild(elem)
679}
680```
681
682See the complete list on https://docs.joinpeertube.org/api-plugins
d2466f0a 683
662e5d4f
C
684### Publishing
685
686PeerTube plugins and themes should be published on [NPM](https://www.npmjs.com/) so that PeerTube indexes
687take 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).
688
689## Write a plugin/theme
690
691Steps:
692 * Find a name for your plugin or your theme (must not have spaces, it can only contain lowercase letters and `-`)
693 * Add the appropriate prefix:
694 * If you develop a plugin, add `peertube-plugin-` prefix to your plugin name (for example: `peertube-plugin-mysupername`)
695 * If you develop a theme, add `peertube-theme-` prefix to your theme name (for example: `peertube-theme-mysupertheme`)
696 * Clone the quickstart repository
697 * Configure your repository
698 * Update `README.md`
699 * Update `package.json`
700 * Register hooks, add CSS and static files
701 * Test your plugin/theme with a local PeerTube installation
702 * Publish your plugin/theme on NPM
703
704### Clone the quickstart repository
705
706If you develop a plugin, clone the `peertube-plugin-quickstart` repository:
707
708```
709$ git clone https://framagit.org/framasoft/peertube/peertube-plugin-quickstart.git peertube-plugin-mysupername
710```
711
712If you develop a theme, clone the `peertube-theme-quickstart` repository:
713
714```
715$ git clone https://framagit.org/framasoft/peertube/peertube-theme-quickstart.git peertube-theme-mysupername
716```
717
718### Configure your repository
719
720Set your repository URL:
721
722```
723$ cd peertube-plugin-mysupername # or cd peertube-theme-mysupername
724$ git remote set-url origin https://your-git-repo
725```
726
727### Update README
728
729Update `README.md` file:
730
731```
732$ $EDITOR README.md
733```
734
735### Update package.json
736
737Update the `package.json` fields:
738 * `name` (should start with `peertube-plugin-` or `peertube-theme-`)
739 * `description`
740 * `homepage`
741 * `author`
742 * `bugs`
743 * `engine.peertube` (the PeerTube version compatibility, must be `>=x.y.z` and nothing else)
5831dbcb 744
662e5d4f 745**Caution:** Don't update or remove other keys, or PeerTube will not be able to index/install your plugin.
5831dbcb 746If you don't need static directories, use an empty `object`:
662e5d4f
C
747
748```json
749{
750 ...,
751 "staticDirs": {},
752 ...
753}
754```
755
9fa6ca16 756And if you don't need CSS or client script files, use an empty `array`:
662e5d4f
C
757
758```json
759{
760 ...,
761 "css": [],
9fa6ca16 762 "clientScripts": [],
662e5d4f
C
763 ...
764}
765```
766
767### Write code
768
769Now you can register hooks or settings, write CSS and add static directories to your plugin or your theme :)
770
5831dbcb 771**Caution:** It's up to you to check the code you write will be compatible with the PeerTube NodeJS version,
662e5d4f
C
772and will be supported by web browsers.
773If you want to write modern JavaScript, please use a transpiler like [Babel](https://babeljs.io/).
774
7545a094
C
775### Add translations
776
777If you want to translate strings of your plugin (like labels of your registered settings), create a file and add it to `package.json`:
778
779```json
780{
781 ...,
782 "translations": {
67baf647 783 "fr": "./languages/fr.json",
7545a094
C
784 "pt-BR": "./languages/pt-BR.json"
785 },
786 ...
787}
788```
789
790The key should be one of the locales defined in [i18n.ts](https://github.com/Chocobozzz/PeerTube/blob/develop/shared/models/i18n/i18n.ts).
7545a094 791
112be80e
C
792Translation files are just objects, with the english sentence as the key and the translation as the value.
793`fr.json` could contain for example:
794
795```json
796{
797 "Hello world": "Hello le monde"
798}
799```
800
36578353
C
801### Build your plugin
802
803If you added client scripts, you'll need to build them using webpack.
804
805Install webpack:
806
807```
808$ npm install
809```
810
811Add/update your files in the `clientFiles` array of `webpack.config.js`:
812
813```
814$ $EDITOR ./webpack.config.js
815```
816
817Build your client files:
818
819```
820$ npm run build
821```
822
823You built files are in the `dist/` directory. Check `package.json` to correctly point to them.
824
825
662e5d4f
C
826### Test your plugin/theme
827
828You'll need to have a local PeerTube instance:
5831dbcb 829 * Follow the [dev prerequisites](https://github.com/Chocobozzz/PeerTube/blob/develop/.github/CONTRIBUTING.md#prerequisites)
662e5d4f 830 (to clone the repository, install dependencies and prepare the database)
5831dbcb 831 * Build PeerTube (`--light` to only build the english language):
662e5d4f
C
832
833```
834$ npm run build -- --light
9fa6ca16
C
835```
836
837 * Build the CLI:
5831dbcb 838
9fa6ca16
C
839```
840$ npm run setup:cli
662e5d4f 841```
5831dbcb
C
842
843 * Run PeerTube (you can access to your instance on http://localhost:9000):
662e5d4f
C
844
845```
846$ NODE_ENV=test npm start
847```
848
5831dbcb 849 * Register the instance via the CLI:
662e5d4f
C
850
851```
852$ node ./dist/server/tools/peertube.js auth add -u 'http://localhost:9000' -U 'root' --password 'test'
853```
854
855Then, you can install or reinstall your local plugin/theme by running:
856
857```
858$ node ./dist/server/tools/peertube.js plugins install --path /your/absolute/plugin-or-theme/path
859```
860
861### Publish
862
863Go in your plugin/theme directory, and run:
864
865```
866$ npm publish
867```
868
869Every time you want to publish another version of your plugin/theme, just update the `version` key from the `package.json`
870and republish it on NPM. Remember that the PeerTube index will take into account your new plugin/theme version after ~24 hours.
871
d8e9a42c 872
bfa1a32b
C
873## Plugin & Theme hooks/helpers API
874
7cf88d09 875See the dedicated documentation: https://docs.joinpeertube.org/api-plugins
bfa1a32b
C
876
877
d8e9a42c
C
878## Tips
879
880### Compatibility with PeerTube
881
882Unfortunately, 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`).
883So please:
884 * Don't make assumptions and check every parameter you want to use. For example:
885
886```js
887registerHook({
888 target: 'filter:api.video.get.result',
889 handler: video => {
890 // We check the parameter exists and the name field exists too, to avoid exceptions
891 if (video && video.name) video.name += ' <3'
892
893 return video
894 }
895})
896```
a4879b53 897 * 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/choose)
5831dbcb 898 * Don't use PeerTube dependencies. Use your own :)
d8e9a42c 899
51326912 900If your plugin is broken with a new PeerTube release, update your code and the `peertubeEngine` field of your `package.json` field.
5831dbcb 901This way, older PeerTube versions will still use your old plugin, and new PeerTube versions will use your updated plugin.
d8e9a42c
C
902
903### Spam/moderation plugin
904
905If you want to create an antispam/moderation plugin, you could use the following hooks:
906 * `filter:api.video.upload.accept.result`: to accept or not local uploads
907 * `filter:api.video-thread.create.accept.result`: to accept or not local thread
908 * `filter:api.video-comment-reply.create.accept.result`: to accept or not local replies
909 * `filter:api.video-threads.list.result`: to change/hide the text of threads
910 * `filter:api.video-thread-comments.list.result`: to change/hide the text of replies
911 * `filter:video.auto-blacklist.result`: to automatically blacklist local or remote videos
5831dbcb 912
112be80e
C
913### Other plugin examples
914
915You can take a look to "official" PeerTube plugins if you want to take inspiration from them: https://framagit.org/framasoft/peertube/official-plugins