aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.editorconfig11
-rw-r--r--client/src/app/shared/shared-share-modal/video-share.component.html29
-rw-r--r--client/src/app/shared/shared-share-modal/video-share.component.ts20
-rw-r--r--client/src/assets/player/shared/hotkeys/peertube-hotkeys-plugin.ts27
-rw-r--r--client/src/root-helpers/video.ts21
-rw-r--r--server/controllers/api/metrics.ts5
-rw-r--r--server/controllers/api/server/contact.ts7
-rw-r--r--server/tests/api/check-params/videos-common-filters.ts3
-rw-r--r--server/tests/api/users/user-subscriptions.ts37
-rw-r--r--server/tests/api/videos/videos-common-filters.ts63
-rw-r--r--server/tests/feeds/feeds.ts8
-rw-r--r--server/tests/fixtures/peertube-plugin-test/main.js2
-rw-r--r--server/tests/plugins/filter-hooks.ts8
-rw-r--r--shared/server-commands/users/subscriptions-command.ts18
-rw-r--r--shared/server-commands/videos/videos-command.ts14
15 files changed, 184 insertions, 89 deletions
diff --git a/.editorconfig b/.editorconfig
index 843d5d926..211eb8550 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -6,16 +6,7 @@ root = true
6[*] 6[*]
7end_of_line = lf 7end_of_line = lf
8charset = utf-8 8charset = utf-8
9
10[*.{yml,html}]
11indent_style = space
12indent_size = 2
13
14[{client,server,shared,scripts}/**.{ts,json,js}]
15trim_trailing_whitespace = true 9trim_trailing_whitespace = true
16insert_final_newline = true 10insert_final_newline = true
17indent_style = space
18indent_size = 2 11indent_size = 2
19 12indent_style = space
20[*.md]
21trim_trailing_whitespace = false
diff --git a/client/src/app/shared/shared-share-modal/video-share.component.html b/client/src/app/shared/shared-share-modal/video-share.component.html
index f4d249b41..01d351783 100644
--- a/client/src/app/shared/shared-share-modal/video-share.component.html
+++ b/client/src/app/shared/shared-share-modal/video-share.component.html
@@ -72,13 +72,21 @@
72 ></my-peertube-checkbox> 72 ></my-peertube-checkbox>
73 </div> 73 </div>
74 74
75 <div class="form-group"> 75 <ng-container *ngIf="isInPlaylistEmbedTab()">
76 <my-peertube-checkbox 76 <div class="form-group">
77 *ngIf="isInPlaylistEmbedTab()" 77 <my-peertube-checkbox
78 inputName="onlyEmbedUrl" [(ngModel)]="customizations.onlyEmbedUrl" 78 inputName="onlyEmbedUrl" [(ngModel)]="customizations.onlyEmbedUrl"
79 i18n-labelText labelText="Only display embed URL" 79 i18n-labelText labelText="Only display embed URL"
80 ></my-peertube-checkbox> 80 ></my-peertube-checkbox>
81 </div> 81 </div>
82
83 <div class="form-group">
84 <my-peertube-checkbox
85 inputName="responsive" [(ngModel)]="customizations.responsive"
86 i18n-labelText labelText="Responsive embed"
87 ></my-peertube-checkbox>
88 </div>
89 </ng-container>
82 90
83 <my-plugin-placeholder pluginId="share-modal-playlist-settings"></my-plugin-placeholder> 91 <my-plugin-placeholder pluginId="share-modal-playlist-settings"></my-plugin-placeholder>
84 </div> 92 </div>
@@ -230,6 +238,13 @@
230 <ng-container *ngIf="isInVideoEmbedTab()"> 238 <ng-container *ngIf="isInVideoEmbedTab()">
231 <div class="form-group"> 239 <div class="form-group">
232 <my-peertube-checkbox 240 <my-peertube-checkbox
241 inputName="responsive" [(ngModel)]="customizations.responsive"
242 i18n-labelText labelText="Responsive embed"
243 ></my-peertube-checkbox>
244 </div>
245
246 <div class="form-group">
247 <my-peertube-checkbox
233 inputName="title" [(ngModel)]="customizations.title" 248 inputName="title" [(ngModel)]="customizations.title"
234 i18n-labelText labelText="Display video title" 249 i18n-labelText labelText="Display video title"
235 ></my-peertube-checkbox> 250 ></my-peertube-checkbox>
diff --git a/client/src/app/shared/shared-share-modal/video-share.component.ts b/client/src/app/shared/shared-share-modal/video-share.component.ts
index e1db4a3b8..43229c330 100644
--- a/client/src/app/shared/shared-share-modal/video-share.component.ts
+++ b/client/src/app/shared/shared-share-modal/video-share.component.ts
@@ -29,6 +29,7 @@ type Customizations = {
29 warningTitle: boolean 29 warningTitle: boolean
30 controlBar: boolean 30 controlBar: boolean
31 peertubeLink: boolean 31 peertubeLink: boolean
32 responsive: boolean
32 33
33 includeVideoInPlaylist: boolean 34 includeVideoInPlaylist: boolean
34} 35}
@@ -100,6 +101,7 @@ export class VideoShareComponent {
100 warningTitle: true, 101 warningTitle: true,
101 controlBar: true, 102 controlBar: true,
102 peertubeLink: true, 103 peertubeLink: true,
104 responsive: false,
103 105
104 includeVideoInPlaylist: false 106 includeVideoInPlaylist: false
105 }, { 107 }, {
@@ -152,10 +154,11 @@ export class VideoShareComponent {
152 ) 154 )
153 } 155 }
154 156
155 async getVideoIframeCode () { 157 async getVideoEmbedCode (options: { responsive: boolean }) {
158 const { responsive } = options
156 return this.hooks.wrapFun( 159 return this.hooks.wrapFun(
157 buildVideoOrPlaylistEmbed, 160 buildVideoOrPlaylistEmbed,
158 { embedUrl: await this.getVideoEmbedUrl(), embedTitle: this.video.name }, 161 { embedUrl: await this.getVideoEmbedUrl(), embedTitle: this.video.name, responsive },
159 'video-watch', 162 'video-watch',
160 'filter:share.video-embed-code.build.params', 163 'filter:share.video-embed-code.build.params',
161 'filter:share.video-embed-code.build.result' 164 'filter:share.video-embed-code.build.result'
@@ -186,10 +189,11 @@ export class VideoShareComponent {
186 ) 189 )
187 } 190 }
188 191
189 async getPlaylistEmbedCode () { 192 async getPlaylistEmbedCode (options: { responsive: boolean }) {
193 const { responsive } = options
190 return this.hooks.wrapFun( 194 return this.hooks.wrapFun(
191 buildVideoOrPlaylistEmbed, 195 buildVideoOrPlaylistEmbed,
192 { embedUrl: await this.getPlaylistEmbedUrl(), embedTitle: this.playlist.displayName }, 196 { embedUrl: await this.getPlaylistEmbedUrl(), embedTitle: this.playlist.displayName, responsive },
193 'video-watch', 197 'video-watch',
194 'filter:share.video-playlist-embed-code.build.params', 198 'filter:share.video-playlist-embed-code.build.params',
195 'filter:share.video-playlist-embed-code.build.result' 199 'filter:share.video-playlist-embed-code.build.result'
@@ -204,15 +208,15 @@ export class VideoShareComponent {
204 if (this.playlist) { 208 if (this.playlist) {
205 this.playlistUrl = await this.getPlaylistUrl() 209 this.playlistUrl = await this.getPlaylistUrl()
206 this.playlistEmbedUrl = await this.getPlaylistEmbedUrl() 210 this.playlistEmbedUrl = await this.getPlaylistEmbedUrl()
207 this.playlistEmbedHTML = await this.getPlaylistEmbedCode() 211 this.playlistEmbedHTML = await this.getPlaylistEmbedCode({ responsive: this.customizations.responsive })
208 this.playlistEmbedSafeHTML = this.sanitizer.bypassSecurityTrustHtml(this.playlistEmbedHTML) 212 this.playlistEmbedSafeHTML = this.sanitizer.bypassSecurityTrustHtml(await this.getPlaylistEmbedCode({ responsive: false }))
209 } 213 }
210 214
211 if (this.video) { 215 if (this.video) {
212 this.videoUrl = await this.getVideoUrl() 216 this.videoUrl = await this.getVideoUrl()
213 this.videoEmbedUrl = await this.getVideoEmbedUrl() 217 this.videoEmbedUrl = await this.getVideoEmbedUrl()
214 this.videoEmbedHTML = await this.getVideoIframeCode() 218 this.videoEmbedHTML = await this.getVideoEmbedCode({ responsive: this.customizations.responsive })
215 this.videoEmbedSafeHTML = this.sanitizer.bypassSecurityTrustHtml(this.videoEmbedHTML) 219 this.videoEmbedSafeHTML = this.sanitizer.bypassSecurityTrustHtml(await this.getVideoEmbedCode({ responsive: false }))
216 } 220 }
217 } 221 }
218 222
diff --git a/client/src/assets/player/shared/hotkeys/peertube-hotkeys-plugin.ts b/client/src/assets/player/shared/hotkeys/peertube-hotkeys-plugin.ts
index f5b4b3919..2742b21a1 100644
--- a/client/src/assets/player/shared/hotkeys/peertube-hotkeys-plugin.ts
+++ b/client/src/assets/player/shared/hotkeys/peertube-hotkeys-plugin.ts
@@ -218,12 +218,37 @@ class PeerTubeHotkeysPlugin extends Plugin {
218 } 218 }
219 219
220 private isNaked (event: KeyboardEvent, key: string) { 220 private isNaked (event: KeyboardEvent, key: string) {
221 return (!event.ctrlKey && !event.altKey && !event.metaKey && !event.shiftKey && event.key === key) 221 if (key.length === 1) key = key.toUpperCase()
222
223 return (!event.ctrlKey && !event.altKey && !event.metaKey && !event.shiftKey && this.getLatinKey(event.key, event.code) === key)
222 } 224 }
223 225
224 private isNakedOrShift (event: KeyboardEvent, key: string) { 226 private isNakedOrShift (event: KeyboardEvent, key: string) {
225 return (!event.ctrlKey && !event.altKey && !event.metaKey && event.key === key) 227 return (!event.ctrlKey && !event.altKey && !event.metaKey && event.key === key)
226 } 228 }
229
230 // Thanks Maciej Krawczyk
231 // https://stackoverflow.com/questions/70211837/keyboard-shortcuts-commands-on-non-latin-alphabet-keyboards-javascript?rq=1
232 private getLatinKey (key: string, code: string) {
233 if (key.length !== 1) {
234 return key
235 }
236
237 const capitalHetaCode = 880
238 const isNonLatin = key.charCodeAt(0) >= capitalHetaCode
239
240 if (isNonLatin) {
241 if (code.indexOf('Key') === 0 && code.length === 4) { // i.e. 'KeyW'
242 return code.charAt(3)
243 }
244
245 if (code.indexOf('Digit') === 0 && code.length === 6) { // i.e. 'Digit7'
246 return code.charAt(5)
247 }
248 }
249
250 return key.toUpperCase()
251 }
227} 252}
228 253
229videojs.registerPlugin('peerTubeHotkeysPlugin', PeerTubeHotkeysPlugin) 254videojs.registerPlugin('peerTubeHotkeysPlugin', PeerTubeHotkeysPlugin)
diff --git a/client/src/root-helpers/video.ts b/client/src/root-helpers/video.ts
index 107ba1eba..01feddbdc 100644
--- a/client/src/root-helpers/video.ts
+++ b/client/src/root-helpers/video.ts
@@ -3,19 +3,34 @@ import { HTMLServerConfig, Video, VideoPrivacy } from '@shared/models'
3function buildVideoOrPlaylistEmbed (options: { 3function buildVideoOrPlaylistEmbed (options: {
4 embedUrl: string 4 embedUrl: string
5 embedTitle: string 5 embedTitle: string
6 responsive?: boolean
6}) { 7}) {
7 const { embedUrl, embedTitle } = options 8 const { embedUrl, embedTitle, responsive = false } = options
8 9
9 const iframe = document.createElement('iframe') 10 const iframe = document.createElement('iframe')
10 11
11 iframe.title = embedTitle 12 iframe.title = embedTitle
12 iframe.width = '560' 13 iframe.width = responsive ? '100%' : '560'
13 iframe.height = '315' 14 iframe.height = responsive ? '100%' : '315'
14 iframe.src = embedUrl 15 iframe.src = embedUrl
15 iframe.frameBorder = '0' 16 iframe.frameBorder = '0'
16 iframe.allowFullscreen = true 17 iframe.allowFullscreen = true
17 iframe.sandbox.add('allow-same-origin', 'allow-scripts', 'allow-popups') 18 iframe.sandbox.add('allow-same-origin', 'allow-scripts', 'allow-popups')
18 19
20 if (responsive) {
21 const wrapper = document.createElement('div')
22
23 wrapper.style.position = 'relative'
24 wrapper.style['padding-top'] = '56.25%'
25
26 iframe.style.position = 'absolute'
27 iframe.style.inset = '0'
28
29 wrapper.appendChild(iframe)
30
31 return wrapper.outerHTML
32 }
33
19 return iframe.outerHTML 34 return iframe.outerHTML
20} 35}
21 36
diff --git a/server/controllers/api/metrics.ts b/server/controllers/api/metrics.ts
index 578b023a1..f66173875 100644
--- a/server/controllers/api/metrics.ts
+++ b/server/controllers/api/metrics.ts
@@ -2,6 +2,7 @@ import express from 'express'
2import { OpenTelemetryMetrics } from '@server/lib/opentelemetry/metrics' 2import { OpenTelemetryMetrics } from '@server/lib/opentelemetry/metrics'
3import { HttpStatusCode, PlaybackMetricCreate } from '@shared/models' 3import { HttpStatusCode, PlaybackMetricCreate } from '@shared/models'
4import { addPlaybackMetricValidator, asyncMiddleware } from '../../middlewares' 4import { addPlaybackMetricValidator, asyncMiddleware } from '../../middlewares'
5import { CONFIG } from '@server/initializers/config'
5 6
6const metricsRouter = express.Router() 7const metricsRouter = express.Router()
7 8
@@ -19,6 +20,10 @@ export {
19// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
20 21
21function addPlaybackMetric (req: express.Request, res: express.Response) { 22function addPlaybackMetric (req: express.Request, res: express.Response) {
23 if (!CONFIG.OPEN_TELEMETRY.METRICS.ENABLED) {
24 return res.sendStatus(HttpStatusCode.FORBIDDEN_403)
25 }
26
22 const body: PlaybackMetricCreate = req.body 27 const body: PlaybackMetricCreate = req.body
23 28
24 OpenTelemetryMetrics.Instance.observePlaybackMetric(res.locals.onlyImmutableVideo, body) 29 OpenTelemetryMetrics.Instance.observePlaybackMetric(res.locals.onlyImmutableVideo, body)
diff --git a/server/controllers/api/server/contact.ts b/server/controllers/api/server/contact.ts
index 09ff50f69..56596bea5 100644
--- a/server/controllers/api/server/contact.ts
+++ b/server/controllers/api/server/contact.ts
@@ -1,3 +1,4 @@
1import { logger } from '@server/helpers/logger'
1import express from 'express' 2import express from 'express'
2import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' 3import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
3import { ContactForm } from '../../../../shared/models/server' 4import { ContactForm } from '../../../../shared/models/server'
@@ -17,7 +18,11 @@ async function contactAdministrator (req: express.Request, res: express.Response
17 18
18 Emailer.Instance.addContactFormJob(data.fromEmail, data.fromName, data.subject, data.body) 19 Emailer.Instance.addContactFormJob(data.fromEmail, data.fromName, data.subject, data.body)
19 20
20 await Redis.Instance.setContactFormIp(req.ip) 21 try {
22 await Redis.Instance.setContactFormIp(req.ip)
23 } catch (err) {
24 logger.error(err)
25 }
21 26
22 return res.status(HttpStatusCode.NO_CONTENT_204).end() 27 return res.status(HttpStatusCode.NO_CONTENT_204).end()
23} 28}
diff --git a/server/tests/api/check-params/videos-common-filters.ts b/server/tests/api/check-params/videos-common-filters.ts
index 95523ce3d..11d9fd95b 100644
--- a/server/tests/api/check-params/videos-common-filters.ts
+++ b/server/tests/api/check-params/videos-common-filters.ts
@@ -42,7 +42,8 @@ describe('Test video filters validators', function () {
42 '/api/v1/video-channels/root_channel/videos', 42 '/api/v1/video-channels/root_channel/videos',
43 '/api/v1/accounts/root/videos', 43 '/api/v1/accounts/root/videos',
44 '/api/v1/videos', 44 '/api/v1/videos',
45 '/api/v1/search/videos' 45 '/api/v1/search/videos',
46 '/api/v1/users/me/subscriptions/videos'
46 ] 47 ]
47 48
48 for (const path of paths) { 49 for (const path of paths) {
diff --git a/server/tests/api/users/user-subscriptions.ts b/server/tests/api/users/user-subscriptions.ts
index b45cfe67e..ad2b82a4a 100644
--- a/server/tests/api/users/user-subscriptions.ts
+++ b/server/tests/api/users/user-subscriptions.ts
@@ -167,14 +167,14 @@ describe('Test users subscriptions', function () {
167 167
168 it('Should list subscription videos', async function () { 168 it('Should list subscription videos', async function () {
169 { 169 {
170 const body = await command.listVideos() 170 const body = await servers[0].videos.listMySubscriptionVideos()
171 expect(body.total).to.equal(0) 171 expect(body.total).to.equal(0)
172 expect(body.data).to.be.an('array') 172 expect(body.data).to.be.an('array')
173 expect(body.data).to.have.lengthOf(0) 173 expect(body.data).to.have.lengthOf(0)
174 } 174 }
175 175
176 { 176 {
177 const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) 177 const body = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken, sort: 'createdAt' })
178 expect(body.total).to.equal(3) 178 expect(body.total).to.equal(3)
179 179
180 const videos = body.data 180 const videos = body.data
@@ -185,6 +185,17 @@ describe('Test users subscriptions', function () {
185 expect(videos[1].name).to.equal('video 2-3') 185 expect(videos[1].name).to.equal('video 2-3')
186 expect(videos[2].name).to.equal('video server 3 added after follow') 186 expect(videos[2].name).to.equal('video server 3 added after follow')
187 } 187 }
188
189 {
190 const body = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken, count: 1, start: 1 })
191 expect(body.total).to.equal(3)
192
193 const videos = body.data
194 expect(videos).to.be.an('array')
195 expect(videos).to.have.lengthOf(1)
196
197 expect(videos[0].name).to.equal('video 2-3')
198 }
188 }) 199 })
189 200
190 it('Should upload a video by root on server 1 and see it in the subscription videos', async function () { 201 it('Should upload a video by root on server 1 and see it in the subscription videos', async function () {
@@ -196,14 +207,14 @@ describe('Test users subscriptions', function () {
196 await waitJobs(servers) 207 await waitJobs(servers)
197 208
198 { 209 {
199 const body = await command.listVideos() 210 const body = await servers[0].videos.listMySubscriptionVideos()
200 expect(body.total).to.equal(0) 211 expect(body.total).to.equal(0)
201 expect(body.data).to.be.an('array') 212 expect(body.data).to.be.an('array')
202 expect(body.data).to.have.lengthOf(0) 213 expect(body.data).to.have.lengthOf(0)
203 } 214 }
204 215
205 { 216 {
206 const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) 217 const body = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken, sort: 'createdAt' })
207 expect(body.total).to.equal(4) 218 expect(body.total).to.equal(4)
208 219
209 const videos = body.data 220 const videos = body.data
@@ -264,14 +275,14 @@ describe('Test users subscriptions', function () {
264 275
265 it('Should still list subscription videos', async function () { 276 it('Should still list subscription videos', async function () {
266 { 277 {
267 const body = await command.listVideos() 278 const body = await servers[0].videos.listMySubscriptionVideos()
268 expect(body.total).to.equal(0) 279 expect(body.total).to.equal(0)
269 expect(body.data).to.be.an('array') 280 expect(body.data).to.be.an('array')
270 expect(body.data).to.have.lengthOf(0) 281 expect(body.data).to.have.lengthOf(0)
271 } 282 }
272 283
273 { 284 {
274 const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) 285 const body = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken, sort: 'createdAt' })
275 expect(body.total).to.equal(4) 286 expect(body.total).to.equal(4)
276 287
277 const videos = body.data 288 const videos = body.data
@@ -295,7 +306,7 @@ describe('Test users subscriptions', function () {
295 306
296 await waitJobs(servers) 307 await waitJobs(servers)
297 308
298 const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) 309 const body = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken, sort: 'createdAt' })
299 expect(body.data[2].name).to.equal('video server 3 added after follow updated') 310 expect(body.data[2].name).to.equal('video server 3 added after follow updated')
300 }) 311 })
301 }) 312 })
@@ -311,7 +322,7 @@ describe('Test users subscriptions', function () {
311 }) 322 })
312 323
313 it('Should not display its videos anymore', async function () { 324 it('Should not display its videos anymore', async function () {
314 const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) 325 const body = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken, sort: 'createdAt' })
315 expect(body.total).to.equal(1) 326 expect(body.total).to.equal(1)
316 327
317 const videos = body.data 328 const videos = body.data
@@ -360,7 +371,7 @@ describe('Test users subscriptions', function () {
360 await waitJobs(servers) 371 await waitJobs(servers)
361 372
362 { 373 {
363 const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) 374 const body = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken, sort: 'createdAt' })
364 expect(body.total).to.equal(3) 375 expect(body.total).to.equal(3)
365 376
366 const videos = body.data 377 const videos = body.data
@@ -569,13 +580,13 @@ describe('Test users subscriptions', function () {
569 await waitJobs(servers) 580 await waitJobs(servers)
570 581
571 { 582 {
572 const { data } = await command.listVideos({ token: users[0].accessToken }) 583 const { data } = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken })
573 expect(data.find(v => v.name === 'internal')).to.not.exist 584 expect(data.find(v => v.name === 'internal')).to.not.exist
574 } 585 }
575 }) 586 })
576 587
577 it('Should see internal from local user', async function () { 588 it('Should see internal from local user', async function () {
578 const { data } = await servers[2].subscriptions.listVideos({ token: servers[2].accessToken }) 589 const { data } = await servers[2].videos.listMySubscriptionVideos({ token: servers[2].accessToken })
579 expect(data.find(v => v.name === 'internal')).to.exist 590 expect(data.find(v => v.name === 'internal')).to.exist
580 }) 591 })
581 592
@@ -586,12 +597,12 @@ describe('Test users subscriptions', function () {
586 await waitJobs(servers) 597 await waitJobs(servers)
587 598
588 { 599 {
589 const { data } = await command.listVideos({ token: users[0].accessToken }) 600 const { data } = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken })
590 expect(data.find(v => v.name === 'private')).to.not.exist 601 expect(data.find(v => v.name === 'private')).to.not.exist
591 } 602 }
592 603
593 { 604 {
594 const { data } = await servers[2].subscriptions.listVideos({ token: servers[2].accessToken }) 605 const { data } = await servers[2].videos.listMySubscriptionVideos({ token: servers[2].accessToken })
595 expect(data.find(v => v.name === 'private')).to.not.exist 606 expect(data.find(v => v.name === 'private')).to.not.exist
596 } 607 }
597 }) 608 })
diff --git a/server/tests/api/videos/videos-common-filters.ts b/server/tests/api/videos/videos-common-filters.ts
index 160091861..1ab78ac49 100644
--- a/server/tests/api/videos/videos-common-filters.ts
+++ b/server/tests/api/videos/videos-common-filters.ts
@@ -20,6 +20,8 @@ describe('Test videos filter', function () {
20 let paths: string[] 20 let paths: string[]
21 let remotePaths: string[] 21 let remotePaths: string[]
22 22
23 const subscriptionVideosPath = '/api/v1/users/me/subscriptions/videos'
24
23 // --------------------------------------------------------------- 25 // ---------------------------------------------------------------
24 26
25 before(async function () { 27 before(async function () {
@@ -49,6 +51,9 @@ describe('Test videos filter', function () {
49 const attributes = { name: 'private ' + server.serverNumber, privacy: VideoPrivacy.PRIVATE } 51 const attributes = { name: 'private ' + server.serverNumber, privacy: VideoPrivacy.PRIVATE }
50 await server.videos.upload({ attributes }) 52 await server.videos.upload({ attributes })
51 } 53 }
54
55 // Subscribing to itself
56 await server.subscriptions.add({ targetUri: 'root_channel@' + server.host })
52 } 57 }
53 58
54 await doubleFollow(servers[0], servers[1]) 59 await doubleFollow(servers[0], servers[1])
@@ -57,7 +62,8 @@ describe('Test videos filter', function () {
57 `/api/v1/video-channels/root_channel/videos`, 62 `/api/v1/video-channels/root_channel/videos`,
58 `/api/v1/accounts/root/videos`, 63 `/api/v1/accounts/root/videos`,
59 '/api/v1/videos', 64 '/api/v1/videos',
60 '/api/v1/search/videos' 65 '/api/v1/search/videos',
66 subscriptionVideosPath
61 ] 67 ]
62 68
63 remotePaths = [ 69 remotePaths = [
@@ -70,10 +76,20 @@ describe('Test videos filter', function () {
70 76
71 describe('Check deprecated videos filter', function () { 77 describe('Check deprecated videos filter', function () {
72 78
73 async function getVideosNames (server: PeerTubeServer, token: string, filter: string, expectedStatus = HttpStatusCode.OK_200) { 79 async function getVideosNames (options: {
80 server: PeerTubeServer
81 token: string
82 filter: string
83 skipSubscription?: boolean
84 expectedStatus?: HttpStatusCode
85 }) {
86 const { server, token, filter, skipSubscription = false, expectedStatus = HttpStatusCode.OK_200 } = options
87
74 const videosResults: Video[][] = [] 88 const videosResults: Video[][] = []
75 89
76 for (const path of paths) { 90 for (const path of paths) {
91 if (skipSubscription && path === subscriptionVideosPath) continue
92
77 const res = await makeGetRequest({ 93 const res = await makeGetRequest({
78 url: server.url, 94 url: server.url,
79 path, 95 path,
@@ -93,7 +109,7 @@ describe('Test videos filter', function () {
93 109
94 it('Should display local videos', async function () { 110 it('Should display local videos', async function () {
95 for (const server of servers) { 111 for (const server of servers) {
96 const namesResults = await getVideosNames(server, server.accessToken, 'local') 112 const namesResults = await getVideosNames({ server, token: server.accessToken, filter: 'local' })
97 for (const names of namesResults) { 113 for (const names of namesResults) {
98 expect(names).to.have.lengthOf(1) 114 expect(names).to.have.lengthOf(1)
99 expect(names[0]).to.equal('public ' + server.serverNumber) 115 expect(names[0]).to.equal('public ' + server.serverNumber)
@@ -105,7 +121,7 @@ describe('Test videos filter', function () {
105 for (const server of servers) { 121 for (const server of servers) {
106 for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) { 122 for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
107 123
108 const namesResults = await getVideosNames(server, token, 'all-local') 124 const namesResults = await getVideosNames({ server, token, filter: 'all-local', skipSubscription: true })
109 for (const names of namesResults) { 125 for (const names of namesResults) {
110 expect(names).to.have.lengthOf(3) 126 expect(names).to.have.lengthOf(3)
111 127
@@ -121,7 +137,7 @@ describe('Test videos filter', function () {
121 for (const server of servers) { 137 for (const server of servers) {
122 for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) { 138 for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
123 139
124 const [ channelVideos, accountVideos, videos, searchVideos ] = await getVideosNames(server, token, 'all') 140 const [ channelVideos, accountVideos, videos, searchVideos ] = await getVideosNames({ server, token, filter: 'all' })
125 expect(channelVideos).to.have.lengthOf(3) 141 expect(channelVideos).to.have.lengthOf(3)
126 expect(accountVideos).to.have.lengthOf(3) 142 expect(accountVideos).to.have.lengthOf(3)
127 143
@@ -162,17 +178,23 @@ describe('Test videos filter', function () {
162 return res.body.data as Video[] 178 return res.body.data as Video[]
163 } 179 }
164 180
165 async function getVideosNames (options: { 181 async function getVideosNames (
166 server: PeerTubeServer 182 options: {
167 isLocal?: boolean 183 server: PeerTubeServer
168 include?: VideoInclude 184 isLocal?: boolean
169 privacyOneOf?: VideoPrivacy[] 185 include?: VideoInclude
170 token?: string 186 privacyOneOf?: VideoPrivacy[]
171 expectedStatus?: HttpStatusCode 187 token?: string
172 }) { 188 expectedStatus?: HttpStatusCode
189 skipSubscription?: boolean
190 }
191 ) {
192 const { skipSubscription = false } = options
173 const videosResults: string[][] = [] 193 const videosResults: string[][] = []
174 194
175 for (const path of paths) { 195 for (const path of paths) {
196 if (skipSubscription && path === subscriptionVideosPath) continue
197
176 const videos = await listVideos({ ...options, path }) 198 const videos = await listVideos({ ...options, path })
177 199
178 videosResults.push(videos.map(v => v.name)) 200 videosResults.push(videos.map(v => v.name))
@@ -196,12 +218,15 @@ describe('Test videos filter', function () {
196 for (const server of servers) { 218 for (const server of servers) {
197 for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) { 219 for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
198 220
199 const namesResults = await getVideosNames({ 221 const namesResults = await getVideosNames(
200 server, 222 {
201 token, 223 server,
202 isLocal: true, 224 token,
203 privacyOneOf: [ VideoPrivacy.UNLISTED, VideoPrivacy.PUBLIC, VideoPrivacy.PRIVATE ] 225 isLocal: true,
204 }) 226 privacyOneOf: [ VideoPrivacy.UNLISTED, VideoPrivacy.PUBLIC, VideoPrivacy.PRIVATE ],
227 skipSubscription: true
228 }
229 )
205 230
206 for (const names of namesResults) { 231 for (const names of namesResults) {
207 expect(names).to.have.lengthOf(3) 232 expect(names).to.have.lengthOf(3)
diff --git a/server/tests/feeds/feeds.ts b/server/tests/feeds/feeds.ts
index 7345f728a..ecd1badc1 100644
--- a/server/tests/feeds/feeds.ts
+++ b/server/tests/feeds/feeds.ts
@@ -387,7 +387,7 @@ describe('Test syndication feeds', () => {
387 } 387 }
388 388
389 { 389 {
390 const body = await servers[0].subscriptions.listVideos({ token: feeduserAccessToken }) 390 const body = await servers[0].videos.listMySubscriptionVideos({ token: feeduserAccessToken })
391 expect(body.total).to.equal(0) 391 expect(body.total).to.equal(0)
392 392
393 const query = { accountId: feeduserAccountId, token: feeduserFeedToken } 393 const query = { accountId: feeduserAccountId, token: feeduserFeedToken }
@@ -408,7 +408,7 @@ describe('Test syndication feeds', () => {
408 }) 408 })
409 409
410 it('Should list no videos for a user with videos but no subscriptions', async function () { 410 it('Should list no videos for a user with videos but no subscriptions', async function () {
411 const body = await servers[0].subscriptions.listVideos({ token: userAccessToken }) 411 const body = await servers[0].videos.listMySubscriptionVideos({ token: userAccessToken })
412 expect(body.total).to.equal(0) 412 expect(body.total).to.equal(0)
413 413
414 const query = { accountId: userAccountId, token: userFeedToken } 414 const query = { accountId: userAccountId, token: userFeedToken }
@@ -424,7 +424,7 @@ describe('Test syndication feeds', () => {
424 await waitJobs(servers) 424 await waitJobs(servers)
425 425
426 { 426 {
427 const body = await servers[0].subscriptions.listVideos({ token: userAccessToken }) 427 const body = await servers[0].videos.listMySubscriptionVideos({ token: userAccessToken })
428 expect(body.total).to.equal(1) 428 expect(body.total).to.equal(1)
429 expect(body.data[0].name).to.equal('user video') 429 expect(body.data[0].name).to.equal('user video')
430 430
@@ -442,7 +442,7 @@ describe('Test syndication feeds', () => {
442 await waitJobs(servers) 442 await waitJobs(servers)
443 443
444 { 444 {
445 const body = await servers[0].subscriptions.listVideos({ token: userAccessToken }) 445 const body = await servers[0].videos.listMySubscriptionVideos({ token: userAccessToken })
446 expect(body.total).to.equal(2, 'there should be 2 videos part of the subscription') 446 expect(body.total).to.equal(2, 'there should be 2 videos part of the subscription')
447 447
448 const query = { accountId: userAccountId, token: userFeedToken } 448 const query = { accountId: userAccountId, token: userFeedToken }
diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js
index 60b8b3ccd..5325e14c4 100644
--- a/server/tests/fixtures/peertube-plugin-test/main.js
+++ b/server/tests/fixtures/peertube-plugin-test/main.js
@@ -91,7 +91,7 @@ async function register ({ registerHook, registerSetting, settingsManager, stora
91 91
92 registerHook({ 92 registerHook({
93 target: 'filter:api.user.me.subscription-videos.list.params', 93 target: 'filter:api.user.me.subscription-videos.list.params',
94 handler: obj => Object.assign({}, obj, { count: 1 }) 94 handler: obj => addToCount(obj)
95 }) 95 })
96 96
97 registerHook({ 97 registerHook({
diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts
index 7e0be4d4e..4d26ff8b7 100644
--- a/server/tests/plugins/filter-hooks.ts
+++ b/server/tests/plugins/filter-hooks.ts
@@ -156,14 +156,14 @@ describe('Test plugin filter hooks', function () {
156 }) 156 })
157 157
158 it('Should run filter:api.user.me.subscription-videos.list.params', async function () { 158 it('Should run filter:api.user.me.subscription-videos.list.params', async function () {
159 const { data } = await servers[0].subscriptions.listVideos() 159 const { data } = await servers[0].videos.listMySubscriptionVideos({ start: 0, count: 2 })
160 160
161 // 1 plugin set the count parameter to 1 161 // 1 plugin do +1 to the count parameter
162 expect(data).to.have.lengthOf(1) 162 expect(data).to.have.lengthOf(3)
163 }) 163 })
164 164
165 it('Should run filter:api.user.me.subscription-videos.list.result', async function () { 165 it('Should run filter:api.user.me.subscription-videos.list.result', async function () {
166 const { total } = await servers[0].subscriptions.listVideos() 166 const { total } = await servers[0].videos.listMySubscriptionVideos({ start: 0, count: 2 })
167 167
168 // Plugin do +4 to the total result 168 // Plugin do +4 to the total result
169 expect(total).to.equal(14) 169 expect(total).to.equal(14)
diff --git a/shared/server-commands/users/subscriptions-command.ts b/shared/server-commands/users/subscriptions-command.ts
index edc60e612..b92f037f8 100644
--- a/shared/server-commands/users/subscriptions-command.ts
+++ b/shared/server-commands/users/subscriptions-command.ts
@@ -1,4 +1,4 @@
1import { HttpStatusCode, ResultList, Video, VideoChannel } from '@shared/models' 1import { HttpStatusCode, ResultList, VideoChannel } from '@shared/models'
2import { AbstractCommand, OverrideCommandOptions } from '../shared' 2import { AbstractCommand, OverrideCommandOptions } from '../shared'
3 3
4export class SubscriptionsCommand extends AbstractCommand { 4export class SubscriptionsCommand extends AbstractCommand {
@@ -38,22 +38,6 @@ export class SubscriptionsCommand extends AbstractCommand {
38 }) 38 })
39 } 39 }
40 40
41 listVideos (options: OverrideCommandOptions & {
42 sort?: string // default -createdAt
43 } = {}) {
44 const { sort = '-createdAt' } = options
45 const path = '/api/v1/users/me/subscriptions/videos'
46
47 return this.getRequestBody<ResultList<Video>>({
48 ...options,
49
50 path,
51 query: { sort },
52 implicitToken: true,
53 defaultExpectedStatus: HttpStatusCode.OK_200
54 })
55 }
56
57 get (options: OverrideCommandOptions & { 41 get (options: OverrideCommandOptions & {
58 uri: string 42 uri: string
59 }) { 43 }) {
diff --git a/shared/server-commands/videos/videos-command.ts b/shared/server-commands/videos/videos-command.ts
index 590244484..b5df9c325 100644
--- a/shared/server-commands/videos/videos-command.ts
+++ b/shared/server-commands/videos/videos-command.ts
@@ -210,6 +210,20 @@ export class VideosCommand extends AbstractCommand {
210 }) 210 })
211 } 211 }
212 212
213 listMySubscriptionVideos (options: OverrideCommandOptions & VideosCommonQuery = {}) {
214 const { sort = '-createdAt' } = options
215 const path = '/api/v1/users/me/subscriptions/videos'
216
217 return this.getRequestBody<ResultList<Video>>({
218 ...options,
219
220 path,
221 query: { sort, ...this.buildListQuery(options) },
222 implicitToken: true,
223 defaultExpectedStatus: HttpStatusCode.OK_200
224 })
225 }
226
213 // --------------------------------------------------------------------------- 227 // ---------------------------------------------------------------------------
214 228
215 list (options: OverrideCommandOptions & VideosCommonQuery = {}) { 229 list (options: OverrideCommandOptions & VideosCommonQuery = {}) {