diff options
42 files changed, 386 insertions, 154 deletions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 65e1acec6..678b0674b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml | |||
@@ -48,6 +48,7 @@ jobs: | |||
48 | ENABLE_OBJECT_STORAGE_TESTS: true | 48 | ENABLE_OBJECT_STORAGE_TESTS: true |
49 | OBJECT_STORAGE_SCALEWAY_KEY_ID: ${{ secrets.OBJECT_STORAGE_SCALEWAY_KEY_ID }} | 49 | OBJECT_STORAGE_SCALEWAY_KEY_ID: ${{ secrets.OBJECT_STORAGE_SCALEWAY_KEY_ID }} |
50 | OBJECT_STORAGE_SCALEWAY_ACCESS_KEY: ${{ secrets.OBJECT_STORAGE_SCALEWAY_ACCESS_KEY }} | 50 | OBJECT_STORAGE_SCALEWAY_ACCESS_KEY: ${{ secrets.OBJECT_STORAGE_SCALEWAY_ACCESS_KEY }} |
51 | YOUTUBE_DL_DOWNLOAD_BEARER_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
51 | 52 | ||
52 | steps: | 53 | steps: |
53 | - uses: actions/checkout@v3 | 54 | - uses: actions/checkout@v3 |
diff --git a/client/package.json b/client/package.json index a53f19e55..5e962550c 100644 --- a/client/package.json +++ b/client/package.json | |||
@@ -52,8 +52,8 @@ | |||
52 | "@ngx-loading-bar/core": "^6.0.0", | 52 | "@ngx-loading-bar/core": "^6.0.0", |
53 | "@ngx-loading-bar/http-client": "^6.0.0", | 53 | "@ngx-loading-bar/http-client": "^6.0.0", |
54 | "@ngx-loading-bar/router": "^6.0.0", | 54 | "@ngx-loading-bar/router": "^6.0.0", |
55 | "@peertube/p2p-media-loader-core": "^1.0.13", | 55 | "@peertube/p2p-media-loader-core": "^1.0.14", |
56 | "@peertube/p2p-media-loader-hlsjs": "^1.0.13", | 56 | "@peertube/p2p-media-loader-hlsjs": "^1.0.14", |
57 | "@peertube/videojs-contextmenu": "^5.5.0", | 57 | "@peertube/videojs-contextmenu": "^5.5.0", |
58 | "@peertube/xliffmerge": "^2.0.3", | 58 | "@peertube/xliffmerge": "^2.0.3", |
59 | "@popperjs/core": "^2.11.5", | 59 | "@popperjs/core": "^2.11.5", |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html index 43f1438e0..174f5d29c 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html | |||
@@ -44,9 +44,13 @@ | |||
44 | 44 | ||
45 | <div class="peertube-select-container"> | 45 | <div class="peertube-select-container"> |
46 | <select id="trendingVideosAlgorithmsDefault" formControlName="default" class="form-control"> | 46 | <select id="trendingVideosAlgorithmsDefault" formControlName="default" class="form-control"> |
47 | <option i18n value="publishedAt">Recently added videos</option> | ||
48 | <option i18n value="originallyPublishedAt">Original publication date</option> | ||
49 | <option i18n value="name">Name</option> | ||
47 | <option i18n value="hot">Hot videos</option> | 50 | <option i18n value="hot">Hot videos</option> |
48 | <option i18n value="most-viewed">Most viewed videos</option> | 51 | <option i18n value="most-viewed">Recent views</option> |
49 | <option i18n value="most-liked">Most liked videos</option> | 52 | <option i18n value="most-liked">Most liked videos</option> |
53 | <option i18n value="views">Global views</option> | ||
50 | </select> | 54 | </select> |
51 | </div> | 55 | </div> |
52 | 56 | ||
diff --git a/client/src/app/+videos/+video-watch/video-watch.component.ts b/client/src/app/+videos/+video-watch/video-watch.component.ts index 94853423b..fa0417832 100644 --- a/client/src/app/+videos/+video-watch/video-watch.component.ts +++ b/client/src/app/+videos/+video-watch/video-watch.component.ts | |||
@@ -295,6 +295,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
295 | subtitle: queryParams.subtitle, | 295 | subtitle: queryParams.subtitle, |
296 | 296 | ||
297 | playerMode: queryParams.mode, | 297 | playerMode: queryParams.mode, |
298 | playbackRate: queryParams.playbackRate, | ||
298 | peertubeLink: false | 299 | peertubeLink: false |
299 | } | 300 | } |
300 | 301 | ||
@@ -657,6 +658,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
657 | muted: urlOptions.muted, | 658 | muted: urlOptions.muted, |
658 | loop: urlOptions.loop, | 659 | loop: urlOptions.loop, |
659 | subtitle: urlOptions.subtitle, | 660 | subtitle: urlOptions.subtitle, |
661 | playbackRate: urlOptions.playbackRate, | ||
660 | 662 | ||
661 | peertubeLink: urlOptions.peertubeLink, | 663 | peertubeLink: urlOptions.peertubeLink, |
662 | 664 | ||
diff --git a/client/src/app/+videos/video-list/videos-list-common-page.component.ts b/client/src/app/+videos/video-list/videos-list-common-page.component.ts index c8fa8ef30..bafe30fd7 100644 --- a/client/src/app/+videos/video-list/videos-list-common-page.component.ts +++ b/client/src/app/+videos/video-list/videos-list-common-page.component.ts | |||
@@ -177,6 +177,9 @@ export class VideosListCommonPageComponent implements OnInit, OnDestroy, Disable | |||
177 | case 'best': | 177 | case 'best': |
178 | return '-hot' | 178 | return '-hot' |
179 | 179 | ||
180 | case 'name': | ||
181 | return 'name' | ||
182 | |||
180 | default: | 183 | default: |
181 | return '-' + algorithm as VideoSortField | 184 | return '-' + algorithm as VideoSortField |
182 | } | 185 | } |
diff --git a/client/src/app/core/rest/rest-extractor.service.ts b/client/src/app/core/rest/rest-extractor.service.ts index de3f2bfff..daed7f178 100644 --- a/client/src/app/core/rest/rest-extractor.service.ts +++ b/client/src/app/core/rest/rest-extractor.service.ts | |||
@@ -87,7 +87,11 @@ export class RestExtractor { | |||
87 | 87 | ||
88 | if (err.status !== undefined) { | 88 | if (err.status !== undefined) { |
89 | const errorMessage = this.buildServerErrorMessage(err) | 89 | const errorMessage = this.buildServerErrorMessage(err) |
90 | logger.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`) | 90 | |
91 | const message = `Backend returned code ${err.status}, errorMessage is: ${errorMessage}` | ||
92 | |||
93 | if (err.status === HttpStatusCode.NOT_FOUND_404) logger.clientError(message) | ||
94 | else logger.error(message) | ||
91 | 95 | ||
92 | return errorMessage | 96 | return errorMessage |
93 | } | 97 | } |
diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts index 85c63c173..706227e66 100644 --- a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts +++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts | |||
@@ -314,6 +314,6 @@ export class VideoMiniatureComponent implements OnInit { | |||
314 | this.cd.markForCheck() | 314 | this.cd.markForCheck() |
315 | }) | 315 | }) |
316 | 316 | ||
317 | this.videoPlaylistService.runPlaylistCheck(this.video.id) | 317 | this.videoPlaylistService.runVideoExistsInPlaylistCheck(this.video.id) |
318 | } | 318 | } |
319 | } | 319 | } |
diff --git a/client/src/app/shared/shared-video-miniature/videos-list.component.ts b/client/src/app/shared/shared-video-miniature/videos-list.component.ts index d5cdd958e..a423377de 100644 --- a/client/src/app/shared/shared-video-miniature/videos-list.component.ts +++ b/client/src/app/shared/shared-video-miniature/videos-list.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as debug from 'debug' | 1 | import * as debug from 'debug' |
2 | import { fromEvent, Observable, Subject, Subscription } from 'rxjs' | 2 | import { fromEvent, Observable, Subject, Subscription } from 'rxjs' |
3 | import { debounceTime, switchMap } from 'rxjs/operators' | 3 | import { concatMap, debounceTime, map, switchMap } from 'rxjs/operators' |
4 | import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core' | 4 | import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core' |
5 | import { ActivatedRoute } from '@angular/router' | 5 | import { ActivatedRoute } from '@angular/router' |
6 | import { | 6 | import { |
@@ -111,6 +111,8 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy { | |||
111 | 111 | ||
112 | private lastQueryLength: number | 112 | private lastQueryLength: number |
113 | 113 | ||
114 | private videoRequests = new Subject<{ reset: boolean, obs: Observable<ResultList<Video>> }>() | ||
115 | |||
114 | constructor ( | 116 | constructor ( |
115 | private notifier: Notifier, | 117 | private notifier: Notifier, |
116 | private authService: AuthService, | 118 | private authService: AuthService, |
@@ -124,6 +126,8 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy { | |||
124 | } | 126 | } |
125 | 127 | ||
126 | ngOnInit () { | 128 | ngOnInit () { |
129 | this.subscribeToVideoRequests() | ||
130 | |||
127 | const hiddenFilters = this.hideScopeFilter | 131 | const hiddenFilters = this.hideScopeFilter |
128 | ? [ 'scope' ] | 132 | ? [ 'scope' ] |
129 | : [] | 133 | : [] |
@@ -228,30 +232,12 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy { | |||
228 | } | 232 | } |
229 | 233 | ||
230 | loadMoreVideos (reset = false) { | 234 | loadMoreVideos (reset = false) { |
231 | if (reset) this.hasDoneFirstQuery = false | 235 | if (reset) { |
232 | 236 | this.hasDoneFirstQuery = false | |
233 | this.getVideosObservableFunction(this.pagination, this.filters) | 237 | this.videos = [] |
234 | .subscribe({ | 238 | } |
235 | next: ({ data }) => { | ||
236 | this.hasDoneFirstQuery = true | ||
237 | this.lastQueryLength = data.length | ||
238 | |||
239 | if (reset) this.videos = [] | ||
240 | this.videos = this.videos.concat(data) | ||
241 | |||
242 | if (this.groupByDate) this.buildGroupedDateLabels() | ||
243 | |||
244 | this.onDataSubject.next(data) | ||
245 | this.videosLoaded.emit(this.videos) | ||
246 | }, | ||
247 | |||
248 | error: err => { | ||
249 | const message = $localize`Cannot load more videos. Try again later.` | ||
250 | 239 | ||
251 | logger.error(message, err) | 240 | this.videoRequests.next({ reset, obs: this.getVideosObservableFunction(this.pagination, this.filters) }) |
252 | this.notifier.error(message) | ||
253 | } | ||
254 | }) | ||
255 | } | 241 | } |
256 | 242 | ||
257 | reloadVideos () { | 243 | reloadVideos () { |
@@ -423,4 +409,32 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy { | |||
423 | this.onFiltersChanged(true) | 409 | this.onFiltersChanged(true) |
424 | }) | 410 | }) |
425 | } | 411 | } |
412 | |||
413 | private subscribeToVideoRequests () { | ||
414 | this.videoRequests | ||
415 | .pipe(concatMap(({ reset, obs }) => obs.pipe(map(({ data }) => ({ data, reset }))))) | ||
416 | .subscribe({ | ||
417 | next: ({ data, reset }) => { | ||
418 | console.log(data[0].name) | ||
419 | |||
420 | this.hasDoneFirstQuery = true | ||
421 | this.lastQueryLength = data.length | ||
422 | |||
423 | if (reset) this.videos = [] | ||
424 | this.videos = this.videos.concat(data) | ||
425 | |||
426 | if (this.groupByDate) this.buildGroupedDateLabels() | ||
427 | |||
428 | this.onDataSubject.next(data) | ||
429 | this.videosLoaded.emit(this.videos) | ||
430 | }, | ||
431 | |||
432 | error: err => { | ||
433 | const message = $localize`Cannot load more videos. Try again later.` | ||
434 | |||
435 | logger.error(message, err) | ||
436 | this.notifier.error(message) | ||
437 | } | ||
438 | }) | ||
439 | } | ||
426 | } | 440 | } |
diff --git a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts index 2fc39fc75..f802416a4 100644 --- a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts +++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts | |||
@@ -81,7 +81,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, | |||
81 | .subscribe(result => { | 81 | .subscribe(result => { |
82 | this.playlistsData = result.data | 82 | this.playlistsData = result.data |
83 | 83 | ||
84 | this.videoPlaylistService.runPlaylistCheck(this.video.id) | 84 | this.videoPlaylistService.runVideoExistsInPlaylistCheck(this.video.id) |
85 | }) | 85 | }) |
86 | 86 | ||
87 | this.videoPlaylistSearchChanged | 87 | this.videoPlaylistSearchChanged |
@@ -129,7 +129,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, | |||
129 | .subscribe(playlistsResult => { | 129 | .subscribe(playlistsResult => { |
130 | this.playlistsData = playlistsResult.data | 130 | this.playlistsData = playlistsResult.data |
131 | 131 | ||
132 | this.videoPlaylistService.runPlaylistCheck(this.video.id) | 132 | this.videoPlaylistService.runVideoExistsInPlaylistCheck(this.video.id) |
133 | }) | 133 | }) |
134 | } | 134 | } |
135 | 135 | ||
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist.service.ts b/client/src/app/shared/shared-video-playlist/video-playlist.service.ts index 330a51f91..bc9fb0d74 100644 --- a/client/src/app/shared/shared-video-playlist/video-playlist.service.ts +++ b/client/src/app/shared/shared-video-playlist/video-playlist.service.ts | |||
@@ -206,7 +206,15 @@ export class VideoPlaylistService { | |||
206 | stopTimestamp: body.stopTimestamp | 206 | stopTimestamp: body.stopTimestamp |
207 | }) | 207 | }) |
208 | 208 | ||
209 | this.runPlaylistCheck(body.videoId) | 209 | this.runVideoExistsInPlaylistCheck(body.videoId) |
210 | |||
211 | if (this.myAccountPlaylistCache) { | ||
212 | const playlist = this.myAccountPlaylistCache.data.find(p => p.id === playlistId) | ||
213 | if (!playlist) return | ||
214 | |||
215 | const otherPlaylists = this.myAccountPlaylistCache.data.filter(p => p !== playlist) | ||
216 | this.myAccountPlaylistCache.data = [ playlist, ...otherPlaylists ] | ||
217 | } | ||
210 | }), | 218 | }), |
211 | catchError(err => this.restExtractor.handleError(err)) | 219 | catchError(err => this.restExtractor.handleError(err)) |
212 | ) | 220 | ) |
@@ -225,7 +233,7 @@ export class VideoPlaylistService { | |||
225 | elem.stopTimestamp = body.stopTimestamp | 233 | elem.stopTimestamp = body.stopTimestamp |
226 | } | 234 | } |
227 | 235 | ||
228 | this.runPlaylistCheck(videoId) | 236 | this.runVideoExistsInPlaylistCheck(videoId) |
229 | }), | 237 | }), |
230 | catchError(err => this.restExtractor.handleError(err)) | 238 | catchError(err => this.restExtractor.handleError(err)) |
231 | ) | 239 | ) |
@@ -242,7 +250,7 @@ export class VideoPlaylistService { | |||
242 | .filter(e => e.playlistElementId !== playlistElementId) | 250 | .filter(e => e.playlistElementId !== playlistElementId) |
243 | } | 251 | } |
244 | 252 | ||
245 | this.runPlaylistCheck(videoId) | 253 | this.runVideoExistsInPlaylistCheck(videoId) |
246 | }), | 254 | }), |
247 | catchError(err => this.restExtractor.handleError(err)) | 255 | catchError(err => this.restExtractor.handleError(err)) |
248 | ) | 256 | ) |
@@ -296,7 +304,7 @@ export class VideoPlaylistService { | |||
296 | return obs | 304 | return obs |
297 | } | 305 | } |
298 | 306 | ||
299 | runPlaylistCheck (videoId: number) { | 307 | runVideoExistsInPlaylistCheck (videoId: number) { |
300 | debugLogger('Running playlist check.') | 308 | debugLogger('Running playlist check.') |
301 | 309 | ||
302 | if (this.videoExistsCache[videoId]) { | 310 | if (this.videoExistsCache[videoId]) { |
diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts index 56310c4e9..413ec2b80 100644 --- a/client/src/assets/player/peertube-player-manager.ts +++ b/client/src/assets/player/peertube-player-manager.ts | |||
@@ -11,6 +11,7 @@ import './shared/control-bar/p2p-info-button' | |||
11 | import './shared/control-bar/peertube-link-button' | 11 | import './shared/control-bar/peertube-link-button' |
12 | import './shared/control-bar/peertube-load-progress-bar' | 12 | import './shared/control-bar/peertube-load-progress-bar' |
13 | import './shared/control-bar/theater-button' | 13 | import './shared/control-bar/theater-button' |
14 | import './shared/control-bar/peertube-live-display' | ||
14 | import './shared/settings/resolution-menu-button' | 15 | import './shared/settings/resolution-menu-button' |
15 | import './shared/settings/resolution-menu-item' | 16 | import './shared/settings/resolution-menu-item' |
16 | import './shared/settings/settings-dialog' | 17 | import './shared/settings/settings-dialog' |
@@ -96,6 +97,10 @@ export class PeertubePlayerManager { | |||
96 | videojs(options.common.playerElement, videojsOptions, function (this: videojs.Player) { | 97 | videojs(options.common.playerElement, videojsOptions, function (this: videojs.Player) { |
97 | const player = this | 98 | const player = this |
98 | 99 | ||
100 | if (!isNaN(+options.common.playbackRate)) { | ||
101 | player.playbackRate(+options.common.playbackRate) | ||
102 | } | ||
103 | |||
99 | let alreadyFallback = false | 104 | let alreadyFallback = false |
100 | 105 | ||
101 | const handleError = () => { | 106 | const handleError = () => { |
diff --git a/client/src/assets/player/shared/control-bar/index.ts b/client/src/assets/player/shared/control-bar/index.ts index db5b8938d..e71e90713 100644 --- a/client/src/assets/player/shared/control-bar/index.ts +++ b/client/src/assets/player/shared/control-bar/index.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | export * from './next-previous-video-button' | 1 | export * from './next-previous-video-button' |
2 | export * from './p2p-info-button' | 2 | export * from './p2p-info-button' |
3 | export * from './peertube-link-button' | 3 | export * from './peertube-link-button' |
4 | export * from './peertube-live-display' | ||
4 | export * from './peertube-load-progress-bar' | 5 | export * from './peertube-load-progress-bar' |
5 | export * from './theater-button' | 6 | export * from './theater-button' |
diff --git a/client/src/assets/player/shared/control-bar/peertube-live-display.ts b/client/src/assets/player/shared/control-bar/peertube-live-display.ts new file mode 100644 index 000000000..649eb0b00 --- /dev/null +++ b/client/src/assets/player/shared/control-bar/peertube-live-display.ts | |||
@@ -0,0 +1,93 @@ | |||
1 | import videojs from 'video.js' | ||
2 | import { PeerTubeLinkButtonOptions } from '../../types' | ||
3 | |||
4 | const ClickableComponent = videojs.getComponent('ClickableComponent') | ||
5 | |||
6 | class PeerTubeLiveDisplay extends ClickableComponent { | ||
7 | private interval: any | ||
8 | |||
9 | private contentEl_: any | ||
10 | |||
11 | constructor (player: videojs.Player, options?: PeerTubeLinkButtonOptions) { | ||
12 | super(player, options as any) | ||
13 | |||
14 | this.interval = this.setInterval(() => this.updateClass(), 1000) | ||
15 | |||
16 | this.show() | ||
17 | this.updateSync(true) | ||
18 | } | ||
19 | |||
20 | dispose () { | ||
21 | if (this.interval) { | ||
22 | this.clearInterval(this.interval) | ||
23 | this.interval = undefined | ||
24 | } | ||
25 | |||
26 | this.contentEl_ = null | ||
27 | |||
28 | super.dispose() | ||
29 | } | ||
30 | |||
31 | createEl () { | ||
32 | const el = super.createEl('div', { | ||
33 | className: 'vjs-live-control vjs-control' | ||
34 | }) | ||
35 | |||
36 | this.contentEl_ = videojs.dom.createEl('div', { | ||
37 | className: 'vjs-live-display' | ||
38 | }, { | ||
39 | 'aria-live': 'off' | ||
40 | }) | ||
41 | |||
42 | this.contentEl_.appendChild(videojs.dom.createEl('span', { | ||
43 | className: 'vjs-control-text', | ||
44 | textContent: `${this.localize('Stream Type')}\u00a0` | ||
45 | })) | ||
46 | |||
47 | this.contentEl_.appendChild(document.createTextNode(this.localize('LIVE'))) | ||
48 | |||
49 | el.appendChild(this.contentEl_) | ||
50 | return el | ||
51 | } | ||
52 | |||
53 | handleClick () { | ||
54 | const hlsjs = this.getHLSJS() | ||
55 | if (!hlsjs) return | ||
56 | |||
57 | this.player().currentTime(hlsjs.liveSyncPosition) | ||
58 | this.player().play() | ||
59 | this.updateSync(true) | ||
60 | } | ||
61 | |||
62 | private updateClass () { | ||
63 | const hlsjs = this.getHLSJS() | ||
64 | if (!hlsjs) return | ||
65 | |||
66 | // Not loaded yet | ||
67 | if (this.player().currentTime() === 0) return | ||
68 | |||
69 | const isSync = Math.abs(this.player().currentTime() - hlsjs.liveSyncPosition) < 10 | ||
70 | this.updateSync(isSync) | ||
71 | } | ||
72 | |||
73 | private updateSync (isSync: boolean) { | ||
74 | if (isSync) { | ||
75 | this.addClass('synced-with-live-edge') | ||
76 | this.removeAttribute('title') | ||
77 | this.disable() | ||
78 | } else { | ||
79 | this.removeClass('synced-with-live-edge') | ||
80 | this.setAttribute('title', this.localize('Go back to the live')) | ||
81 | this.enable() | ||
82 | } | ||
83 | } | ||
84 | |||
85 | private getHLSJS () { | ||
86 | const p2pMediaLoader = this.player()?.p2pMediaLoader | ||
87 | if (!p2pMediaLoader) return undefined | ||
88 | |||
89 | return p2pMediaLoader().getHLSJS() | ||
90 | } | ||
91 | } | ||
92 | |||
93 | videojs.registerComponent('PeerTubeLiveDisplay', PeerTubeLiveDisplay) | ||
diff --git a/client/src/assets/player/shared/manager-options/control-bar-options-builder.ts b/client/src/assets/player/shared/manager-options/control-bar-options-builder.ts index 27f366732..df1e8eabe 100644 --- a/client/src/assets/player/shared/manager-options/control-bar-options-builder.ts +++ b/client/src/assets/player/shared/manager-options/control-bar-options-builder.ts | |||
@@ -30,10 +30,7 @@ export class ControlBarOptionsBuilder { | |||
30 | } | 30 | } |
31 | 31 | ||
32 | Object.assign(children, { | 32 | Object.assign(children, { |
33 | currentTimeDisplay: {}, | 33 | ...this.getTimeControls(), |
34 | timeDivider: {}, | ||
35 | durationDisplay: {}, | ||
36 | liveDisplay: {}, | ||
37 | 34 | ||
38 | flexibleWidthSpacer: {}, | 35 | flexibleWidthSpacer: {}, |
39 | 36 | ||
@@ -90,7 +87,23 @@ export class ControlBarOptionsBuilder { | |||
90 | } | 87 | } |
91 | } | 88 | } |
92 | 89 | ||
90 | private getTimeControls () { | ||
91 | if (this.options.isLive) { | ||
92 | return { | ||
93 | peerTubeLiveDisplay: {} | ||
94 | } | ||
95 | } | ||
96 | |||
97 | return { | ||
98 | currentTimeDisplay: {}, | ||
99 | timeDivider: {}, | ||
100 | durationDisplay: {} | ||
101 | } | ||
102 | } | ||
103 | |||
93 | private getProgressControl () { | 104 | private getProgressControl () { |
105 | if (this.options.isLive) return {} | ||
106 | |||
94 | const loadProgressBar = this.mode === 'webtorrent' | 107 | const loadProgressBar = this.mode === 'webtorrent' |
95 | ? 'peerTubeLoadProgressBar' | 108 | ? 'peerTubeLoadProgressBar' |
96 | : 'loadProgressBar' | 109 | : 'loadProgressBar' |
diff --git a/client/src/assets/player/shared/p2p-media-loader/hls-plugin.ts b/client/src/assets/player/shared/p2p-media-loader/hls-plugin.ts index a14beb347..7f7d90ab9 100644 --- a/client/src/assets/player/shared/p2p-media-loader/hls-plugin.ts +++ b/client/src/assets/player/shared/p2p-media-loader/hls-plugin.ts | |||
@@ -281,8 +281,8 @@ class Html5Hlsjs { | |||
281 | if (this.errorCounts[data.type]) this.errorCounts[data.type] += 1 | 281 | if (this.errorCounts[data.type]) this.errorCounts[data.type] += 1 |
282 | else this.errorCounts[data.type] = 1 | 282 | else this.errorCounts[data.type] = 1 |
283 | 283 | ||
284 | if (data.fatal) logger.warn(error.message) | 284 | if (data.fatal) logger.error(error.message, { currentTime: this.player.currentTime(), data }) |
285 | else logger.error(error.message, { data }) | 285 | else logger.warn(error.message) |
286 | 286 | ||
287 | if (data.type === Hlsjs.ErrorTypes.NETWORK_ERROR) { | 287 | if (data.type === Hlsjs.ErrorTypes.NETWORK_ERROR) { |
288 | error.code = 2 | 288 | error.code = 2 |
diff --git a/client/src/assets/player/shared/stats/stats-card.ts b/client/src/assets/player/shared/stats/stats-card.ts index f23ae48be..471a5e46c 100644 --- a/client/src/assets/player/shared/stats/stats-card.ts +++ b/client/src/assets/player/shared/stats/stats-card.ts | |||
@@ -182,7 +182,7 @@ class StatsCard extends Component { | |||
182 | let colorSpace = 'unknown' | 182 | let colorSpace = 'unknown' |
183 | let codecs = 'unknown' | 183 | let codecs = 'unknown' |
184 | 184 | ||
185 | if (metadata?.streams[0]) { | 185 | if (metadata?.streams?.[0]) { |
186 | const stream = metadata.streams[0] | 186 | const stream = metadata.streams[0] |
187 | 187 | ||
188 | colorSpace = stream['color_space'] !== 'unknown' | 188 | colorSpace = stream['color_space'] !== 'unknown' |
@@ -193,7 +193,7 @@ class StatsCard extends Component { | |||
193 | } | 193 | } |
194 | 194 | ||
195 | const resolution = videoFile?.resolution.label + videoFile?.fps | 195 | const resolution = videoFile?.resolution.label + videoFile?.fps |
196 | const buffer = this.timeRangesToString(this.player().buffered()) | 196 | const buffer = this.timeRangesToString(this.player_.buffered()) |
197 | const progress = this.player_.webtorrent().getTorrent()?.progress | 197 | const progress = this.player_.webtorrent().getTorrent()?.progress |
198 | 198 | ||
199 | return { | 199 | return { |
diff --git a/client/src/assets/player/types/manager-options.ts b/client/src/assets/player/types/manager-options.ts index 3057a5adb..3fbcec29c 100644 --- a/client/src/assets/player/types/manager-options.ts +++ b/client/src/assets/player/types/manager-options.ts | |||
@@ -29,6 +29,8 @@ export interface CustomizationOptions { | |||
29 | resume?: string | 29 | resume?: string |
30 | 30 | ||
31 | peertubeLink: boolean | 31 | peertubeLink: boolean |
32 | |||
33 | playbackRate?: number | string | ||
32 | } | 34 | } |
33 | 35 | ||
34 | export interface CommonOptions extends CustomizationOptions { | 36 | export interface CommonOptions extends CustomizationOptions { |
diff --git a/client/src/root-helpers/logger.ts b/client/src/root-helpers/logger.ts index d1fdf73aa..618be62cd 100644 --- a/client/src/root-helpers/logger.ts +++ b/client/src/root-helpers/logger.ts | |||
@@ -27,6 +27,10 @@ class Logger { | |||
27 | warn (message: LoggerMessage, meta?: LoggerMeta) { | 27 | warn (message: LoggerMessage, meta?: LoggerMeta) { |
28 | this.runHooks('warn', message, meta) | 28 | this.runHooks('warn', message, meta) |
29 | 29 | ||
30 | this.clientWarn(message, meta) | ||
31 | } | ||
32 | |||
33 | clientWarn (message: LoggerMessage, meta?: LoggerMeta) { | ||
30 | if (meta) console.warn(message, meta) | 34 | if (meta) console.warn(message, meta) |
31 | else console.warn(message) | 35 | else console.warn(message) |
32 | } | 36 | } |
@@ -34,6 +38,10 @@ class Logger { | |||
34 | error (message: LoggerMessage, meta?: LoggerMeta) { | 38 | error (message: LoggerMessage, meta?: LoggerMeta) { |
35 | this.runHooks('error', message, meta) | 39 | this.runHooks('error', message, meta) |
36 | 40 | ||
41 | this.clientError(message, meta) | ||
42 | } | ||
43 | |||
44 | clientError (message: LoggerMessage, meta?: LoggerMeta) { | ||
37 | if (meta) console.error(message, meta) | 45 | if (meta) console.error(message, meta) |
38 | else console.error(message) | 46 | else console.error(message) |
39 | } | 47 | } |
diff --git a/client/src/sass/player/control-bar.scss b/client/src/sass/player/control-bar.scss index 0082378e4..96b3adf66 100644 --- a/client/src/sass/player/control-bar.scss +++ b/client/src/sass/player/control-bar.scss | |||
@@ -153,8 +153,25 @@ | |||
153 | } | 153 | } |
154 | 154 | ||
155 | .vjs-live-control { | 155 | .vjs-live-control { |
156 | line-height: $control-bar-height; | 156 | padding: 5px 7px; |
157 | min-width: 4em; | 157 | border-radius: 3px; |
158 | height: fit-content; | ||
159 | margin: auto 10px; | ||
160 | font-weight: bold; | ||
161 | max-width: fit-content; | ||
162 | opacity: 1 !important; | ||
163 | line-height: normal; | ||
164 | position: relative; | ||
165 | top: -1px; | ||
166 | |||
167 | &.synced-with-live-edge { | ||
168 | background: #d7281c; | ||
169 | } | ||
170 | |||
171 | &:not(.synced-with-live-edge) { | ||
172 | cursor: pointer; | ||
173 | background: #80807f; | ||
174 | } | ||
158 | } | 175 | } |
159 | 176 | ||
160 | .vjs-peertube { | 177 | .vjs-peertube { |
diff --git a/client/src/standalone/videos/shared/player-manager-options.ts b/client/src/standalone/videos/shared/player-manager-options.ts index b0bdb2dd9..f09c86d14 100644 --- a/client/src/standalone/videos/shared/player-manager-options.ts +++ b/client/src/standalone/videos/shared/player-manager-options.ts | |||
@@ -38,6 +38,7 @@ export class PlayerManagerOptions { | |||
38 | private enableApi = false | 38 | private enableApi = false |
39 | private startTime: number | string = 0 | 39 | private startTime: number | string = 0 |
40 | private stopTime: number | string | 40 | private stopTime: number | string |
41 | private playbackRate: number | string | ||
41 | 42 | ||
42 | private title: boolean | 43 | private title: boolean |
43 | private warningTitle: boolean | 44 | private warningTitle: boolean |
@@ -130,6 +131,7 @@ export class PlayerManagerOptions { | |||
130 | this.subtitle = getParamString(params, 'subtitle') | 131 | this.subtitle = getParamString(params, 'subtitle') |
131 | this.startTime = getParamString(params, 'start') | 132 | this.startTime = getParamString(params, 'start') |
132 | this.stopTime = getParamString(params, 'stop') | 133 | this.stopTime = getParamString(params, 'stop') |
134 | this.playbackRate = getParamString(params, 'playbackRate') | ||
133 | 135 | ||
134 | this.bigPlayBackgroundColor = getParamString(params, 'bigPlayBackgroundColor') | 136 | this.bigPlayBackgroundColor = getParamString(params, 'bigPlayBackgroundColor') |
135 | this.foregroundColor = getParamString(params, 'foregroundColor') | 137 | this.foregroundColor = getParamString(params, 'foregroundColor') |
@@ -210,6 +212,8 @@ export class PlayerManagerOptions { | |||
210 | ? playlistTracker.getCurrentElement().stopTimestamp | 212 | ? playlistTracker.getCurrentElement().stopTimestamp |
211 | : this.stopTime, | 213 | : this.stopTime, |
212 | 214 | ||
215 | playbackRate: this.playbackRate, | ||
216 | |||
213 | videoCaptions, | 217 | videoCaptions, |
214 | inactivityTimeout: 2500, | 218 | inactivityTimeout: 2500, |
215 | videoViewUrl: this.videoFetcher.getVideoViewsUrl(video.uuid), | 219 | videoViewUrl: this.videoFetcher.getVideoViewsUrl(video.uuid), |
diff --git a/client/yarn.lock b/client/yarn.lock index b680bfdfb..6a5456283 100644 --- a/client/yarn.lock +++ b/client/yarn.lock | |||
@@ -1789,10 +1789,10 @@ | |||
1789 | read-package-json-fast "^2.0.3" | 1789 | read-package-json-fast "^2.0.3" |
1790 | which "^2.0.2" | 1790 | which "^2.0.2" |
1791 | 1791 | ||
1792 | "@peertube/p2p-media-loader-core@^1.0.13", "@peertube/p2p-media-loader-core@^1.0.8": | 1792 | "@peertube/p2p-media-loader-core@^1.0.14": |
1793 | version "1.0.13" | 1793 | version "1.0.14" |
1794 | resolved "https://registry.yarnpkg.com/@peertube/p2p-media-loader-core/-/p2p-media-loader-core-1.0.13.tgz#36744a291b69c001b2562c1a93017979f8534ff8" | 1794 | resolved "https://registry.yarnpkg.com/@peertube/p2p-media-loader-core/-/p2p-media-loader-core-1.0.14.tgz#b4442dd343d6b30a51502e1240275eb98ef2c788" |
1795 | integrity sha512-ArSAaeuxwwBAG0Xd3Gj0TzKObLfJFYzHz9+fREvmUf+GZQEG6qGwWmrdVWL6xjPiEuo6LdFeCOnHSQzAbj/ptg== | 1795 | integrity sha512-tjQv1CNziNY+zYzcL1h4q6AA2WuBUZnBIeVyjWR/EsO1EEC1VMdvPsL02cqYLz9yvIxgycjeTsWCm6XDqNgXRw== |
1796 | dependencies: | 1796 | dependencies: |
1797 | bittorrent-tracker "^9.19.0" | 1797 | bittorrent-tracker "^9.19.0" |
1798 | debug "^4.3.4" | 1798 | debug "^4.3.4" |
@@ -1800,12 +1800,12 @@ | |||
1800 | sha.js "^2.4.11" | 1800 | sha.js "^2.4.11" |
1801 | simple-peer "^9.11.1" | 1801 | simple-peer "^9.11.1" |
1802 | 1802 | ||
1803 | "@peertube/p2p-media-loader-hlsjs@^1.0.13": | 1803 | "@peertube/p2p-media-loader-hlsjs@^1.0.14": |
1804 | version "1.0.13" | 1804 | version "1.0.14" |
1805 | resolved "https://registry.yarnpkg.com/@peertube/p2p-media-loader-hlsjs/-/p2p-media-loader-hlsjs-1.0.13.tgz#5305e2008041d01850802544d1c49298f79dd67a" | 1805 | resolved "https://registry.yarnpkg.com/@peertube/p2p-media-loader-hlsjs/-/p2p-media-loader-hlsjs-1.0.14.tgz#829629a57608b0e30f4b50bc98578e6bee9f8b9b" |
1806 | integrity sha512-2BO2oaRsSHEhLkgi2iw1r4n1Yqq1EnyoOgOZccPDqjmHUsZSV/wNrno8WYr6LsleudrHA26Imu57hVD1jDx7lg== | 1806 | integrity sha512-ySUVgUvAFXCE5E94xxjfywQ8xzk3jy9UGVkgi5Oqq+QeY7uG+o7CZ+LsQ/RjXgWBD70tEnyyfADHtL+9FCnwyQ== |
1807 | dependencies: | 1807 | dependencies: |
1808 | "@peertube/p2p-media-loader-core" "^1.0.8" | 1808 | "@peertube/p2p-media-loader-core" "^1.0.14" |
1809 | debug "^4.3.4" | 1809 | debug "^4.3.4" |
1810 | events "^3.3.0" | 1810 | events "^3.3.0" |
1811 | m3u8-parser "^4.7.1" | 1811 | m3u8-parser "^4.7.1" |
diff --git a/scripts/i18n/create-custom-files.ts b/scripts/i18n/create-custom-files.ts index bcd7fe2a2..3b5045954 100755 --- a/scripts/i18n/create-custom-files.ts +++ b/scripts/i18n/create-custom-files.ts | |||
@@ -41,6 +41,7 @@ const playerKeys = { | |||
41 | 'Volume': 'Volume', | 41 | 'Volume': 'Volume', |
42 | 'Codecs': 'Codecs', | 42 | 'Codecs': 'Codecs', |
43 | 'Color': 'Color', | 43 | 'Color': 'Color', |
44 | 'Go back to the live': 'Go back to the live', | ||
44 | 'Connection Speed': 'Connection Speed', | 45 | 'Connection Speed': 'Connection Speed', |
45 | 'Network Activity': 'Network Activity', | 46 | 'Network Activity': 'Network Activity', |
46 | 'Total Transfered': 'Total Transfered', | 47 | 'Total Transfered': 'Total Transfered', |
diff --git a/server/controllers/api/videos/token.ts b/server/controllers/api/videos/token.ts index 009b6dfb6..22387c3e8 100644 --- a/server/controllers/api/videos/token.ts +++ b/server/controllers/api/videos/token.ts | |||
@@ -22,7 +22,7 @@ export { | |||
22 | function generateToken (req: express.Request, res: express.Response) { | 22 | function generateToken (req: express.Request, res: express.Response) { |
23 | const video = res.locals.onlyVideo | 23 | const video = res.locals.onlyVideo |
24 | 24 | ||
25 | const { token, expires } = VideoTokensManager.Instance.create(video.uuid) | 25 | const { token, expires } = VideoTokensManager.Instance.create({ videoUUID: video.uuid, user: res.locals.oauth.token.User }) |
26 | 26 | ||
27 | return res.json({ | 27 | return res.json({ |
28 | files: { | 28 | files: { |
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index 772fe734d..ef810a842 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts | |||
@@ -285,8 +285,8 @@ function addVideosToFeed (feed: Feed, videos: VideoModel[]) { | |||
285 | content: toSafeHtml(video.description), | 285 | content: toSafeHtml(video.description), |
286 | author: [ | 286 | author: [ |
287 | { | 287 | { |
288 | name: video.VideoChannel.Account.getDisplayName(), | 288 | name: video.VideoChannel.getDisplayName(), |
289 | link: video.VideoChannel.Account.Actor.url | 289 | link: video.VideoChannel.Actor.url |
290 | } | 290 | } |
291 | ], | 291 | ], |
292 | date: video.publishedAt, | 292 | date: video.publishedAt, |
diff --git a/server/controllers/tracker.ts b/server/controllers/tracker.ts index 19a8b2bc9..0ef98c35e 100644 --- a/server/controllers/tracker.ts +++ b/server/controllers/tracker.ts | |||
@@ -1,17 +1,22 @@ | |||
1 | import { Server as TrackerServer } from 'bittorrent-tracker' | 1 | import { Server as TrackerServer } from 'bittorrent-tracker' |
2 | import express from 'express' | 2 | import express from 'express' |
3 | import { createServer } from 'http' | 3 | import { createServer } from 'http' |
4 | import LRUCache from 'lru-cache' | ||
4 | import proxyAddr from 'proxy-addr' | 5 | import proxyAddr from 'proxy-addr' |
5 | import { WebSocketServer } from 'ws' | 6 | import { WebSocketServer } from 'ws' |
6 | import { Redis } from '@server/lib/redis' | ||
7 | import { logger } from '../helpers/logger' | 7 | import { logger } from '../helpers/logger' |
8 | import { CONFIG } from '../initializers/config' | 8 | import { CONFIG } from '../initializers/config' |
9 | import { TRACKER_RATE_LIMITS } from '../initializers/constants' | 9 | import { LRU_CACHE, TRACKER_RATE_LIMITS } from '../initializers/constants' |
10 | import { VideoFileModel } from '../models/video/video-file' | 10 | import { VideoFileModel } from '../models/video/video-file' |
11 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | 11 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' |
12 | 12 | ||
13 | const trackerRouter = express.Router() | 13 | const trackerRouter = express.Router() |
14 | 14 | ||
15 | const blockedIPs = new LRUCache<string, boolean>({ | ||
16 | max: LRU_CACHE.TRACKER_IPS.MAX_SIZE, | ||
17 | ttl: TRACKER_RATE_LIMITS.BLOCK_IP_LIFETIME | ||
18 | }) | ||
19 | |||
15 | let peersIps = {} | 20 | let peersIps = {} |
16 | let peersIpInfoHash = {} | 21 | let peersIpInfoHash = {} |
17 | runPeersChecker() | 22 | runPeersChecker() |
@@ -55,8 +60,7 @@ const trackerServer = new TrackerServer({ | |||
55 | 60 | ||
56 | // Close socket connection and block IP for a few time | 61 | // Close socket connection and block IP for a few time |
57 | if (params.type === 'ws') { | 62 | if (params.type === 'ws') { |
58 | Redis.Instance.setTrackerBlockIP(ip) | 63 | blockedIPs.set(ip, true) |
59 | .catch(err => logger.error('Cannot set tracker block ip.', { err })) | ||
60 | 64 | ||
61 | // setTimeout to wait filter response | 65 | // setTimeout to wait filter response |
62 | setTimeout(() => params.socket.close(), 0) | 66 | setTimeout(() => params.socket.close(), 0) |
@@ -102,20 +106,16 @@ function createWebsocketTrackerServer (app: express.Application) { | |||
102 | if (request.url === '/tracker/socket') { | 106 | if (request.url === '/tracker/socket') { |
103 | const ip = proxyAddr(request, CONFIG.TRUST_PROXY) | 107 | const ip = proxyAddr(request, CONFIG.TRUST_PROXY) |
104 | 108 | ||
105 | Redis.Instance.doesTrackerBlockIPExist(ip) | 109 | if (blockedIPs.has(ip)) { |
106 | .then(result => { | 110 | logger.debug('Blocking IP %s from tracker.', ip) |
107 | if (result === true) { | ||
108 | logger.debug('Blocking IP %s from tracker.', ip) | ||
109 | 111 | ||
110 | socket.write('HTTP/1.1 403 Forbidden\r\n\r\n') | 112 | socket.write('HTTP/1.1 403 Forbidden\r\n\r\n') |
111 | socket.destroy() | 113 | socket.destroy() |
112 | return | 114 | return |
113 | } | 115 | } |
114 | 116 | ||
115 | // FIXME: typings | 117 | // FIXME: typings |
116 | return wss.handleUpgrade(request, socket as any, head, ws => wss.emit('connection', ws, request)) | 118 | return wss.handleUpgrade(request, socket as any, head, ws => wss.emit('connection', ws, request)) |
117 | }) | ||
118 | .catch(err => logger.error('Cannot check if tracker block ip exists.', { err })) | ||
119 | } | 119 | } |
120 | 120 | ||
121 | // Don't destroy socket, we have Socket.IO too | 121 | // Don't destroy socket, we have Socket.IO too |
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts index 3dc5504e3..b3ab3ac64 100644 --- a/server/helpers/custom-validators/misc.ts +++ b/server/helpers/custom-validators/misc.ts | |||
@@ -103,7 +103,13 @@ function checkMimetypeRegex (fileMimeType: string, mimeTypeRegex: string) { | |||
103 | // --------------------------------------------------------------------------- | 103 | // --------------------------------------------------------------------------- |
104 | 104 | ||
105 | function toCompleteUUID (value: string) { | 105 | function toCompleteUUID (value: string) { |
106 | if (isShortUUID(value)) return shortToUUID(value) | 106 | if (isShortUUID(value)) { |
107 | try { | ||
108 | return shortToUUID(value) | ||
109 | } catch { | ||
110 | return null | ||
111 | } | ||
112 | } | ||
107 | 113 | ||
108 | return value | 114 | return value |
109 | } | 115 | } |
diff --git a/server/helpers/youtube-dl/youtube-dl-cli.ts b/server/helpers/youtube-dl/youtube-dl-cli.ts index a2f630953..765038cea 100644 --- a/server/helpers/youtube-dl/youtube-dl-cli.ts +++ b/server/helpers/youtube-dl/youtube-dl-cli.ts | |||
@@ -6,6 +6,7 @@ import { VideoResolution } from '@shared/models' | |||
6 | import { logger, loggerTagsFactory } from '../logger' | 6 | import { logger, loggerTagsFactory } from '../logger' |
7 | import { getProxy, isProxyEnabled } from '../proxy' | 7 | import { getProxy, isProxyEnabled } from '../proxy' |
8 | import { isBinaryResponse, peertubeGot } from '../requests' | 8 | import { isBinaryResponse, peertubeGot } from '../requests' |
9 | import { OptionsOfBufferResponseBody } from 'got/dist/source' | ||
9 | 10 | ||
10 | const lTags = loggerTagsFactory('youtube-dl') | 11 | const lTags = loggerTagsFactory('youtube-dl') |
11 | 12 | ||
@@ -28,7 +29,16 @@ export class YoutubeDLCLI { | |||
28 | 29 | ||
29 | logger.info('Updating youtubeDL binary from %s.', url, lTags()) | 30 | logger.info('Updating youtubeDL binary from %s.', url, lTags()) |
30 | 31 | ||
31 | const gotOptions = { context: { bodyKBLimit: 20_000 }, responseType: 'buffer' as 'buffer' } | 32 | const gotOptions: OptionsOfBufferResponseBody = { |
33 | context: { bodyKBLimit: 20_000 }, | ||
34 | responseType: 'buffer' as 'buffer' | ||
35 | } | ||
36 | |||
37 | if (process.env.YOUTUBE_DL_DOWNLOAD_BEARER_TOKEN) { | ||
38 | gotOptions.headers = { | ||
39 | authorization: 'Bearer ' + process.env.YOUTUBE_DL_DOWNLOAD_BEARER_TOKEN | ||
40 | } | ||
41 | } | ||
32 | 42 | ||
33 | try { | 43 | try { |
34 | let gotResult = await peertubeGot(url, gotOptions) | 44 | let gotResult = await peertubeGot(url, gotOptions) |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 0e56f0c9f..ec5045078 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -781,6 +781,9 @@ const LRU_CACHE = { | |||
781 | VIDEO_TOKENS: { | 781 | VIDEO_TOKENS: { |
782 | MAX_SIZE: 100_000, | 782 | MAX_SIZE: 100_000, |
783 | TTL: parseDurationToMs('8 hours') | 783 | TTL: parseDurationToMs('8 hours') |
784 | }, | ||
785 | TRACKER_IPS: { | ||
786 | MAX_SIZE: 100_000 | ||
784 | } | 787 | } |
785 | } | 788 | } |
786 | 789 | ||
@@ -884,7 +887,7 @@ const TRACKER_RATE_LIMITS = { | |||
884 | INTERVAL: 60000 * 5, // 5 minutes | 887 | INTERVAL: 60000 * 5, // 5 minutes |
885 | ANNOUNCES_PER_IP_PER_INFOHASH: 15, // maximum announces per torrent in the interval | 888 | ANNOUNCES_PER_IP_PER_INFOHASH: 15, // maximum announces per torrent in the interval |
886 | ANNOUNCES_PER_IP: 30, // maximum announces for all our torrents in the interval | 889 | ANNOUNCES_PER_IP: 30, // maximum announces for all our torrents in the interval |
887 | BLOCK_IP_LIFETIME: 60000 * 3 // 3 minutes | 890 | BLOCK_IP_LIFETIME: parseDurationToMs('3 minutes') |
888 | } | 891 | } |
889 | 892 | ||
890 | const P2P_MEDIA_LOADER_PEER_VERSION = 2 | 893 | const P2P_MEDIA_LOADER_PEER_VERSION = 2 |
diff --git a/server/lib/plugins/plugin-helpers-builder.ts b/server/lib/plugins/plugin-helpers-builder.ts index 7b1def6e3..e75c0b994 100644 --- a/server/lib/plugins/plugin-helpers-builder.ts +++ b/server/lib/plugins/plugin-helpers-builder.ts | |||
@@ -245,7 +245,7 @@ function buildUserHelpers () { | |||
245 | }, | 245 | }, |
246 | 246 | ||
247 | getAuthUser: (res: express.Response) => { | 247 | getAuthUser: (res: express.Response) => { |
248 | const user = res.locals.oauth?.token?.User | 248 | const user = res.locals.oauth?.token?.User || res.locals.videoFileToken?.user |
249 | if (!user) return undefined | 249 | if (!user) return undefined |
250 | 250 | ||
251 | return UserModel.loadByIdFull(user.id) | 251 | return UserModel.loadByIdFull(user.id) |
diff --git a/server/lib/redis.ts b/server/lib/redis.ts index c0e9aece7..451ddd0b6 100644 --- a/server/lib/redis.ts +++ b/server/lib/redis.ts | |||
@@ -8,7 +8,6 @@ import { | |||
8 | AP_CLEANER, | 8 | AP_CLEANER, |
9 | CONTACT_FORM_LIFETIME, | 9 | CONTACT_FORM_LIFETIME, |
10 | RESUMABLE_UPLOAD_SESSION_LIFETIME, | 10 | RESUMABLE_UPLOAD_SESSION_LIFETIME, |
11 | TRACKER_RATE_LIMITS, | ||
12 | TWO_FACTOR_AUTH_REQUEST_TOKEN_LIFETIME, | 11 | TWO_FACTOR_AUTH_REQUEST_TOKEN_LIFETIME, |
13 | USER_EMAIL_VERIFY_LIFETIME, | 12 | USER_EMAIL_VERIFY_LIFETIME, |
14 | USER_PASSWORD_CREATE_LIFETIME, | 13 | USER_PASSWORD_CREATE_LIFETIME, |
@@ -157,16 +156,6 @@ class Redis { | |||
157 | return this.exists(this.generateIPViewKey(ip, videoUUID)) | 156 | return this.exists(this.generateIPViewKey(ip, videoUUID)) |
158 | } | 157 | } |
159 | 158 | ||
160 | /* ************ Tracker IP block ************ */ | ||
161 | |||
162 | setTrackerBlockIP (ip: string) { | ||
163 | return this.setValue(this.generateTrackerBlockIPKey(ip), '1', TRACKER_RATE_LIMITS.BLOCK_IP_LIFETIME) | ||
164 | } | ||
165 | |||
166 | async doesTrackerBlockIPExist (ip: string) { | ||
167 | return this.exists(this.generateTrackerBlockIPKey(ip)) | ||
168 | } | ||
169 | |||
170 | /* ************ Video views stats ************ */ | 159 | /* ************ Video views stats ************ */ |
171 | 160 | ||
172 | addVideoViewStats (videoId: number) { | 161 | addVideoViewStats (videoId: number) { |
@@ -365,10 +354,6 @@ class Redis { | |||
365 | return `views-${videoUUID}-${ip}` | 354 | return `views-${videoUUID}-${ip}` |
366 | } | 355 | } |
367 | 356 | ||
368 | private generateTrackerBlockIPKey (ip: string) { | ||
369 | return `tracker-block-ip-${ip}` | ||
370 | } | ||
371 | |||
372 | private generateContactFormKey (ip: string) { | 357 | private generateContactFormKey (ip: string) { |
373 | return 'contact-form-' + ip | 358 | return 'contact-form-' + ip |
374 | } | 359 | } |
diff --git a/server/lib/sync-channel.ts b/server/lib/sync-channel.ts index 10167ee38..3a805a943 100644 --- a/server/lib/sync-channel.ts +++ b/server/lib/sync-channel.ts | |||
@@ -76,7 +76,7 @@ export async function synchronizeChannel (options: { | |||
76 | 76 | ||
77 | await JobQueue.Instance.createJobWithChildren(parent, children) | 77 | await JobQueue.Instance.createJobWithChildren(parent, children) |
78 | } catch (err) { | 78 | } catch (err) { |
79 | logger.error(`Failed to import channel ${channel.name}`, { err }) | 79 | logger.error(`Failed to import ${externalChannelUrl} in channel ${channel.name}`, { err }) |
80 | channelSync.state = VideoChannelSyncState.FAILED | 80 | channelSync.state = VideoChannelSyncState.FAILED |
81 | await channelSync.save() | 81 | await channelSync.save() |
82 | } | 82 | } |
diff --git a/server/lib/video-tokens-manager.ts b/server/lib/video-tokens-manager.ts index c43085d16..17aa29cdd 100644 --- a/server/lib/video-tokens-manager.ts +++ b/server/lib/video-tokens-manager.ts | |||
@@ -1,5 +1,7 @@ | |||
1 | import LRUCache from 'lru-cache' | 1 | import LRUCache from 'lru-cache' |
2 | import { LRU_CACHE } from '@server/initializers/constants' | 2 | import { LRU_CACHE } from '@server/initializers/constants' |
3 | import { MUserAccountUrl } from '@server/types/models' | ||
4 | import { pick } from '@shared/core-utils' | ||
3 | import { buildUUID } from '@shared/extra-utils' | 5 | import { buildUUID } from '@shared/extra-utils' |
4 | 6 | ||
5 | // --------------------------------------------------------------------------- | 7 | // --------------------------------------------------------------------------- |
@@ -10,19 +12,22 @@ class VideoTokensManager { | |||
10 | 12 | ||
11 | private static instance: VideoTokensManager | 13 | private static instance: VideoTokensManager |
12 | 14 | ||
13 | private readonly lruCache = new LRUCache<string, string>({ | 15 | private readonly lruCache = new LRUCache<string, { videoUUID: string, user: MUserAccountUrl }>({ |
14 | max: LRU_CACHE.VIDEO_TOKENS.MAX_SIZE, | 16 | max: LRU_CACHE.VIDEO_TOKENS.MAX_SIZE, |
15 | ttl: LRU_CACHE.VIDEO_TOKENS.TTL | 17 | ttl: LRU_CACHE.VIDEO_TOKENS.TTL |
16 | }) | 18 | }) |
17 | 19 | ||
18 | private constructor () {} | 20 | private constructor () {} |
19 | 21 | ||
20 | create (videoUUID: string) { | 22 | create (options: { |
23 | user: MUserAccountUrl | ||
24 | videoUUID: string | ||
25 | }) { | ||
21 | const token = buildUUID() | 26 | const token = buildUUID() |
22 | 27 | ||
23 | const expires = new Date(new Date().getTime() + LRU_CACHE.VIDEO_TOKENS.TTL) | 28 | const expires = new Date(new Date().getTime() + LRU_CACHE.VIDEO_TOKENS.TTL) |
24 | 29 | ||
25 | this.lruCache.set(token, videoUUID) | 30 | this.lruCache.set(token, pick(options, [ 'user', 'videoUUID' ])) |
26 | 31 | ||
27 | return { token, expires } | 32 | return { token, expires } |
28 | } | 33 | } |
@@ -34,7 +39,16 @@ class VideoTokensManager { | |||
34 | const value = this.lruCache.get(options.token) | 39 | const value = this.lruCache.get(options.token) |
35 | if (!value) return false | 40 | if (!value) return false |
36 | 41 | ||
37 | return value === options.videoUUID | 42 | return value.videoUUID === options.videoUUID |
43 | } | ||
44 | |||
45 | getUserFromToken (options: { | ||
46 | token: string | ||
47 | }) { | ||
48 | const value = this.lruCache.get(options.token) | ||
49 | if (!value) return undefined | ||
50 | |||
51 | return value.user | ||
38 | } | 52 | } |
39 | 53 | ||
40 | static get Instance () { | 54 | static get Instance () { |
diff --git a/server/middlewares/validators/shared/videos.ts b/server/middlewares/validators/shared/videos.ts index ebbfc0a0a..0033a32ff 100644 --- a/server/middlewares/validators/shared/videos.ts +++ b/server/middlewares/validators/shared/videos.ts | |||
@@ -180,18 +180,16 @@ async function checkCanAccessVideoStaticFiles (options: { | |||
180 | return checkCanSeeVideo(options) | 180 | return checkCanSeeVideo(options) |
181 | } | 181 | } |
182 | 182 | ||
183 | if (!video.hasPrivateStaticPath()) return true | ||
184 | |||
185 | const videoFileToken = req.query.videoFileToken | 183 | const videoFileToken = req.query.videoFileToken |
186 | if (!videoFileToken) { | 184 | if (videoFileToken && VideoTokensManager.Instance.hasToken({ token: videoFileToken, videoUUID: video.uuid })) { |
187 | res.sendStatus(HttpStatusCode.FORBIDDEN_403) | 185 | const user = VideoTokensManager.Instance.getUserFromToken({ token: videoFileToken }) |
188 | return false | ||
189 | } | ||
190 | 186 | ||
191 | if (VideoTokensManager.Instance.hasToken({ token: videoFileToken, videoUUID: video.uuid })) { | 187 | res.locals.videoFileToken = { user } |
192 | return true | 188 | return true |
193 | } | 189 | } |
194 | 190 | ||
191 | if (!video.hasPrivateStaticPath()) return true | ||
192 | |||
195 | res.sendStatus(HttpStatusCode.FORBIDDEN_403) | 193 | res.sendStatus(HttpStatusCode.FORBIDDEN_403) |
196 | return false | 194 | return false |
197 | } | 195 | } |
diff --git a/server/tests/api/check-params/redundancy.ts b/server/tests/api/check-params/redundancy.ts index 908407b9a..73dfd489d 100644 --- a/server/tests/api/check-params/redundancy.ts +++ b/server/tests/api/check-params/redundancy.ts | |||
@@ -24,7 +24,7 @@ describe('Test server redundancy API validators', function () { | |||
24 | // --------------------------------------------------------------- | 24 | // --------------------------------------------------------------- |
25 | 25 | ||
26 | before(async function () { | 26 | before(async function () { |
27 | this.timeout(80000) | 27 | this.timeout(160000) |
28 | 28 | ||
29 | servers = await createMultipleServers(2) | 29 | servers = await createMultipleServers(2) |
30 | 30 | ||
diff --git a/server/tests/api/notifications/moderation-notifications.ts b/server/tests/api/notifications/moderation-notifications.ts index b127a7a31..c7b9b5fb0 100644 --- a/server/tests/api/notifications/moderation-notifications.ts +++ b/server/tests/api/notifications/moderation-notifications.ts | |||
@@ -34,7 +34,7 @@ describe('Test moderation notifications', function () { | |||
34 | let emails: object[] = [] | 34 | let emails: object[] = [] |
35 | 35 | ||
36 | before(async function () { | 36 | before(async function () { |
37 | this.timeout(120000) | 37 | this.timeout(50000) |
38 | 38 | ||
39 | const res = await prepareNotificationsTest(3) | 39 | const res = await prepareNotificationsTest(3) |
40 | emails = res.emails | 40 | emails = res.emails |
@@ -60,7 +60,7 @@ describe('Test moderation notifications', function () { | |||
60 | }) | 60 | }) |
61 | 61 | ||
62 | it('Should not send a notification to moderators on local abuse reported by an admin', async function () { | 62 | it('Should not send a notification to moderators on local abuse reported by an admin', async function () { |
63 | this.timeout(20000) | 63 | this.timeout(50000) |
64 | 64 | ||
65 | const name = 'video for abuse ' + buildUUID() | 65 | const name = 'video for abuse ' + buildUUID() |
66 | const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } }) | 66 | const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } }) |
@@ -72,7 +72,7 @@ describe('Test moderation notifications', function () { | |||
72 | }) | 72 | }) |
73 | 73 | ||
74 | it('Should send a notification to moderators on local video abuse', async function () { | 74 | it('Should send a notification to moderators on local video abuse', async function () { |
75 | this.timeout(20000) | 75 | this.timeout(50000) |
76 | 76 | ||
77 | const name = 'video for abuse ' + buildUUID() | 77 | const name = 'video for abuse ' + buildUUID() |
78 | const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } }) | 78 | const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } }) |
@@ -84,7 +84,7 @@ describe('Test moderation notifications', function () { | |||
84 | }) | 84 | }) |
85 | 85 | ||
86 | it('Should send a notification to moderators on remote video abuse', async function () { | 86 | it('Should send a notification to moderators on remote video abuse', async function () { |
87 | this.timeout(20000) | 87 | this.timeout(50000) |
88 | 88 | ||
89 | const name = 'video for abuse ' + buildUUID() | 89 | const name = 'video for abuse ' + buildUUID() |
90 | const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } }) | 90 | const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } }) |
@@ -99,7 +99,7 @@ describe('Test moderation notifications', function () { | |||
99 | }) | 99 | }) |
100 | 100 | ||
101 | it('Should send a notification to moderators on local comment abuse', async function () { | 101 | it('Should send a notification to moderators on local comment abuse', async function () { |
102 | this.timeout(20000) | 102 | this.timeout(50000) |
103 | 103 | ||
104 | const name = 'video for abuse ' + buildUUID() | 104 | const name = 'video for abuse ' + buildUUID() |
105 | const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } }) | 105 | const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } }) |
@@ -118,7 +118,7 @@ describe('Test moderation notifications', function () { | |||
118 | }) | 118 | }) |
119 | 119 | ||
120 | it('Should send a notification to moderators on remote comment abuse', async function () { | 120 | it('Should send a notification to moderators on remote comment abuse', async function () { |
121 | this.timeout(20000) | 121 | this.timeout(50000) |
122 | 122 | ||
123 | const name = 'video for abuse ' + buildUUID() | 123 | const name = 'video for abuse ' + buildUUID() |
124 | const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } }) | 124 | const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } }) |
@@ -140,7 +140,7 @@ describe('Test moderation notifications', function () { | |||
140 | }) | 140 | }) |
141 | 141 | ||
142 | it('Should send a notification to moderators on local account abuse', async function () { | 142 | it('Should send a notification to moderators on local account abuse', async function () { |
143 | this.timeout(20000) | 143 | this.timeout(50000) |
144 | 144 | ||
145 | const username = 'user' + new Date().getTime() | 145 | const username = 'user' + new Date().getTime() |
146 | const { account } = await servers[0].users.create({ username, password: 'donald' }) | 146 | const { account } = await servers[0].users.create({ username, password: 'donald' }) |
@@ -153,7 +153,7 @@ describe('Test moderation notifications', function () { | |||
153 | }) | 153 | }) |
154 | 154 | ||
155 | it('Should send a notification to moderators on remote account abuse', async function () { | 155 | it('Should send a notification to moderators on remote account abuse', async function () { |
156 | this.timeout(20000) | 156 | this.timeout(50000) |
157 | 157 | ||
158 | const username = 'user' + new Date().getTime() | 158 | const username = 'user' + new Date().getTime() |
159 | const tmpToken = await servers[0].users.generateUserAndToken(username) | 159 | const tmpToken = await servers[0].users.generateUserAndToken(username) |
@@ -512,10 +512,14 @@ describe('Test moderation notifications', function () { | |||
512 | }) | 512 | }) |
513 | 513 | ||
514 | it('Should not send video publish notification if auto-blacklisted', async function () { | 514 | it('Should not send video publish notification if auto-blacklisted', async function () { |
515 | this.timeout(120000) | ||
516 | |||
515 | await checkVideoIsPublished({ ...userBaseParams, videoName, shortUUID, checkType: 'absence' }) | 517 | await checkVideoIsPublished({ ...userBaseParams, videoName, shortUUID, checkType: 'absence' }) |
516 | }) | 518 | }) |
517 | 519 | ||
518 | it('Should not send a local user subscription notification if auto-blacklisted', async function () { | 520 | it('Should not send a local user subscription notification if auto-blacklisted', async function () { |
521 | this.timeout(120000) | ||
522 | |||
519 | await checkNewVideoFromSubscription({ ...adminBaseParamsServer1, videoName, shortUUID, checkType: 'absence' }) | 523 | await checkNewVideoFromSubscription({ ...adminBaseParamsServer1, videoName, shortUUID, checkType: 'absence' }) |
520 | }) | 524 | }) |
521 | 525 | ||
@@ -524,7 +528,7 @@ describe('Test moderation notifications', function () { | |||
524 | }) | 528 | }) |
525 | 529 | ||
526 | it('Should send video published and unblacklist after video unblacklisted', async function () { | 530 | it('Should send video published and unblacklist after video unblacklisted', async function () { |
527 | this.timeout(40000) | 531 | this.timeout(120000) |
528 | 532 | ||
529 | await servers[0].blacklist.remove({ videoId: uuid }) | 533 | await servers[0].blacklist.remove({ videoId: uuid }) |
530 | 534 | ||
@@ -537,10 +541,14 @@ describe('Test moderation notifications', function () { | |||
537 | }) | 541 | }) |
538 | 542 | ||
539 | it('Should send a local user subscription notification after removed from blacklist', async function () { | 543 | it('Should send a local user subscription notification after removed from blacklist', async function () { |
544 | this.timeout(120000) | ||
545 | |||
540 | await checkNewVideoFromSubscription({ ...adminBaseParamsServer1, videoName, shortUUID, checkType: 'presence' }) | 546 | await checkNewVideoFromSubscription({ ...adminBaseParamsServer1, videoName, shortUUID, checkType: 'presence' }) |
541 | }) | 547 | }) |
542 | 548 | ||
543 | it('Should send a remote user subscription notification after removed from blacklist', async function () { | 549 | it('Should send a remote user subscription notification after removed from blacklist', async function () { |
550 | this.timeout(120000) | ||
551 | |||
544 | await checkNewVideoFromSubscription({ ...adminBaseParamsServer2, videoName, shortUUID, checkType: 'presence' }) | 552 | await checkNewVideoFromSubscription({ ...adminBaseParamsServer2, videoName, shortUUID, checkType: 'presence' }) |
545 | }) | 553 | }) |
546 | 554 | ||
@@ -576,7 +584,7 @@ describe('Test moderation notifications', function () { | |||
576 | }) | 584 | }) |
577 | 585 | ||
578 | it('Should not send publish/subscription notifications after scheduled update if video still auto-blacklisted', async function () { | 586 | it('Should not send publish/subscription notifications after scheduled update if video still auto-blacklisted', async function () { |
579 | this.timeout(40000) | 587 | this.timeout(120000) |
580 | 588 | ||
581 | // In 2 seconds | 589 | // In 2 seconds |
582 | const updateAt = new Date(new Date().getTime() + 2000) | 590 | const updateAt = new Date(new Date().getTime() + 2000) |
diff --git a/server/tests/api/object-storage/video-static-file-privacy.ts b/server/tests/api/object-storage/video-static-file-privacy.ts index 71ad35a43..869d437d5 100644 --- a/server/tests/api/object-storage/video-static-file-privacy.ts +++ b/server/tests/api/object-storage/video-static-file-privacy.ts | |||
@@ -120,7 +120,7 @@ describe('Object storage for video static file privacy', function () { | |||
120 | // --------------------------------------------------------------------------- | 120 | // --------------------------------------------------------------------------- |
121 | 121 | ||
122 | it('Should upload a private video and have appropriate object storage ACL', async function () { | 122 | it('Should upload a private video and have appropriate object storage ACL', async function () { |
123 | this.timeout(60000) | 123 | this.timeout(120000) |
124 | 124 | ||
125 | { | 125 | { |
126 | const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE }) | 126 | const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE }) |
@@ -138,7 +138,7 @@ describe('Object storage for video static file privacy', function () { | |||
138 | }) | 138 | }) |
139 | 139 | ||
140 | it('Should upload a public video and have appropriate object storage ACL', async function () { | 140 | it('Should upload a public video and have appropriate object storage ACL', async function () { |
141 | this.timeout(60000) | 141 | this.timeout(120000) |
142 | 142 | ||
143 | const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.UNLISTED }) | 143 | const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.UNLISTED }) |
144 | await waitJobs([ server ]) | 144 | await waitJobs([ server ]) |
diff --git a/server/tests/api/videos/video-channel-syncs.ts b/server/tests/api/videos/video-channel-syncs.ts index 91291524d..dd483f95e 100644 --- a/server/tests/api/videos/video-channel-syncs.ts +++ b/server/tests/api/videos/video-channel-syncs.ts | |||
@@ -307,6 +307,7 @@ describe('Test channel synchronizations', function () { | |||
307 | }) | 307 | }) |
308 | } | 308 | } |
309 | 309 | ||
310 | runSuite('youtube-dl') | 310 | // FIXME: suite is broken with youtube-dl |
311 | // runSuite('youtube-dl') | ||
311 | runSuite('yt-dlp') | 312 | runSuite('yt-dlp') |
312 | }) | 313 | }) |
diff --git a/server/tests/feeds/feeds.ts b/server/tests/feeds/feeds.ts index 906dab1a3..7345f728a 100644 --- a/server/tests/feeds/feeds.ts +++ b/server/tests/feeds/feeds.ts | |||
@@ -189,7 +189,7 @@ describe('Test syndication feeds', () => { | |||
189 | const jsonObj = JSON.parse(json) | 189 | const jsonObj = JSON.parse(json) |
190 | expect(jsonObj.items.length).to.be.equal(1) | 190 | expect(jsonObj.items.length).to.be.equal(1) |
191 | expect(jsonObj.items[0].title).to.equal('my super name for server 1') | 191 | expect(jsonObj.items[0].title).to.equal('my super name for server 1') |
192 | expect(jsonObj.items[0].author.name).to.equal('root') | 192 | expect(jsonObj.items[0].author.name).to.equal('Main root channel') |
193 | } | 193 | } |
194 | 194 | ||
195 | { | 195 | { |
@@ -197,7 +197,7 @@ describe('Test syndication feeds', () => { | |||
197 | const jsonObj = JSON.parse(json) | 197 | const jsonObj = JSON.parse(json) |
198 | expect(jsonObj.items.length).to.be.equal(1) | 198 | expect(jsonObj.items.length).to.be.equal(1) |
199 | expect(jsonObj.items[0].title).to.equal('user video') | 199 | expect(jsonObj.items[0].title).to.equal('user video') |
200 | expect(jsonObj.items[0].author.name).to.equal('john') | 200 | expect(jsonObj.items[0].author.name).to.equal('Main john channel') |
201 | } | 201 | } |
202 | 202 | ||
203 | for (const server of servers) { | 203 | for (const server of servers) { |
@@ -223,7 +223,7 @@ describe('Test syndication feeds', () => { | |||
223 | const jsonObj = JSON.parse(json) | 223 | const jsonObj = JSON.parse(json) |
224 | expect(jsonObj.items.length).to.be.equal(1) | 224 | expect(jsonObj.items.length).to.be.equal(1) |
225 | expect(jsonObj.items[0].title).to.equal('my super name for server 1') | 225 | expect(jsonObj.items[0].title).to.equal('my super name for server 1') |
226 | expect(jsonObj.items[0].author.name).to.equal('root') | 226 | expect(jsonObj.items[0].author.name).to.equal('Main root channel') |
227 | } | 227 | } |
228 | 228 | ||
229 | { | 229 | { |
@@ -231,7 +231,7 @@ describe('Test syndication feeds', () => { | |||
231 | const jsonObj = JSON.parse(json) | 231 | const jsonObj = JSON.parse(json) |
232 | expect(jsonObj.items.length).to.be.equal(1) | 232 | expect(jsonObj.items.length).to.be.equal(1) |
233 | expect(jsonObj.items[0].title).to.equal('user video') | 233 | expect(jsonObj.items[0].title).to.equal('user video') |
234 | expect(jsonObj.items[0].author.name).to.equal('john') | 234 | expect(jsonObj.items[0].author.name).to.equal('Main john channel') |
235 | } | 235 | } |
236 | 236 | ||
237 | for (const server of servers) { | 237 | for (const server of servers) { |
diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js index 19dccf26e..19ba9f278 100644 --- a/server/tests/fixtures/peertube-plugin-test/main.js +++ b/server/tests/fixtures/peertube-plugin-test/main.js | |||
@@ -250,7 +250,10 @@ async function register ({ registerHook, registerSetting, settingsManager, stora | |||
250 | 250 | ||
251 | registerHook({ | 251 | registerHook({ |
252 | target: 'filter:api.download.video.allowed.result', | 252 | target: 'filter:api.download.video.allowed.result', |
253 | handler: (result, params) => { | 253 | handler: async (result, params) => { |
254 | const loggedInUser = await peertubeHelpers.user.getAuthUser(params.res) | ||
255 | if (loggedInUser) return { allowed: true } | ||
256 | |||
254 | if (params && !params.streamingPlaylist && params.video.name.includes('bad file')) { | 257 | if (params && !params.streamingPlaylist && params.video.name.includes('bad file')) { |
255 | return { allowed: false, errorMessage: 'Cao Cao' } | 258 | return { allowed: false, errorMessage: 'Cao Cao' } |
256 | } | 259 | } |
diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts index 083fd43ca..6724b3bf8 100644 --- a/server/tests/plugins/filter-hooks.ts +++ b/server/tests/plugins/filter-hooks.ts | |||
@@ -430,6 +430,7 @@ describe('Test plugin filter hooks', function () { | |||
430 | 430 | ||
431 | describe('Download hooks', function () { | 431 | describe('Download hooks', function () { |
432 | const downloadVideos: VideoDetails[] = [] | 432 | const downloadVideos: VideoDetails[] = [] |
433 | let downloadVideo2Token: string | ||
433 | 434 | ||
434 | before(async function () { | 435 | before(async function () { |
435 | this.timeout(120000) | 436 | this.timeout(120000) |
@@ -459,6 +460,8 @@ describe('Test plugin filter hooks', function () { | |||
459 | for (const uuid of uuids) { | 460 | for (const uuid of uuids) { |
460 | downloadVideos.push(await servers[0].videos.get({ id: uuid })) | 461 | downloadVideos.push(await servers[0].videos.get({ id: uuid })) |
461 | } | 462 | } |
463 | |||
464 | downloadVideo2Token = await servers[0].videoToken.getVideoFileToken({ videoId: downloadVideos[2].uuid }) | ||
462 | }) | 465 | }) |
463 | 466 | ||
464 | it('Should run filter:api.download.torrent.allowed.result', async function () { | 467 | it('Should run filter:api.download.torrent.allowed.result', async function () { |
@@ -471,32 +474,42 @@ describe('Test plugin filter hooks', function () { | |||
471 | 474 | ||
472 | it('Should run filter:api.download.video.allowed.result', async function () { | 475 | it('Should run filter:api.download.video.allowed.result', async function () { |
473 | { | 476 | { |
474 | const res = await makeRawRequest({ url: downloadVideos[1].files[0].fileDownloadUrl, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | 477 | const refused = downloadVideos[1].files[0].fileDownloadUrl |
478 | const allowed = [ | ||
479 | downloadVideos[0].files[0].fileDownloadUrl, | ||
480 | downloadVideos[2].files[0].fileDownloadUrl | ||
481 | ] | ||
482 | |||
483 | const res = await makeRawRequest({ url: refused, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
475 | expect(res.body.error).to.equal('Cao Cao') | 484 | expect(res.body.error).to.equal('Cao Cao') |
476 | 485 | ||
477 | await makeRawRequest({ url: downloadVideos[0].files[0].fileDownloadUrl, expectedStatus: HttpStatusCode.OK_200 }) | 486 | for (const url of allowed) { |
478 | await makeRawRequest({ url: downloadVideos[2].files[0].fileDownloadUrl, expectedStatus: HttpStatusCode.OK_200 }) | 487 | await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 }) |
488 | await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 }) | ||
489 | } | ||
479 | } | 490 | } |
480 | 491 | ||
481 | { | 492 | { |
482 | const res = await makeRawRequest({ | 493 | const refused = downloadVideos[2].streamingPlaylists[0].files[0].fileDownloadUrl |
483 | url: downloadVideos[2].streamingPlaylists[0].files[0].fileDownloadUrl, | ||
484 | expectedStatus: HttpStatusCode.FORBIDDEN_403 | ||
485 | }) | ||
486 | 494 | ||
487 | expect(res.body.error).to.equal('Sun Jian') | 495 | const allowed = [ |
496 | downloadVideos[2].files[0].fileDownloadUrl, | ||
497 | downloadVideos[0].streamingPlaylists[0].files[0].fileDownloadUrl, | ||
498 | downloadVideos[1].streamingPlaylists[0].files[0].fileDownloadUrl | ||
499 | ] | ||
488 | 500 | ||
489 | await makeRawRequest({ url: downloadVideos[2].files[0].fileDownloadUrl, expectedStatus: HttpStatusCode.OK_200 }) | 501 | // Only streaming playlist is refuse |
502 | const res = await makeRawRequest({ url: refused, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | ||
503 | expect(res.body.error).to.equal('Sun Jian') | ||
490 | 504 | ||
491 | await makeRawRequest({ | 505 | // But not we there is a user in res |
492 | url: downloadVideos[0].streamingPlaylists[0].files[0].fileDownloadUrl, | 506 | await makeRawRequest({ url: refused, token: servers[0].accessToken, expectedStatus: HttpStatusCode.OK_200 }) |
493 | expectedStatus: HttpStatusCode.OK_200 | 507 | await makeRawRequest({ url: refused, query: { videoFileToken: downloadVideo2Token }, expectedStatus: HttpStatusCode.OK_200 }) |
494 | }) | ||
495 | 508 | ||
496 | await makeRawRequest({ | 509 | // Other files work |
497 | url: downloadVideos[1].streamingPlaylists[0].files[0].fileDownloadUrl, | 510 | for (const url of allowed) { |
498 | expectedStatus: HttpStatusCode.OK_200 | 511 | await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 }) |
499 | }) | 512 | } |
500 | } | 513 | } |
501 | }) | 514 | }) |
502 | }) | 515 | }) |
diff --git a/server/types/express.d.ts b/server/types/express.d.ts index 3738ffc47..99244d2a0 100644 --- a/server/types/express.d.ts +++ b/server/types/express.d.ts | |||
@@ -10,6 +10,7 @@ import { | |||
10 | MChannelBannerAccountDefault, | 10 | MChannelBannerAccountDefault, |
11 | MChannelSyncChannel, | 11 | MChannelSyncChannel, |
12 | MStreamingPlaylist, | 12 | MStreamingPlaylist, |
13 | MUserAccountUrl, | ||
13 | MVideoChangeOwnershipFull, | 14 | MVideoChangeOwnershipFull, |
14 | MVideoFile, | 15 | MVideoFile, |
15 | MVideoFormattableDetails, | 16 | MVideoFormattableDetails, |
@@ -187,6 +188,10 @@ declare module 'express' { | |||
187 | actor: MActorAccountChannelId | 188 | actor: MActorAccountChannelId |
188 | } | 189 | } |
189 | 190 | ||
191 | videoFileToken?: { | ||
192 | user: MUserAccountUrl | ||
193 | } | ||
194 | |||
190 | authenticated?: boolean | 195 | authenticated?: boolean |
191 | 196 | ||
192 | registeredPlugin?: RegisteredPlugin | 197 | registeredPlugin?: RegisteredPlugin |
diff --git a/support/doc/production.md b/support/doc/production.md index dd57e9120..9a84f19a3 100644 --- a/support/doc/production.md +++ b/support/doc/production.md | |||
@@ -177,16 +177,17 @@ $ sudo vim /etc/letsencrypt/renewal/your-domain.com.conf | |||
177 | 177 | ||
178 | If you plan to have many concurrent viewers on your PeerTube instance, consider increasing `worker_connections` value: https://nginx.org/en/docs/ngx_core_module.html#worker_connections. | 178 | If you plan to have many concurrent viewers on your PeerTube instance, consider increasing `worker_connections` value: https://nginx.org/en/docs/ngx_core_module.html#worker_connections. |
179 | 179 | ||
180 | **FreeBSD** | 180 | <details> |
181 | <summary><strong>If using FreeBSD</strong></summary> | ||
182 | |||
181 | On FreeBSD you can use [Dehydrated](https://dehydrated.io/) `security/dehydrated` for [Let's Encrypt](https://letsencrypt.org/) | 183 | On FreeBSD you can use [Dehydrated](https://dehydrated.io/) `security/dehydrated` for [Let's Encrypt](https://letsencrypt.org/) |
182 | 184 | ||
183 | ```bash | 185 | ```bash |
184 | $ sudo pkg install dehydrated | 186 | $ sudo pkg install dehydrated |
185 | ``` | 187 | ``` |
188 | </details> | ||
186 | 189 | ||
187 | ### :alembic: TCP/IP Tuning | 190 | ### :alembic: Linux TCP/IP Tuning |
188 | |||
189 | **On Linux** | ||
190 | 191 | ||
191 | ```bash | 192 | ```bash |
192 | $ sudo cp /var/www/peertube/peertube-latest/support/sysctl.d/30-peertube-tcp.conf /etc/sysctl.d/ | 193 | $ sudo cp /var/www/peertube/peertube-latest/support/sysctl.d/30-peertube-tcp.conf /etc/sysctl.d/ |
@@ -231,7 +232,9 @@ $ sudo systemctl start peertube | |||
231 | $ sudo journalctl -feu peertube | 232 | $ sudo journalctl -feu peertube |
232 | ``` | 233 | ``` |
233 | 234 | ||
234 | **FreeBSD** | 235 | <details> |
236 | <summary><strong>If using FreeBSD</strong></summary> | ||
237 | |||
235 | On FreeBSD, copy the startup script and update rc.conf: | 238 | On FreeBSD, copy the startup script and update rc.conf: |
236 | 239 | ||
237 | ```bash | 240 | ```bash |
@@ -244,8 +247,10 @@ Run: | |||
244 | ```bash | 247 | ```bash |
245 | $ sudo service peertube start | 248 | $ sudo service peertube start |
246 | ``` | 249 | ``` |
250 | </details> | ||
247 | 251 | ||
248 | ### :bricks: OpenRC | 252 | <details> |
253 | <summary><strong>If using OpenRC</strong></summary> | ||
249 | 254 | ||
250 | If your OS uses OpenRC, copy the service script: | 255 | If your OS uses OpenRC, copy the service script: |
251 | 256 | ||
@@ -265,6 +270,7 @@ Run and print last logs: | |||
265 | $ sudo /etc/init.d/peertube start | 270 | $ sudo /etc/init.d/peertube start |
266 | $ tail -f /var/log/peertube/peertube.log | 271 | $ tail -f /var/log/peertube/peertube.log |
267 | ``` | 272 | ``` |
273 | </details> | ||
268 | 274 | ||
269 | ### :technologist: Administrator | 275 | ### :technologist: Administrator |
270 | 276 | ||
@@ -291,16 +297,15 @@ Now your instance is up you can: | |||
291 | 297 | ||
292 | **Check the changelog (in particular the *IMPORTANT NOTES* section):** https://github.com/Chocobozzz/PeerTube/blob/develop/CHANGELOG.md | 298 | **Check the changelog (in particular the *IMPORTANT NOTES* section):** https://github.com/Chocobozzz/PeerTube/blob/develop/CHANGELOG.md |
293 | 299 | ||
294 | #### Auto | 300 | Run the upgrade script (the password it asks is PeerTube's database user password): |
295 | |||
296 | The password it asks is PeerTube's database user password. | ||
297 | 301 | ||
298 | ```bash | 302 | ```bash |
299 | $ cd /var/www/peertube/peertube-latest/scripts && sudo -H -u peertube ./upgrade.sh | 303 | $ cd /var/www/peertube/peertube-latest/scripts && sudo -H -u peertube ./upgrade.sh |
300 | $ sudo systemctl restart peertube # Or use your OS command to restart PeerTube if you don't use systemd | 304 | $ sudo systemctl restart peertube # Or use your OS command to restart PeerTube if you don't use systemd |
301 | ``` | 305 | ``` |
302 | 306 | ||
303 | #### Manually | 307 | <details> |
308 | <summary><strong>Prefer manual upgrade?</strong></summary> | ||
304 | 309 | ||
305 | Make a SQL backup | 310 | Make a SQL backup |
306 | 311 | ||
@@ -346,17 +351,18 @@ $ cd /var/www/peertube && \ | |||
346 | sudo unlink ./peertube-latest && \ | 351 | sudo unlink ./peertube-latest && \ |
347 | sudo -u peertube ln -s versions/peertube-${VERSION} ./peertube-latest | 352 | sudo -u peertube ln -s versions/peertube-${VERSION} ./peertube-latest |
348 | ``` | 353 | ``` |
354 | </details> | ||
349 | 355 | ||
350 | ### Configuration | 356 | ### Update PeerTube configuration |
351 | 357 | ||
352 | You can check for configuration changes, and report them in your `config/production.yaml` file: | 358 | Check for configuration changes, and report them in your `config/production.yaml` file: |
353 | 359 | ||
354 | ```bash | 360 | ```bash |
355 | $ cd /var/www/peertube/versions | 361 | $ cd /var/www/peertube/versions |
356 | $ diff -u "$(ls --sort=t | head -2 | tail -1)/config/production.yaml.example" "$(ls --sort=t | head -1)/config/production.yaml.example" | 362 | $ diff -u "$(ls --sort=t | head -2 | tail -1)/config/production.yaml.example" "$(ls --sort=t | head -1)/config/production.yaml.example" |
357 | ``` | 363 | ``` |
358 | 364 | ||
359 | ### nginx | 365 | ### Update nginx configuration |
360 | 366 | ||
361 | Check changes in nginx configuration: | 367 | Check changes in nginx configuration: |
362 | 368 | ||
@@ -365,7 +371,7 @@ $ cd /var/www/peertube/versions | |||
365 | $ diff -u "$(ls --sort=t | head -2 | tail -1)/support/nginx/peertube" "$(ls --sort=t | head -1)/support/nginx/peertube" | 371 | $ diff -u "$(ls --sort=t | head -2 | tail -1)/support/nginx/peertube" "$(ls --sort=t | head -1)/support/nginx/peertube" |
366 | ``` | 372 | ``` |
367 | 373 | ||
368 | ### systemd | 374 | ### Update systemd service |
369 | 375 | ||
370 | Check changes in systemd configuration: | 376 | Check changes in systemd configuration: |
371 | 377 | ||