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