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