aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md13
-rw-r--r--client/package.json2
-rw-r--r--client/src/app/+videos/+video-edit/video-add.component.html2
-rw-r--r--client/src/app/+videos/+video-edit/video-add.component.ts2
-rw-r--r--client/src/app/core/routing/custom-reuse-strategy.ts12
-rw-r--r--client/src/app/core/users/user.model.ts5
-rw-r--r--client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts3
-rw-r--r--client/src/app/shared/shared-video-miniature/videos-list.component.html3
-rw-r--r--client/src/app/shared/shared-video-miniature/videos-selection.component.html3
-rw-r--r--client/src/root-helpers/peertube-web-storage.ts13
-rw-r--r--client/src/standalone/videos/test-embed.ts2
-rw-r--r--package.json2
-rw-r--r--server/controllers/api/videos/import.ts12
-rw-r--r--server/controllers/client.ts10
-rw-r--r--server/controllers/static.ts1
-rw-r--r--server/helpers/regexp.ts1
-rw-r--r--server/initializers/migrations/0135-video-channel-actor.ts1
-rw-r--r--server/lib/video-state.ts6
-rw-r--r--server/middlewares/index.ts1
-rw-r--r--server/middlewares/robots.ts13
-rw-r--r--server/middlewares/validators/oembed.ts16
-rw-r--r--server/models/video/video-channel.ts2
-rw-r--r--server/models/video/video.ts4
-rw-r--r--server/tests/api/live/live.ts4
-rw-r--r--server/tests/api/search/search-channels.ts7
-rw-r--r--server/tests/api/server/services.ts66
-rw-r--r--server/tests/cli/create-transcoding-job.ts16
-rw-r--r--server/tests/client.ts10
-rw-r--r--shared/extra-utils/miscs/sql-command.ts1
-rw-r--r--support/doc/dependencies.md8
30 files changed, 174 insertions, 67 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 83a1f2549..fec8926f9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
1# Changelog 1# Changelog
2 2
3## v3.4.1
4
5### Bug fixes
6
7 * Fix broken PeerTube when cookies are disabled or if the embed iframe does not have appropriate options
8 * Fix search by channel's handle with an handle containing the local host
9 * Don't display autoblock message in upload page it is not enabled by the admin
10 * Don't index `/about/peertube` page
11 * Correctly handle OEmbed with an URL containing query parameters
12 * More robust youtube-dl thumbnail import
13 * Don't send a new video notification when using create transcoding CLI script
14
15
3## v3.4.0 16## v3.4.0
4 17
5### IMPORTANT NOTES 18### IMPORTANT NOTES
diff --git a/client/package.json b/client/package.json
index 7d3edd0a7..afa0efe13 100644
--- a/client/package.json
+++ b/client/package.json
@@ -1,6 +1,6 @@
1{ 1{
2 "name": "peertube-client", 2 "name": "peertube-client",
3 "version": "3.4.0", 3 "version": "3.4.1",
4 "private": true, 4 "private": true,
5 "license": "AGPL-3.0", 5 "license": "AGPL-3.0",
6 "author": { 6 "author": {
diff --git a/client/src/app/+videos/+video-edit/video-add.component.html b/client/src/app/+videos/+video-edit/video-add.component.html
index 7dd9ba357..29cf08e75 100644
--- a/client/src/app/+videos/+video-edit/video-add.component.html
+++ b/client/src/app/+videos/+video-edit/video-add.component.html
@@ -15,7 +15,7 @@
15</ng-container> 15</ng-container>
16 16
17<ng-container *ngIf="!user.isUploadDisabled()"> 17<ng-container *ngIf="!user.isUploadDisabled()">
18 <div *ngIf="user.isAutoBlocked()" class="upload-message auto-blocked alert alert-warning"> 18 <div *ngIf="user.isAutoBlocked(serverConfig)" class="upload-message auto-blocked alert alert-warning">
19 <div>{{ uploadMessages.autoBlock }}</div> 19 <div>{{ uploadMessages.autoBlock }}</div>
20 <ng-template [ngTemplateOutlet]="AlertButtons" *ngIf="!hasNoQuotaLeft && !hasNoQuotaLeftDaily"></ng-template> 20 <ng-template [ngTemplateOutlet]="AlertButtons" *ngIf="!hasNoQuotaLeft && !hasNoQuotaLeftDaily"></ng-template>
21 </div> 21 </div>
diff --git a/client/src/app/+videos/+video-edit/video-add.component.ts b/client/src/app/+videos/+video-edit/video-add.component.ts
index 46881be4e..bcb2fc4fa 100644
--- a/client/src/app/+videos/+video-edit/video-add.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add.component.ts
@@ -43,7 +43,7 @@ export class VideoAddComponent implements OnInit, CanComponentDeactivate {
43 hasNoQuotaLeft = false 43 hasNoQuotaLeft = false
44 hasNoQuotaLeftDaily = false 44 hasNoQuotaLeftDaily = false
45 45
46 private serverConfig: HTMLServerConfig 46 serverConfig: HTMLServerConfig
47 47
48 constructor ( 48 constructor (
49 private auth: AuthService, 49 private auth: AuthService,
diff --git a/client/src/app/core/routing/custom-reuse-strategy.ts b/client/src/app/core/routing/custom-reuse-strategy.ts
index 3000093a8..1498e221f 100644
--- a/client/src/app/core/routing/custom-reuse-strategy.ts
+++ b/client/src/app/core/routing/custom-reuse-strategy.ts
@@ -1,7 +1,7 @@
1import { Injectable } from '@angular/core' 1import { ComponentRef, Injectable } from '@angular/core'
2import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router' 2import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'
3import { RouterSetting } from './' 3import { DisableForReuseHook } from './disable-for-reuse-hook'
4import { PeerTubeRouterService } from './peertube-router.service' 4import { PeerTubeRouterService, RouterSetting } from './peertube-router.service'
5 5
6@Injectable() 6@Injectable()
7export class CustomReuseStrategy implements RouteReuseStrategy { 7export class CustomReuseStrategy implements RouteReuseStrategy {
@@ -22,9 +22,11 @@ export class CustomReuseStrategy implements RouteReuseStrategy {
22 const key = this.generateKey(route) 22 const key = this.generateKey(route)
23 this.recentlyUsed = key 23 this.recentlyUsed = key
24 24
25 console.log('Storing component %s to reuse later.', key); 25 console.log('Storing component %s to reuse later.', key)
26 26
27 (handle as any).componentRef.instance.disableForReuse() 27 const componentRef = (handle as any).componentRef as ComponentRef<DisableForReuseHook>
28 componentRef.instance.disableForReuse()
29 componentRef.changeDetectorRef.detectChanges()
28 30
29 this.storedRouteHandles.set(key, handle) 31 this.storedRouteHandles.set(key, handle)
30 32
diff --git a/client/src/app/core/users/user.model.ts b/client/src/app/core/users/user.model.ts
index 00078af5d..c0e5d3169 100644
--- a/client/src/app/core/users/user.model.ts
+++ b/client/src/app/core/users/user.model.ts
@@ -2,6 +2,7 @@ import { Account } from '@app/shared/shared-main/account/account.model'
2import { hasUserRight } from '@shared/core-utils/users' 2import { hasUserRight } from '@shared/core-utils/users'
3import { 3import {
4 ActorImage, 4 ActorImage,
5 HTMLServerConfig,
5 NSFWPolicyType, 6 NSFWPolicyType,
6 User as UserServerModel, 7 User as UserServerModel,
7 UserAdminFlag, 8 UserAdminFlag,
@@ -136,7 +137,9 @@ export class User implements UserServerModel {
136 return this.videoQuota === 0 || this.videoQuotaDaily === 0 137 return this.videoQuota === 0 || this.videoQuotaDaily === 0
137 } 138 }
138 139
139 isAutoBlocked () { 140 isAutoBlocked (serverConfig: HTMLServerConfig) {
141 if (serverConfig.autoBlacklist.videos.ofUsers.enabled !== true) return false
142
140 return this.role === UserRole.USER && this.adminFlags !== UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST 143 return this.role === UserRole.USER && this.adminFlags !== UserAdminFlag.BYPASS_VIDEO_AUTO_BLACKLIST
141 } 144 }
142} 145}
diff --git a/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts b/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts
index bebc6efa7..c247cfde2 100644
--- a/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts
+++ b/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts
@@ -13,6 +13,7 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy, AfterViewCh
13 13
14 // Add angular state in query params to reuse the routed component 14 // Add angular state in query params to reuse the routed component
15 @Input() setAngularState: boolean 15 @Input() setAngularState: boolean
16 @Input() parentDisabled = false
16 17
17 @Output() nearOfBottom = new EventEmitter<void>() 18 @Output() nearOfBottom = new EventEmitter<void>()
18 19
@@ -74,7 +75,7 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy, AfterViewCh
74 filter(({ current, maximumScroll }) => (current / maximumScroll) > this.decimalLimit) 75 filter(({ current, maximumScroll }) => (current / maximumScroll) > this.decimalLimit)
75 ) 76 )
76 .subscribe(() => { 77 .subscribe(() => {
77 if (this.setAngularState) this.setScrollRouteParams() 78 if (this.setAngularState && !this.parentDisabled) this.setScrollRouteParams()
78 79
79 this.nearOfBottom.emit() 80 this.nearOfBottom.emit()
80 }) 81 })
diff --git a/client/src/app/shared/shared-video-miniature/videos-list.component.html b/client/src/app/shared/shared-video-miniature/videos-list.component.html
index 67933f177..2b554517f 100644
--- a/client/src/app/shared/shared-video-miniature/videos-list.component.html
+++ b/client/src/app/shared/shared-video-miniature/videos-list.component.html
@@ -40,7 +40,8 @@
40 40
41 <div class="no-results" i18n *ngIf="hasDoneFirstQuery && videos.length === 0">No results.</div> 41 <div class="no-results" i18n *ngIf="hasDoneFirstQuery && videos.length === 0">No results.</div>
42 <div 42 <div
43 myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()" [setAngularState]="true" 43 myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()"
44 [setAngularState]="true" [parentDisabled]="disabled"
44 class="videos" [ngClass]="{ 'display-as-row': displayAsRow }" 45 class="videos" [ngClass]="{ 'display-as-row': displayAsRow }"
45 > 46 >
46 <ng-container *ngFor="let video of videos; trackBy: videoById;"> 47 <ng-container *ngFor="let video of videos; trackBy: videoById;">
diff --git a/client/src/app/shared/shared-video-miniature/videos-selection.component.html b/client/src/app/shared/shared-video-miniature/videos-selection.component.html
index f2af874dd..6ea2661e4 100644
--- a/client/src/app/shared/shared-video-miniature/videos-selection.component.html
+++ b/client/src/app/shared/shared-video-miniature/videos-selection.component.html
@@ -2,7 +2,8 @@
2 2
3<div 3<div
4 class="videos" 4 class="videos"
5 myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()" [setAngularState]="true" 5 myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()"
6 [parentDisabled]="disabled" [setAngularState]="true"
6> 7>
7 <div class="video" *ngFor="let video of videos; let i = index; trackBy: videoById"> 8 <div class="video" *ngFor="let video of videos; let i = index; trackBy: videoById">
8 9
diff --git a/client/src/root-helpers/peertube-web-storage.ts b/client/src/root-helpers/peertube-web-storage.ts
index 68a2462de..3622cdc44 100644
--- a/client/src/root-helpers/peertube-web-storage.ts
+++ b/client/src/root-helpers/peertube-web-storage.ts
@@ -6,19 +6,20 @@ function proxify (instance: MemoryStorage) {
6 return new Proxy(instance, { 6 return new Proxy(instance, {
7 set: function (obj, prop: string | symbol, value) { 7 set: function (obj, prop: string | symbol, value) {
8 if (Object.prototype.hasOwnProperty.call(MemoryStorage, prop)) { 8 if (Object.prototype.hasOwnProperty.call(MemoryStorage, prop)) {
9 // FIXME: symbol typing issue https://github.com/microsoft/TypeScript/issues/1863 9 // FIXME: remove cast on typescript upgrade
10 instance[prop as any] = value 10 instance[prop as any] = value
11 } else { 11 } else {
12 instance.setItem(prop, value) 12 instance.setItem(prop, value)
13 } 13 }
14
14 return true 15 return true
15 }, 16 },
16 get: function (target, name: string | symbol | number) { 17 get: function (target, name: string | symbol | number) {
17 if (Object.prototype.hasOwnProperty.call(MemoryStorage, name)) { 18 // FIXME: remove cast on typescript upgrade
18 // FIXME: symbol typing issue https://github.com/microsoft/TypeScript/issues/1863 19 if (typeof instance[name as any] === 'function') {
20 // FIXME: remove cast on typescript upgrade
19 return instance[name as any] 21 return instance[name as any]
20 } 22 } else if (valuesMap.has(name)) {
21 if (valuesMap.has(name)) {
22 return instance.getItem(name) 23 return instance.getItem(name)
23 } 24 }
24 } 25 }
@@ -83,7 +84,7 @@ try {
83} 84}
84 85
85// support Brave and other browsers using null rather than an exception 86// support Brave and other browsers using null rather than an exception
86if (peertubeLocalStorage === null || peertubeSessionStorage === null) { 87if (!peertubeLocalStorage || !peertubeSessionStorage) {
87 reinitStorage() 88 reinitStorage()
88} 89}
89 90
diff --git a/client/src/standalone/videos/test-embed.ts b/client/src/standalone/videos/test-embed.ts
index 7213cbf8b..18c338a2d 100644
--- a/client/src/standalone/videos/test-embed.ts
+++ b/client/src/standalone/videos/test-embed.ts
@@ -15,6 +15,8 @@ window.addEventListener('load', async () => {
15 ? `/video-playlists/embed/${elementId}?api=1` 15 ? `/video-playlists/embed/${elementId}?api=1`
16 : `/videos/embed/${elementId}?api=1` 16 : `/videos/embed/${elementId}?api=1`
17 17
18 iframe.sandbox.add('allow-same-origin', 'allow-scripts', 'allow-popups')
19
18 const mainElement = document.querySelector('#host') 20 const mainElement = document.querySelector('#host')
19 mainElement.appendChild(iframe) 21 mainElement.appendChild(iframe)
20 22
diff --git a/package.json b/package.json
index ee95c4f34..91b2be0da 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
1{ 1{
2 "name": "peertube", 2 "name": "peertube",
3 "description": "PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.", 3 "description": "PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.",
4 "version": "3.4.0", 4 "version": "3.4.1",
5 "private": true, 5 "private": true,
6 "licence": "AGPL-3.0", 6 "licence": "AGPL-3.0",
7 "engines": { 7 "engines": {
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts
index 5f90e4308..4265f3217 100644
--- a/server/controllers/api/videos/import.ts
+++ b/server/controllers/api/videos/import.ts
@@ -158,7 +158,11 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
158 158
159 // Process video thumbnail from url if processing from request.files failed 159 // Process video thumbnail from url if processing from request.files failed
160 if (!thumbnailModel && youtubeDLInfo.thumbnailUrl) { 160 if (!thumbnailModel && youtubeDLInfo.thumbnailUrl) {
161 thumbnailModel = await processThumbnailFromUrl(youtubeDLInfo.thumbnailUrl, video) 161 try {
162 thumbnailModel = await processThumbnailFromUrl(youtubeDLInfo.thumbnailUrl, video)
163 } catch (err) {
164 logger.warn('Cannot process thumbnail %s from youtubedl.', youtubeDLInfo.thumbnailUrl, { err })
165 }
162 } 166 }
163 167
164 // Process video preview from request.files 168 // Process video preview from request.files
@@ -166,7 +170,11 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
166 170
167 // Process video preview from url if processing from request.files failed 171 // Process video preview from url if processing from request.files failed
168 if (!previewModel && youtubeDLInfo.thumbnailUrl) { 172 if (!previewModel && youtubeDLInfo.thumbnailUrl) {
169 previewModel = await processPreviewFromUrl(youtubeDLInfo.thumbnailUrl, video) 173 try {
174 previewModel = await processPreviewFromUrl(youtubeDLInfo.thumbnailUrl, video)
175 } catch (err) {
176 logger.warn('Cannot process preview %s from youtubedl.', youtubeDLInfo.thumbnailUrl, { err })
177 }
170 } 178 }
171 179
172 const videoImport = await insertIntoDB({ 180 const videoImport = await insertIntoDB({
diff --git a/server/controllers/client.ts b/server/controllers/client.ts
index d81e35ec3..cdc556da2 100644
--- a/server/controllers/client.ts
+++ b/server/controllers/client.ts
@@ -5,12 +5,12 @@ import { join } from 'path'
5import { logger } from '@server/helpers/logger' 5import { logger } from '@server/helpers/logger'
6import { CONFIG } from '@server/initializers/config' 6import { CONFIG } from '@server/initializers/config'
7import { Hooks } from '@server/lib/plugins/hooks' 7import { Hooks } from '@server/lib/plugins/hooks'
8import { HttpStatusCode } from '@shared/models'
9import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '@shared/core-utils/i18n' 8import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '@shared/core-utils/i18n'
9import { HttpStatusCode } from '@shared/models'
10import { root } from '../helpers/core-utils' 10import { root } from '../helpers/core-utils'
11import { STATIC_MAX_AGE } from '../initializers/constants' 11import { STATIC_MAX_AGE } from '../initializers/constants'
12import { ClientHtml, sendHTML, serveIndexHTML } from '../lib/client-html' 12import { ClientHtml, sendHTML, serveIndexHTML } from '../lib/client-html'
13import { asyncMiddleware, embedCSP } from '../middlewares' 13import { asyncMiddleware, disableRobots, embedCSP } from '../middlewares'
14 14
15const clientsRouter = express.Router() 15const clientsRouter = express.Router()
16 16
@@ -81,6 +81,12 @@ clientsRouter.use('/client/*', (req: express.Request, res: express.Response) =>
81 res.status(HttpStatusCode.NOT_FOUND_404).end() 81 res.status(HttpStatusCode.NOT_FOUND_404).end()
82}) 82})
83 83
84// No index exceptions
85clientsRouter.all('/about/peertube',
86 disableRobots,
87 asyncMiddleware(serveIndexHTML)
88)
89
84// Always serve index client page (the client is a single page application, let it handle routing) 90// Always serve index client page (the client is a single page application, let it handle routing)
85// Try to provide the right language index.html 91// Try to provide the right language index.html
86clientsRouter.use('/(:language)?', asyncMiddleware(serveIndexHTML)) 92clientsRouter.use('/(:language)?', asyncMiddleware(serveIndexHTML))
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index 1c04e4b11..fe1629910 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -69,6 +69,7 @@ staticRouter.get('/robots.txt',
69 cacheRoute(ROUTE_CACHE_LIFETIME.ROBOTS), 69 cacheRoute(ROUTE_CACHE_LIFETIME.ROBOTS),
70 (_, res: express.Response) => { 70 (_, res: express.Response) => {
71 res.type('text/plain') 71 res.type('text/plain')
72
72 return res.send(CONFIG.INSTANCE.ROBOTS) 73 return res.send(CONFIG.INSTANCE.ROBOTS)
73 } 74 }
74) 75)
diff --git a/server/helpers/regexp.ts b/server/helpers/regexp.ts
index cfc2be488..257054cea 100644
--- a/server/helpers/regexp.ts
+++ b/server/helpers/regexp.ts
@@ -4,7 +4,6 @@ function regexpCapture (str: string, regex: RegExp, maxIterations = 100) {
4 let m: RegExpExecArray 4 let m: RegExpExecArray
5 let i = 0 5 let i = 0
6 6
7 // tslint:disable:no-conditional-assignment
8 while ((m = regex.exec(str)) !== null && i < maxIterations) { 7 while ((m = regex.exec(str)) !== null && i < maxIterations) {
9 // This is necessary to avoid infinite loops with zero-width matches 8 // This is necessary to avoid infinite loops with zero-width matches
10 if (m.index === regex.lastIndex) { 9 if (m.index === regex.lastIndex) {
diff --git a/server/initializers/migrations/0135-video-channel-actor.ts b/server/initializers/migrations/0135-video-channel-actor.ts
index 3f620dfa3..6989e1cbb 100644
--- a/server/initializers/migrations/0135-video-channel-actor.ts
+++ b/server/initializers/migrations/0135-video-channel-actor.ts
@@ -56,7 +56,6 @@ async function up (utils: {
56 } 56 }
57 57
58 { 58 {
59 // tslint:disable:no-trailing-whitespace
60 const query1 = 59 const query1 =
61 ` 60 `
62 INSERT INTO "actor" 61 INSERT INTO "actor"
diff --git a/server/lib/video-state.ts b/server/lib/video-state.ts
index 0613d94bf..9352a67d1 100644
--- a/server/lib/video-state.ts
+++ b/server/lib/video-state.ts
@@ -70,13 +70,13 @@ async function moveToPublishedState (video: MVideoFullLight, isNewVideo: boolean
70 logger.info('Publishing video %s.', video.uuid, { tags: [ video.uuid ] }) 70 logger.info('Publishing video %s.', video.uuid, { tags: [ video.uuid ] })
71 71
72 const previousState = video.state 72 const previousState = video.state
73 await video.setNewState(VideoState.PUBLISHED, transaction) 73 await video.setNewState(VideoState.PUBLISHED, isNewVideo, transaction)
74 74
75 // If the video was not published, we consider it is a new one for other instances 75 // If the video was not published, we consider it is a new one for other instances
76 // Live videos are always federated, so it's not a new video 76 // Live videos are always federated, so it's not a new video
77 await federateVideoIfNeeded(video, isNewVideo, transaction) 77 await federateVideoIfNeeded(video, isNewVideo, transaction)
78 78
79 Notifier.Instance.notifyOnNewVideoIfNeeded(video) 79 if (isNewVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(video)
80 80
81 if (previousState === VideoState.TO_TRANSCODE) { 81 if (previousState === VideoState.TO_TRANSCODE) {
82 Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(video) 82 Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(video)
@@ -90,7 +90,7 @@ async function moveToExternalStorageState (video: MVideoFullLight, isNewVideo: b
90 // We want to wait all transcoding jobs before moving the video on an external storage 90 // We want to wait all transcoding jobs before moving the video on an external storage
91 if (pendingTranscode !== 0) return 91 if (pendingTranscode !== 0) return
92 92
93 await video.setNewState(VideoState.TO_MOVE_TO_EXTERNAL_STORAGE, transaction) 93 await video.setNewState(VideoState.TO_MOVE_TO_EXTERNAL_STORAGE, isNewVideo, transaction)
94 94
95 logger.info('Creating external storage move job for video %s.', video.uuid, { tags: [ video.uuid ] }) 95 logger.info('Creating external storage move job for video %s.', video.uuid, { tags: [ video.uuid ] })
96 96
diff --git a/server/middlewares/index.ts b/server/middlewares/index.ts
index a0035f623..d2ed079b6 100644
--- a/server/middlewares/index.ts
+++ b/server/middlewares/index.ts
@@ -4,6 +4,7 @@ export * from './activitypub'
4export * from './async' 4export * from './async'
5export * from './auth' 5export * from './auth'
6export * from './pagination' 6export * from './pagination'
7export * from './robots'
7export * from './servers' 8export * from './servers'
8export * from './sort' 9export * from './sort'
9export * from './user-right' 10export * from './user-right'
diff --git a/server/middlewares/robots.ts b/server/middlewares/robots.ts
new file mode 100644
index 000000000..b22b24a9f
--- /dev/null
+++ b/server/middlewares/robots.ts
@@ -0,0 +1,13 @@
1import express from 'express'
2
3function disableRobots (req: express.Request, res: express.Response, next: express.NextFunction) {
4 res.setHeader('X-Robots-Tag', 'noindex')
5
6 return next()
7}
8
9// ---------------------------------------------------------------------------
10
11export {
12 disableRobots
13}
diff --git a/server/middlewares/validators/oembed.ts b/server/middlewares/validators/oembed.ts
index 5e47211b5..96c8adc99 100644
--- a/server/middlewares/validators/oembed.ts
+++ b/server/middlewares/validators/oembed.ts
@@ -62,12 +62,26 @@ const oembedValidator = [
62 62
63 const url = req.query.url as string 63 const url = req.query.url as string
64 64
65 let urlPath: string
66
67 try {
68 urlPath = new URL(url).pathname
69 } catch (err) {
70 return res.fail({
71 status: HttpStatusCode.BAD_REQUEST_400,
72 message: err.message,
73 data: {
74 url
75 }
76 })
77 }
78
65 const isPlaylist = startPlaylistURLs.some(u => url.startsWith(u)) 79 const isPlaylist = startPlaylistURLs.some(u => url.startsWith(u))
66 const isVideo = isPlaylist ? false : startVideoURLs.some(u => url.startsWith(u)) 80 const isVideo = isPlaylist ? false : startVideoURLs.some(u => url.startsWith(u))
67 81
68 const startIsOk = isVideo || isPlaylist 82 const startIsOk = isVideo || isPlaylist
69 83
70 const matches = watchRegex.exec(url) 84 const matches = watchRegex.exec(urlPath)
71 85
72 if (startIsOk === false || matches === null) { 86 if (startIsOk === false || matches === null) {
73 return res.fail({ 87 return res.fail({
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index c04bd4355..8ccb818b3 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -121,7 +121,7 @@ export type SummaryOptions = {
121 for (const handle of options.handles || []) { 121 for (const handle of options.handles || []) {
122 const [ preferredUsername, host ] = handle.split('@') 122 const [ preferredUsername, host ] = handle.split('@')
123 123
124 if (!host) { 124 if (!host || host === WEBSERVER.HOST) {
125 or.push({ 125 or.push({
126 '$Actor.preferredUsername$': preferredUsername, 126 '$Actor.preferredUsername$': preferredUsername,
127 '$Actor.serverId$': null 127 '$Actor.serverId$': null
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index e0c4dd2db..d2daf18ee 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1766,12 +1766,12 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1766 this.privacy === VideoPrivacy.INTERNAL 1766 this.privacy === VideoPrivacy.INTERNAL
1767 } 1767 }
1768 1768
1769 async setNewState (newState: VideoState, transaction: Transaction) { 1769 async setNewState (newState: VideoState, isNewVideo: boolean, transaction: Transaction) {
1770 if (this.state === newState) throw new Error('Cannot use same state ' + newState) 1770 if (this.state === newState) throw new Error('Cannot use same state ' + newState)
1771 1771
1772 this.state = newState 1772 this.state = newState
1773 1773
1774 if (this.state === VideoState.PUBLISHED) { 1774 if (this.state === VideoState.PUBLISHED && isNewVideo) {
1775 this.publishedAt = new Date() 1775 this.publishedAt = new Date()
1776 } 1776 }
1777 1777
diff --git a/server/tests/api/live/live.ts b/server/tests/api/live/live.ts
index ba952aff5..5cac3bc4e 100644
--- a/server/tests/api/live/live.ts
+++ b/server/tests/api/live/live.ts
@@ -439,7 +439,7 @@ describe('Test live', function () {
439 }) 439 })
440 440
441 it('Should enable transcoding without additional resolutions', async function () { 441 it('Should enable transcoding without additional resolutions', async function () {
442 this.timeout(60000) 442 this.timeout(120000)
443 443
444 liveVideoId = await createLiveWrapper(false) 444 liveVideoId = await createLiveWrapper(false)
445 445
@@ -453,7 +453,7 @@ describe('Test live', function () {
453 }) 453 })
454 454
455 it('Should enable transcoding with some resolutions', async function () { 455 it('Should enable transcoding with some resolutions', async function () {
456 this.timeout(60000) 456 this.timeout(120000)
457 457
458 const resolutions = [ 240, 480 ] 458 const resolutions = [ 240, 480 ]
459 await updateConf(resolutions) 459 await updateConf(resolutions)
diff --git a/server/tests/api/search/search-channels.ts b/server/tests/api/search/search-channels.ts
index 8a01aff90..67612537c 100644
--- a/server/tests/api/search/search-channels.ts
+++ b/server/tests/api/search/search-channels.ts
@@ -129,6 +129,13 @@ describe('Test channels search', function () {
129 } 129 }
130 130
131 { 131 {
132 const body = await command.advancedChannelSearch({ search: { handles: [ 'squall_channel@' + server.host ] } })
133 expect(body.total).to.equal(1)
134 expect(body.data).to.have.lengthOf(1)
135 expect(body.data[0].displayName).to.equal('Squall channel')
136 }
137
138 {
132 const body = await command.advancedChannelSearch({ search: { handles: [ 'chocobozzz_channel' ] } }) 139 const body = await command.advancedChannelSearch({ search: { handles: [ 'chocobozzz_channel' ] } })
133 expect(body.total).to.equal(0) 140 expect(body.total).to.equal(0)
134 expect(body.data).to.have.lengthOf(0) 141 expect(body.data).to.have.lengthOf(0)
diff --git a/server/tests/api/server/services.ts b/server/tests/api/server/services.ts
index 69d030dbb..3a87df981 100644
--- a/server/tests/api/server/services.ts
+++ b/server/tests/api/server/services.ts
@@ -52,42 +52,46 @@ describe('Test services', function () {
52 52
53 it('Should have a valid oEmbed video response', async function () { 53 it('Should have a valid oEmbed video response', async function () {
54 for (const basePath of [ '/videos/watch/', '/w/' ]) { 54 for (const basePath of [ '/videos/watch/', '/w/' ]) {
55 const oembedUrl = 'http://localhost:' + server.port + basePath + video.uuid 55 for (const suffix of [ '', '?param=1' ]) {
56 56 const oembedUrl = server.url + basePath + video.uuid + suffix
57 const res = await server.services.getOEmbed({ oembedUrl }) 57
58 const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' + 58 const res = await server.services.getOEmbed({ oembedUrl })
59 `title="${video.name}" src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` + 59 const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' +
60 'frameborder="0" allowfullscreen></iframe>' 60 `title="${video.name}" src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` +
61 const expectedThumbnailUrl = 'http://localhost:' + server.port + video.previewPath 61 'frameborder="0" allowfullscreen></iframe>'
62 62 const expectedThumbnailUrl = 'http://localhost:' + server.port + video.previewPath
63 expect(res.body.html).to.equal(expectedHtml) 63
64 expect(res.body.title).to.equal(video.name) 64 expect(res.body.html).to.equal(expectedHtml)
65 expect(res.body.author_name).to.equal(server.store.channel.displayName) 65 expect(res.body.title).to.equal(video.name)
66 expect(res.body.width).to.equal(560) 66 expect(res.body.author_name).to.equal(server.store.channel.displayName)
67 expect(res.body.height).to.equal(315) 67 expect(res.body.width).to.equal(560)
68 expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl) 68 expect(res.body.height).to.equal(315)
69 expect(res.body.thumbnail_width).to.equal(850) 69 expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl)
70 expect(res.body.thumbnail_height).to.equal(480) 70 expect(res.body.thumbnail_width).to.equal(850)
71 expect(res.body.thumbnail_height).to.equal(480)
72 }
71 } 73 }
72 }) 74 })
73 75
74 it('Should have a valid playlist oEmbed response', async function () { 76 it('Should have a valid playlist oEmbed response', async function () {
75 for (const basePath of [ '/videos/watch/playlist/', '/w/p/' ]) { 77 for (const basePath of [ '/videos/watch/playlist/', '/w/p/' ]) {
76 const oembedUrl = 'http://localhost:' + server.port + basePath + playlistUUID 78 for (const suffix of [ '', '?param=1' ]) {
77 79 const oembedUrl = server.url + basePath + playlistUUID + suffix
78 const res = await server.services.getOEmbed({ oembedUrl }) 80
79 const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' + 81 const res = await server.services.getOEmbed({ oembedUrl })
80 `title="${playlistDisplayName}" src="http://localhost:${server.port}/video-playlists/embed/${playlistUUID}" ` + 82 const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' +
81 'frameborder="0" allowfullscreen></iframe>' 83 `title="${playlistDisplayName}" src="http://localhost:${server.port}/video-playlists/embed/${playlistUUID}" ` +
82 84 'frameborder="0" allowfullscreen></iframe>'
83 expect(res.body.html).to.equal(expectedHtml) 85
84 expect(res.body.title).to.equal('The Life and Times of Scrooge McDuck') 86 expect(res.body.html).to.equal(expectedHtml)
85 expect(res.body.author_name).to.equal(server.store.channel.displayName) 87 expect(res.body.title).to.equal('The Life and Times of Scrooge McDuck')
86 expect(res.body.width).to.equal(560) 88 expect(res.body.author_name).to.equal(server.store.channel.displayName)
87 expect(res.body.height).to.equal(315) 89 expect(res.body.width).to.equal(560)
88 expect(res.body.thumbnail_url).exist 90 expect(res.body.height).to.equal(315)
89 expect(res.body.thumbnail_width).to.equal(280) 91 expect(res.body.thumbnail_url).exist
90 expect(res.body.thumbnail_height).to.equal(157) 92 expect(res.body.thumbnail_width).to.equal(280)
93 expect(res.body.thumbnail_height).to.equal(157)
94 }
91 } 95 }
92 }) 96 })
93 97
diff --git a/server/tests/cli/create-transcoding-job.ts b/server/tests/cli/create-transcoding-job.ts
index 3fd624091..2b388ab0c 100644
--- a/server/tests/cli/create-transcoding-job.ts
+++ b/server/tests/cli/create-transcoding-job.ts
@@ -33,9 +33,10 @@ async function checkFilesInObjectStorage (files: VideoFile[], type: 'webtorrent'
33function runTests (objectStorage: boolean) { 33function runTests (objectStorage: boolean) {
34 let servers: PeerTubeServer[] = [] 34 let servers: PeerTubeServer[] = []
35 const videosUUID: string[] = [] 35 const videosUUID: string[] = []
36 const publishedAt: string[] = []
36 37
37 before(async function () { 38 before(async function () {
38 this.timeout(60000) 39 this.timeout(120000)
39 40
40 const config = objectStorage 41 const config = objectStorage
41 ? ObjectStorageCommand.getDefaultConfig() 42 ? ObjectStorageCommand.getDefaultConfig()
@@ -54,6 +55,11 @@ function runTests (objectStorage: boolean) {
54 for (let i = 1; i <= 5; i++) { 55 for (let i = 1; i <= 5; i++) {
55 const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'video' + i } }) 56 const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'video' + i } })
56 57
58 await waitJobs(servers)
59
60 const video = await servers[0].videos.get({ id: uuid })
61 publishedAt.push(video.publishedAt as string)
62
57 if (i > 2) { 63 if (i > 2) {
58 videosUUID.push(uuid) 64 videosUUID.push(uuid)
59 } else { 65 } else {
@@ -225,6 +231,14 @@ function runTests (objectStorage: boolean) {
225 } 231 }
226 }) 232 })
227 233
234 it('Should not have updated published at attributes', async function () {
235 for (const id of videosUUID) {
236 const video = await servers[0].videos.get({ id })
237
238 expect(publishedAt.some(p => video.publishedAt === p)).to.be.true
239 }
240 })
241
228 after(async function () { 242 after(async function () {
229 await cleanupTests(servers) 243 await cleanupTests(servers)
230 }) 244 })
diff --git a/server/tests/client.ts b/server/tests/client.ts
index a91bec906..6c32c81db 100644
--- a/server/tests/client.ts
+++ b/server/tests/client.ts
@@ -482,6 +482,16 @@ describe('Test a client controllers', function () {
482 } 482 }
483 } 483 }
484 }) 484 })
485
486 it('Should add noindex header for some paths', async function () {
487 const paths = [ '/about/peertube' ]
488
489 for (const path of paths) {
490 const { headers } = await makeHTMLRequest(servers[0].url, path)
491
492 expect(headers['x-robots-tag']).to.equal('noindex')
493 }
494 })
485 }) 495 })
486 496
487 describe('Embed HTML', function () { 497 describe('Embed HTML', function () {
diff --git a/shared/extra-utils/miscs/sql-command.ts b/shared/extra-utils/miscs/sql-command.ts
index 80c8cd271..bedb3349b 100644
--- a/shared/extra-utils/miscs/sql-command.ts
+++ b/shared/extra-utils/miscs/sql-command.ts
@@ -50,7 +50,6 @@ export class SQLCommand extends AbstractCommand {
50 async countVideoViewsOf (uuid: string) { 50 async countVideoViewsOf (uuid: string) {
51 const seq = this.getSequelize() 51 const seq = this.getSequelize()
52 52
53 // tslint:disable
54 const query = 'SELECT SUM("videoView"."views") AS "total" FROM "videoView" ' + 53 const query = 'SELECT SUM("videoView"."views") AS "total" FROM "videoView" ' +
55 `INNER JOIN "video" ON "video"."id" = "videoView"."videoId" WHERE "video"."uuid" = '${uuid}'` 54 `INNER JOIN "video" ON "video"."id" = "videoView"."videoId" WHERE "video"."uuid" = '${uuid}'`
56 55
diff --git a/support/doc/dependencies.md b/support/doc/dependencies.md
index 8fe190320..7c3fcd55b 100644
--- a/support/doc/dependencies.md
+++ b/support/doc/dependencies.md
@@ -1,5 +1,7 @@
1# Dependencies 1# Dependencies
2 2
3:warning: **Warning**: dependencies guide is maintained by the community. Some parts may be outdated! :warning:
4
3Follow the below guides, and check their versions match [required external dependencies versions](https://github.com/Chocobozzz/PeerTube/blob/master/engines.yaml). You can check them automatically via `sudo npx engineslist`. 5Follow the below guides, and check their versions match [required external dependencies versions](https://github.com/Chocobozzz/PeerTube/blob/master/engines.yaml). You can check them automatically via `sudo npx engineslist`.
4 6
5_note_: only **LTS** versions of external dependencies are supported. If no LTS version matching the version constraint is available, only **release** versions are supported. 7_note_: only **LTS** versions of external dependencies are supported. If no LTS version matching the version constraint is available, only **release** versions are supported.
@@ -554,6 +556,12 @@ rc-service redis start
554rc-service postgresql-11 start 556rc-service postgresql-11 start
555``` 557```
556 558
5596. Create Python version symlink for youtube-dl:
560
561```
562sudo ln -s /usr/bin/python3 /usr/bin/python
563```
564
557## OpenBSD 565## OpenBSD
558 566
5591. Install Packages: 5671. Install Packages: