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