diff options
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 @@ | |||
1 | import { Injectable } from '@angular/core' | 1 | import { ComponentRef, Injectable } from '@angular/core' |
2 | import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router' | 2 | import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router' |
3 | import { RouterSetting } from './' | 3 | import { DisableForReuseHook } from './disable-for-reuse-hook' |
4 | import { PeerTubeRouterService } from './peertube-router.service' | 4 | import { PeerTubeRouterService, RouterSetting } from './peertube-router.service' |
5 | 5 | ||
6 | @Injectable() | 6 | @Injectable() |
7 | export class CustomReuseStrategy implements RouteReuseStrategy { | 7 | export 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' | |||
2 | import { hasUserRight } from '@shared/core-utils/users' | 2 | import { hasUserRight } from '@shared/core-utils/users' |
3 | import { | 3 | import { |
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 |
86 | if (peertubeLocalStorage === null || peertubeSessionStorage === null) { | 87 | if (!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' | |||
5 | import { logger } from '@server/helpers/logger' | 5 | import { logger } from '@server/helpers/logger' |
6 | import { CONFIG } from '@server/initializers/config' | 6 | import { CONFIG } from '@server/initializers/config' |
7 | import { Hooks } from '@server/lib/plugins/hooks' | 7 | import { Hooks } from '@server/lib/plugins/hooks' |
8 | import { HttpStatusCode } from '@shared/models' | ||
9 | import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '@shared/core-utils/i18n' | 8 | import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '@shared/core-utils/i18n' |
9 | import { HttpStatusCode } from '@shared/models' | ||
10 | import { root } from '../helpers/core-utils' | 10 | import { root } from '../helpers/core-utils' |
11 | import { STATIC_MAX_AGE } from '../initializers/constants' | 11 | import { STATIC_MAX_AGE } from '../initializers/constants' |
12 | import { ClientHtml, sendHTML, serveIndexHTML } from '../lib/client-html' | 12 | import { ClientHtml, sendHTML, serveIndexHTML } from '../lib/client-html' |
13 | import { asyncMiddleware, embedCSP } from '../middlewares' | 13 | import { asyncMiddleware, disableRobots, embedCSP } from '../middlewares' |
14 | 14 | ||
15 | const clientsRouter = express.Router() | 15 | const 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 | ||
85 | clientsRouter.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 |
86 | clientsRouter.use('/(:language)?', asyncMiddleware(serveIndexHTML)) | 92 | clientsRouter.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' | |||
4 | export * from './async' | 4 | export * from './async' |
5 | export * from './auth' | 5 | export * from './auth' |
6 | export * from './pagination' | 6 | export * from './pagination' |
7 | export * from './robots' | ||
7 | export * from './servers' | 8 | export * from './servers' |
8 | export * from './sort' | 9 | export * from './sort' |
9 | export * from './user-right' | 10 | export * 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 @@ | |||
1 | import express from 'express' | ||
2 | |||
3 | function 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 | |||
11 | export { | ||
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' | |||
33 | function runTests (objectStorage: boolean) { | 33 | function 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 | |||
3 | Follow 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`. | 5 | Follow 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 | |||
554 | rc-service postgresql-11 start | 556 | rc-service postgresql-11 start |
555 | ``` | 557 | ``` |
556 | 558 | ||
559 | 6. Create Python version symlink for youtube-dl: | ||
560 | |||
561 | ``` | ||
562 | sudo ln -s /usr/bin/python3 /usr/bin/python | ||
563 | ``` | ||
564 | |||
557 | ## OpenBSD | 565 | ## OpenBSD |
558 | 566 | ||
559 | 1. Install Packages: | 567 | 1. Install Packages: |