aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/e2e/src/po/my-account.ts2
-rw-r--r--client/e2e/src/po/video-upload.po.ts2
-rw-r--r--client/e2e/src/po/video-watch.po.ts6
-rw-r--r--client/e2e/src/videos.e2e-spec.ts2
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html2
-rw-r--r--client/src/app/+my-library/my-video-imports/my-video-imports.component.ts2
-rw-r--r--client/src/app/+page-not-found/page-not-found.component.ts2
-rw-r--r--client/src/app/+remote-interaction/remote-interaction.component.ts2
-rw-r--r--client/src/app/+search/video-lazy-load.resolver.ts2
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts2
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts2
-rw-r--r--client/src/app/+videos/+video-edit/video-update.component.html2
-rw-r--r--client/src/app/+videos/+video-edit/video-update.component.ts2
-rw-r--r--client/src/app/+videos/+video-watch/comment/video-comment.component.html4
-rw-r--r--client/src/app/+videos/+video-watch/video-watch-routing.module.ts2
-rw-r--r--client/src/app/+videos/+video-watch/video-watch.component.ts2
-rw-r--r--client/src/app/+videos/videos-routing.module.ts25
-rw-r--r--client/src/app/app-routing.module.ts65
-rw-r--r--client/src/app/shared/shared-main/users/user-notification.model.ts2
-rw-r--r--client/src/app/shared/shared-main/video/video.model.ts2
-rw-r--r--client/src/app/shared/shared-share-modal/video-share.component.ts4
-rw-r--r--client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts2
-rw-r--r--client/src/app/shared/shared-video-comment/video-comment.model.ts2
-rw-r--r--client/src/app/shared/shared-video-miniature/video-miniature.component.ts2
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts2
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.ts2
-rw-r--r--client/src/assets/player/utils.ts4
-rw-r--r--server/controllers/bots.ts2
-rw-r--r--server/controllers/client.ts4
-rw-r--r--server/controllers/feeds.ts2
-rw-r--r--server/middlewares/validators/oembed.ts24
-rw-r--r--server/models/video/video-playlist.ts2
-rw-r--r--server/models/video/video.ts2
-rw-r--r--server/tests/api/server/services.ts104
-rw-r--r--server/tests/client.ts196
-rw-r--r--server/tools/peertube-watch.ts2
36 files changed, 273 insertions, 215 deletions
diff --git a/client/e2e/src/po/my-account.ts b/client/e2e/src/po/my-account.ts
index 61d42214d..9866953e9 100644
--- a/client/e2e/src/po/my-account.ts
+++ b/client/e2e/src/po/my-account.ts
@@ -61,7 +61,7 @@ export class MyAccountPage {
61 61
62 async goOnAssociatedPlaylistEmbed () { 62 async goOnAssociatedPlaylistEmbed () {
63 let url = await browser.getCurrentUrl() 63 let url = await browser.getCurrentUrl()
64 url = url.replace('/videos/watch/playlist/', '/video-playlists/embed/') 64 url = url.replace('/w/p/', '/video-playlists/embed/')
65 url = url.replace(':3333', ':9001') 65 url = url.replace(':3333', ':9001')
66 66
67 return browser.get(url) 67 return browser.get(url)
diff --git a/client/e2e/src/po/video-upload.po.ts b/client/e2e/src/po/video-upload.po.ts
index ad2acee7f..a248912ed 100644
--- a/client/e2e/src/po/video-upload.po.ts
+++ b/client/e2e/src/po/video-upload.po.ts
@@ -41,7 +41,7 @@ export class VideoUploadPage {
41 41
42 await this.getSecondStepSubmitButton().click() 42 await this.getSecondStepSubmitButton().click()
43 43
44 return browser.wait(browser.ExpectedConditions.urlContains('/watch/')) 44 return browser.wait(browser.ExpectedConditions.urlContains('/w/'))
45 } 45 }
46 46
47 private getSecondStepSubmitButton () { 47 private getSecondStepSubmitButton () {
diff --git a/client/e2e/src/po/video-watch.po.ts b/client/e2e/src/po/video-watch.po.ts
index b41fe0882..6d91d241e 100644
--- a/client/e2e/src/po/video-watch.po.ts
+++ b/client/e2e/src/po/video-watch.po.ts
@@ -43,7 +43,7 @@ export class VideoWatchPage {
43 43
44 async goOnAssociatedEmbed () { 44 async goOnAssociatedEmbed () {
45 let url = await browser.getCurrentUrl() 45 let url = await browser.getCurrentUrl()
46 url = url.replace('/watch/', '/embed/') 46 url = url.replace('/w/', '/embed/')
47 url = url.replace(':3333', ':9001') 47 url = url.replace(':3333', ':9001')
48 48
49 return browser.get(url) 49 return browser.get(url)
@@ -65,7 +65,7 @@ export class VideoWatchPage {
65 await browser.wait(browser.ExpectedConditions.elementToBeClickable(video)) 65 await browser.wait(browser.ExpectedConditions.elementToBeClickable(video))
66 await video.click() 66 await video.click()
67 67
68 await browser.wait(browser.ExpectedConditions.urlContains('/watch/')) 68 await browser.wait(browser.ExpectedConditions.urlContains('/w/'))
69 } 69 }
70 70
71 async clickOnFirstVideo () { 71 async clickOnFirstVideo () {
@@ -78,7 +78,7 @@ export class VideoWatchPage {
78 const textToReturn = videoName.getText() 78 const textToReturn = videoName.getText()
79 await video.click() 79 await video.click()
80 80
81 await browser.wait(browser.ExpectedConditions.urlContains('/watch/')) 81 await browser.wait(browser.ExpectedConditions.urlContains('/w/'))
82 return textToReturn 82 return textToReturn
83 } 83 }
84 84
diff --git a/client/e2e/src/videos.e2e-spec.ts b/client/e2e/src/videos.e2e-spec.ts
index bcc810297..fc816d1bf 100644
--- a/client/e2e/src/videos.e2e-spec.ts
+++ b/client/e2e/src/videos.e2e-spec.ts
@@ -85,7 +85,7 @@ describe('Videos workflow', () => {
85 let videoNameToExcept = videoName 85 let videoNameToExcept = videoName
86 86
87 if (await isMobileDevice() || await isSafari()) { 87 if (await isMobileDevice() || await isSafari()) {
88 await browser.get('https://peertube2.cpy.re/videos/watch/122d093a-1ede-43bd-bd34-59d2931ffc5e') 88 await browser.get('https://peertube2.cpy.re/w/122d093a-1ede-43bd-bd34-59d2931ffc5e')
89 videoNameToExcept = 'E2E tests' 89 videoNameToExcept = 'E2E tests'
90 } else { 90 } else {
91 await videoWatchPage.clickOnVideo(videoName) 91 await videoWatchPage.clickOnVideo(videoName)
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 451e6a34a..03997ea40 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
@@ -489,7 +489,7 @@
489 <ng-container i18n> 489 <ng-container i18n>
490 If your instance is explicitly allowed by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br /> 490 If your instance is explicitly allowed by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br />
491 If the instance is not, we use an image link card that will redirect to your PeerTube instance.<br /><br /> 491 If the instance is not, we use an image link card that will redirect to your PeerTube instance.<br /><br />
492 Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on 492 Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/w/blabla) on
493 <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> 493 <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a>
494 to see if you instance is allowed. 494 to see if you instance is allowed.
495 </ng-container> 495 </ng-container>
diff --git a/client/src/app/+my-library/my-video-imports/my-video-imports.component.ts b/client/src/app/+my-library/my-video-imports/my-video-imports.component.ts
index 359535526..bb9d70524 100644
--- a/client/src/app/+my-library/my-video-imports/my-video-imports.component.ts
+++ b/client/src/app/+my-library/my-video-imports/my-video-imports.component.ts
@@ -55,7 +55,7 @@ export class MyVideoImportsComponent extends RestTable implements OnInit {
55 } 55 }
56 56
57 getVideoUrl (video: { uuid: string }) { 57 getVideoUrl (video: { uuid: string }) {
58 return '/videos/watch/' + video.uuid 58 return '/w/' + video.uuid
59 } 59 }
60 60
61 getEditVideoUrl (video: { uuid: string }) { 61 getEditVideoUrl (video: { uuid: string }) {
diff --git a/client/src/app/+page-not-found/page-not-found.component.ts b/client/src/app/+page-not-found/page-not-found.component.ts
index 695568898..639e5db78 100644
--- a/client/src/app/+page-not-found/page-not-found.component.ts
+++ b/client/src/app/+page-not-found/page-not-found.component.ts
@@ -2,8 +2,6 @@ import { Component, OnInit } from '@angular/core'
2import { Title } from '@angular/platform-browser' 2import { Title } from '@angular/platform-browser'
3import { Router } from '@angular/router' 3import { Router } from '@angular/router'
4import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' 4import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
5
6
7@Component({ 5@Component({
8 selector: 'my-page-not-found', 6 selector: 'my-page-not-found',
9 templateUrl: './page-not-found.component.html', 7 templateUrl: './page-not-found.component.html',
diff --git a/client/src/app/+remote-interaction/remote-interaction.component.ts b/client/src/app/+remote-interaction/remote-interaction.component.ts
index 3ebe62f49..6ddf5b58d 100644
--- a/client/src/app/+remote-interaction/remote-interaction.component.ts
+++ b/client/src/app/+remote-interaction/remote-interaction.component.ts
@@ -39,7 +39,7 @@ export class RemoteInteractionComponent implements OnInit {
39 if (videoResult.data.length !== 0) { 39 if (videoResult.data.length !== 0) {
40 const video = videoResult.data[0] 40 const video = videoResult.data[0]
41 41
42 redirectUrl = '/videos/watch/' + video.uuid 42 redirectUrl = '/w/' + video.uuid
43 } else if (channelResult.data.length !== 0) { 43 } else if (channelResult.data.length !== 0) {
44 const channel = new VideoChannel(channelResult.data[0]) 44 const channel = new VideoChannel(channelResult.data[0])
45 45
diff --git a/client/src/app/+search/video-lazy-load.resolver.ts b/client/src/app/+search/video-lazy-load.resolver.ts
index d4fe6ed79..e43e0089b 100644
--- a/client/src/app/+search/video-lazy-load.resolver.ts
+++ b/client/src/app/+search/video-lazy-load.resolver.ts
@@ -28,7 +28,7 @@ export class VideoLazyLoadResolver implements Resolve<any> {
28 28
29 const video = result.data[0] 29 const video = result.data[0]
30 30
31 return this.router.navigateByUrl('/videos/watch/' + video.uuid) 31 return this.router.navigateByUrl('/w/' + video.uuid)
32 }) 32 })
33 ) 33 )
34 } 34 }
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts
index 8e035b6bb..727bbc32f 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts
@@ -127,7 +127,7 @@ export class VideoGoLiveComponent extends VideoSend implements OnInit, AfterView
127 () => { 127 () => {
128 this.notifier.success($localize`Live published.`) 128 this.notifier.success($localize`Live published.`)
129 129
130 this.router.navigate(['/videos/watch', video.uuid]) 130 this.router.navigate(['/w', video.uuid])
131 }, 131 },
132 132
133 err => { 133 err => {
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
index bca1b6eb6..e20f08879 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
@@ -244,7 +244,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
244 this.isUploadingVideo = false 244 this.isUploadingVideo = false
245 245
246 this.notifier.success($localize`Video published.`) 246 this.notifier.success($localize`Video published.`)
247 this.router.navigate([ '/videos/watch', video.uuid ]) 247 this.router.navigate([ '/w', video.uuid ])
248 }, 248 },
249 249
250 err => { 250 err => {
diff --git a/client/src/app/+videos/+video-edit/video-update.component.html b/client/src/app/+videos/+video-edit/video-update.component.html
index 3ce3e623e..9629081e3 100644
--- a/client/src/app/+videos/+video-edit/video-update.component.html
+++ b/client/src/app/+videos/+video-edit/video-update.component.html
@@ -1,7 +1,7 @@
1<div class="margin-content"> 1<div class="margin-content">
2 <div class="title-page title-page-single"> 2 <div class="title-page title-page-single">
3 <span class="mr-1" i18n>Update</span> 3 <span class="mr-1" i18n>Update</span>
4 <a [routerLink]="[ '/videos/watch', video.uuid ]">{{ video?.name }}</a> 4 <a [routerLink]="[ '/w', video.uuid ]">{{ video?.name }}</a>
5 </div> 5 </div>
6 6
7 <form novalidate [formGroup]="form"> 7 <form novalidate [formGroup]="form">
diff --git a/client/src/app/+videos/+video-edit/video-update.component.ts b/client/src/app/+videos/+video-edit/video-update.component.ts
index 2973c6840..574669a23 100644
--- a/client/src/app/+videos/+video-edit/video-update.component.ts
+++ b/client/src/app/+videos/+video-edit/video-update.component.ts
@@ -156,7 +156,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
156 this.isUpdatingVideo = false 156 this.isUpdatingVideo = false
157 this.loadingBar.useRef().complete() 157 this.loadingBar.useRef().complete()
158 this.notifier.success($localize`Video updated.`) 158 this.notifier.success($localize`Video updated.`)
159 this.router.navigate([ '/videos/watch', this.video.uuid ]) 159 this.router.navigate([ '/w', this.video.uuid ])
160 }, 160 },
161 161
162 err => { 162 err => {
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.component.html b/client/src/app/+videos/+video-watch/comment/video-comment.component.html
index fc0d66ffd..06548edc8 100644
--- a/client/src/app/+videos/+video-watch/comment/video-comment.component.html
+++ b/client/src/app/+videos/+video-watch/comment/video-comment.component.html
@@ -20,7 +20,7 @@
20 </a> 20 </a>
21 </div> 21 </div>
22 22
23 <a [routerLink]="['/videos/watch', video.uuid, { 'threadId': comment.threadId }]" class="comment-date" [title]="comment.createdAt"> 23 <a [routerLink]="['/w', video.uuid, { 'threadId': comment.threadId }]" class="comment-date" [title]="comment.createdAt">
24 {{ comment.createdAt | myFromNow }} 24 {{ comment.createdAt | myFromNow }}
25 </a> 25 </a>
26 </div> 26 </div>
@@ -45,7 +45,7 @@
45 <ng-container *ngIf="comment.isDeleted"> 45 <ng-container *ngIf="comment.isDeleted">
46 <div class="comment-account-date"> 46 <div class="comment-account-date">
47 <span class="comment-account" i18n>Deleted</span> 47 <span class="comment-account" i18n>Deleted</span>
48 <a [routerLink]="['/videos/watch', video.uuid, { 'threadId': comment.threadId }]" 48 <a [routerLink]="['/w', video.uuid, { 'threadId': comment.threadId }]"
49 class="comment-date">{{ comment.createdAt | myFromNow }}</a> 49 class="comment-date">{{ comment.createdAt | myFromNow }}</a>
50 </div> 50 </div>
51 51
diff --git a/client/src/app/+videos/+video-watch/video-watch-routing.module.ts b/client/src/app/+videos/+video-watch/video-watch-routing.module.ts
index cb77685c0..657fd10f8 100644
--- a/client/src/app/+videos/+video-watch/video-watch-routing.module.ts
+++ b/client/src/app/+videos/+video-watch/video-watch-routing.module.ts
@@ -4,7 +4,7 @@ import { VideoWatchComponent } from './video-watch.component'
4 4
5const videoWatchRoutes: Routes = [ 5const videoWatchRoutes: Routes = [
6 { 6 {
7 path: 'playlist/:playlistId', 7 path: 'p/:playlistId',
8 component: VideoWatchComponent 8 component: VideoWatchComponent
9 }, 9 },
10 { 10 {
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 88c5cef52..0acd44524 100644
--- a/client/src/app/+videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/+videos/+video-watch/video-watch.component.ts
@@ -690,7 +690,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
690 if (this.playlist) { 690 if (this.playlist) {
691 this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo()) 691 this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo())
692 } else if (this.nextVideoUuid) { 692 } else if (this.nextVideoUuid) {
693 this.router.navigate([ '/videos/watch', this.nextVideoUuid ]) 693 this.router.navigate([ '/w', this.nextVideoUuid ])
694 } 694 }
695 } 695 }
696 696
diff --git a/client/src/app/+videos/videos-routing.module.ts b/client/src/app/+videos/videos-routing.module.ts
index f9f476b18..926dfaab0 100644
--- a/client/src/app/+videos/videos-routing.module.ts
+++ b/client/src/app/+videos/videos-routing.module.ts
@@ -74,31 +74,6 @@ const videosRoutes: Routes = [
74 key: 'local-videos-list' 74 key: 'local-videos-list'
75 } 75 }
76 } 76 }
77 },
78 {
79 path: 'upload',
80 loadChildren: () => import('@app/+videos/+video-edit/video-add.module').then(m => m.VideoAddModule),
81 data: {
82 meta: {
83 title: $localize`Upload a video`
84 }
85 }
86 },
87 {
88 path: 'update/:uuid',
89 loadChildren: () => import('@app/+videos/+video-edit/video-update.module').then(m => m.VideoUpdateModule),
90 data: {
91 meta: {
92 title: $localize`Edit a video`
93 }
94 }
95 },
96 {
97 path: 'watch',
98 loadChildren: () => import('@app/+videos/+video-watch/video-watch.module').then(m => m.VideoWatchModule),
99 data: {
100 preload: 3000
101 }
102 } 77 }
103 ] 78 ]
104 } 79 }
diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts
index 444b6f134..e35f540be 100644
--- a/client/src/app/app-routing.module.ts
+++ b/client/src/app/app-routing.module.ts
@@ -36,16 +36,27 @@ const routes: Routes = [
36 loadChildren: () => import('./+signup/+verify-account/verify-account.module').then(m => m.VerifyAccountModule), 36 loadChildren: () => import('./+signup/+verify-account/verify-account.module').then(m => m.VerifyAccountModule),
37 canActivateChild: [ MetaGuard ] 37 canActivateChild: [ MetaGuard ]
38 }, 38 },
39
40 {
41 path: 'accounts',
42 redirectTo: 'a'
43 },
39 { 44 {
40 path: 'a', 45 path: 'a',
41 loadChildren: () => import('./+accounts/accounts.module').then(m => m.AccountsModule), 46 loadChildren: () => import('./+accounts/accounts.module').then(m => m.AccountsModule),
42 canActivateChild: [ MetaGuard ] 47 canActivateChild: [ MetaGuard ]
43 }, 48 },
49
50 {
51 path: 'video-channels',
52 redirectTo: 'c'
53 },
44 { 54 {
45 path: 'c', 55 path: 'c',
46 loadChildren: () => import('./+video-channels/video-channels.module').then(m => m.VideoChannelsModule), 56 loadChildren: () => import('./+video-channels/video-channels.module').then(m => m.VideoChannelsModule),
47 canActivateChild: [ MetaGuard ] 57 canActivateChild: [ MetaGuard ]
48 }, 58 },
59
49 { 60 {
50 path: 'about', 61 path: 'about',
51 loadChildren: () => import('./+about/about.module').then(m => m.AboutModule), 62 loadChildren: () => import('./+about/about.module').then(m => m.AboutModule),
@@ -71,31 +82,60 @@ const routes: Routes = [
71 loadChildren: () => import('./+search/search.module').then(m => m.SearchModule), 82 loadChildren: () => import('./+search/search.module').then(m => m.SearchModule),
72 canActivateChild: [ MetaGuard ] 83 canActivateChild: [ MetaGuard ]
73 }, 84 },
85
74 { 86 {
75 path: 'videos', 87 path: 'videos/upload',
76 loadChildren: () => import('./+videos/videos.module').then(m => m.VideosModule), 88 loadChildren: () => import('@app/+videos/+video-edit/video-add.module').then(m => m.VideoAddModule),
77 canActivateChild: [ MetaGuard ] 89 data: {
90 meta: {
91 title: $localize`Upload a video`
92 }
93 }
78 }, 94 },
79 { 95 {
80 path: 'remote-interaction', 96 path: 'videos/update/:uuid',
81 loadChildren: () => import('./+remote-interaction/remote-interaction.module').then(m => m.RemoteInteractionModule), 97 loadChildren: () => import('@app/+videos/+video-edit/video-update.module').then(m => m.VideoUpdateModule),
98 data: {
99 meta: {
100 title: $localize`Edit a video`
101 }
102 }
103 },
104
105 {
106 path: 'videos/watch/playlist',
107 redirectTo: 'w/p'
108 },
109 {
110 path: 'videos/watch',
111 redirectTo: 'w'
112 },
113 {
114 path: 'w',
115 loadChildren: () => import('@app/+videos/+video-watch/video-watch.module').then(m => m.VideoWatchModule),
116 data: {
117 preload: 3000
118 }
119 },
120 {
121 path: 'videos',
122 loadChildren: () => import('./+videos/videos.module').then(m => m.VideosModule),
82 canActivateChild: [ MetaGuard ] 123 canActivateChild: [ MetaGuard ]
83 }, 124 },
84 { 125 {
85 path: 'video-playlists/watch', 126 path: 'video-playlists/watch',
86 redirectTo: 'videos/watch/playlist' 127 redirectTo: 'videos/watch/playlist'
87 }, 128 },
129
88 { 130 {
89 path: 'accounts', 131 path: 'remote-interaction',
90 redirectTo: 'a' 132 loadChildren: () => import('./+remote-interaction/remote-interaction.module').then(m => m.RemoteInteractionModule),
91 }, 133 canActivateChild: [ MetaGuard ]
92 {
93 path: 'video-channels',
94 redirectTo: 'c'
95 }, 134 },
135
136 // Matches /@:actorName
96 { 137 {
97 matcher: (url): UrlMatchResult => { 138 matcher: (url): UrlMatchResult => {
98 // Matches /@:actorName
99 const regex = new RegExp(`^@(${USER_USERNAME_REGEX_CHARACTERS}+)$`) 139 const regex = new RegExp(`^@(${USER_USERNAME_REGEX_CHARACTERS}+)$`)
100 if (url.length !== 1) return null 140 if (url.length !== 1) return null
101 141
@@ -113,6 +153,7 @@ const routes: Routes = [
113 canActivate: [ ActorRedirectGuard ], 153 canActivate: [ ActorRedirectGuard ],
114 component: EmptyComponent 154 component: EmptyComponent
115 }, 155 },
156
116 { 157 {
117 path: '', 158 path: '',
118 component: EmptyComponent // Avoid 404, app component will redirect dynamically 159 component: EmptyComponent // Avoid 404, app component will redirect dynamically
diff --git a/client/src/app/shared/shared-main/users/user-notification.model.ts b/client/src/app/shared/shared-main/users/user-notification.model.ts
index 002a01583..c80bc13b0 100644
--- a/client/src/app/shared/shared-main/users/user-notification.model.ts
+++ b/client/src/app/shared/shared-main/users/user-notification.model.ts
@@ -238,7 +238,7 @@ export class UserNotification implements UserNotificationServer {
238 } 238 }
239 239
240 private buildVideoUrl (video: { uuid: string }) { 240 private buildVideoUrl (video: { uuid: string }) {
241 return '/videos/watch/' + video.uuid 241 return '/w/' + video.uuid
242 } 242 }
243 243
244 private buildAccountUrl (account: { name: string, host: string }) { 244 private buildAccountUrl (account: { name: string, host: string }) {
diff --git a/client/src/app/shared/shared-main/video/video.model.ts b/client/src/app/shared/shared-main/video/video.model.ts
index 526d10e32..e7f739bfe 100644
--- a/client/src/app/shared/shared-main/video/video.model.ts
+++ b/client/src/app/shared/shared-main/video/video.model.ts
@@ -88,7 +88,7 @@ export class Video implements VideoServerModel {
88 pluginData?: any 88 pluginData?: any
89 89
90 static buildClientUrl (videoUUID: string) { 90 static buildClientUrl (videoUUID: string) {
91 return '/videos/watch/' + videoUUID 91 return '/w/' + videoUUID
92 } 92 }
93 93
94 constructor (hash: VideoServerModel, translations = {}) { 94 constructor (hash: VideoServerModel, translations = {}) {
diff --git a/client/src/app/shared/shared-share-modal/video-share.component.ts b/client/src/app/shared/shared-share-modal/video-share.component.ts
index e8760bfcc..2a73e6166 100644
--- a/client/src/app/shared/shared-share-modal/video-share.component.ts
+++ b/client/src/app/shared/shared-share-modal/video-share.component.ts
@@ -98,14 +98,14 @@ export class VideoShareComponent {
98 98
99 getVideoUrl () { 99 getVideoUrl () {
100 let baseUrl = this.customizations.originUrl ? this.video.originInstanceUrl : window.location.origin 100 let baseUrl = this.customizations.originUrl ? this.video.originInstanceUrl : window.location.origin
101 baseUrl += '/videos/watch/' + this.video.uuid 101 baseUrl += '/w/' + this.video.uuid
102 const options = this.getVideoOptions(baseUrl) 102 const options = this.getVideoOptions(baseUrl)
103 103
104 return buildVideoLink(options) 104 return buildVideoLink(options)
105 } 105 }
106 106
107 getPlaylistUrl () { 107 getPlaylistUrl () {
108 const base = window.location.origin + '/videos/watch/playlist/' + this.playlist.uuid 108 const base = window.location.origin + '/w/p/' + this.playlist.uuid
109 109
110 if (!this.includeVideoInPlaylist) return base 110 if (!this.includeVideoInPlaylist) return base
111 111
diff --git a/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts
index bdede17a3..d5583c29f 100644
--- a/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts
+++ b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts
@@ -57,7 +57,7 @@ export class VideoThumbnailComponent {
57 getVideoRouterLink () { 57 getVideoRouterLink () {
58 if (this.videoRouterLink) return this.videoRouterLink 58 if (this.videoRouterLink) return this.videoRouterLink
59 59
60 return [ '/videos/watch', this.video.uuid ] 60 return [ '/w', this.video.uuid ]
61 } 61 }
62 62
63 onWatchLaterClick (event: Event) { 63 onWatchLaterClick (event: Event) {
diff --git a/client/src/app/shared/shared-video-comment/video-comment.model.ts b/client/src/app/shared/shared-video-comment/video-comment.model.ts
index 1a2fe03db..94d6c5fa8 100644
--- a/client/src/app/shared/shared-video-comment/video-comment.model.ts
+++ b/client/src/app/shared/shared-video-comment/video-comment.model.ts
@@ -85,7 +85,7 @@ export class VideoCommentAdmin implements VideoCommentAdminServerModel {
85 id: hash.video.id, 85 id: hash.video.id,
86 uuid: hash.video.uuid, 86 uuid: hash.video.uuid,
87 name: hash.video.name, 87 name: hash.video.name,
88 localUrl: '/videos/watch/' + hash.video.uuid 88 localUrl: '/w/' + hash.video.uuid
89 } 89 }
90 90
91 this.localUrl = this.video.localUrl + ';threadId=' + this.threadId 91 this.localUrl = this.video.localUrl + ';threadId=' + this.threadId
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 b58c118be..aac55a6e9 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
@@ -125,7 +125,7 @@ export class VideoMiniatureComponent implements OnInit {
125 125
126 buildVideoLink () { 126 buildVideoLink () {
127 if (this.videoLinkType === 'internal' || !this.video.url) { 127 if (this.videoLinkType === 'internal' || !this.video.url) {
128 this.videoRouterLink = [ '/videos/watch', this.video.uuid ] 128 this.videoRouterLink = [ '/w', this.video.uuid ]
129 return 129 return
130 } 130 }
131 131
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts
index 7c083ae26..86c281a1e 100644
--- a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts
+++ b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts
@@ -71,7 +71,7 @@ export class VideoPlaylistElementMiniatureComponent implements OnInit {
71 buildRouterLink () { 71 buildRouterLink () {
72 if (!this.playlist) return null 72 if (!this.playlist) return null
73 73
74 return [ '/videos/watch/playlist', this.playlist.uuid ] 74 return [ '/w/p', this.playlist.uuid ]
75 } 75 }
76 76
77 buildRouterQuery () { 77 buildRouterQuery () {
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.ts b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.ts
index 6b0b1056f..9bbec6038 100644
--- a/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.ts
+++ b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.ts
@@ -18,6 +18,6 @@ export class VideoPlaylistMiniatureComponent {
18 if (this.toManage) return [ '/my-library/video-playlists', this.playlist.uuid ] 18 if (this.toManage) return [ '/my-library/video-playlists', this.playlist.uuid ]
19 if (this.playlist.videosLength === 0) return null 19 if (this.playlist.videosLength === 0) return null
20 20
21 return [ '/videos/watch/playlist', this.playlist.uuid ] 21 return [ '/w/p', this.playlist.uuid ]
22 } 22 }
23} 23}
diff --git a/client/src/assets/player/utils.ts b/client/src/assets/player/utils.ts
index 1243526d2..2bb70d1fa 100644
--- a/client/src/assets/player/utils.ts
+++ b/client/src/assets/player/utils.ts
@@ -65,7 +65,7 @@ function buildVideoLink (options: {
65 65
66 const url = baseUrl 66 const url = baseUrl
67 ? baseUrl 67 ? baseUrl
68 : window.location.origin + window.location.pathname.replace('/embed/', '/watch/') 68 : window.location.origin + window.location.pathname.replace('/embed/', '/w/')
69 69
70 const params = generateParams(window.location.search) 70 const params = generateParams(window.location.search)
71 71
@@ -101,7 +101,7 @@ function buildPlaylistLink (options: {
101 101
102 const url = baseUrl 102 const url = baseUrl
103 ? baseUrl 103 ? baseUrl
104 : window.location.origin + window.location.pathname.replace('/video-playlists/embed/', '/videos/watch/playlist/') 104 : window.location.origin + window.location.pathname.replace('/video-playlists/embed/', '/w/p/')
105 105
106 const params = generateParams(window.location.search) 106 const params = generateParams(window.location.search)
107 107
diff --git a/server/controllers/bots.ts b/server/controllers/bots.ts
index 8d1fa72f3..9e92063d4 100644
--- a/server/controllers/bots.ts
+++ b/server/controllers/bots.ts
@@ -75,7 +75,7 @@ async function getSitemapLocalVideoUrls () {
75 }) 75 })
76 76
77 return data.map(v => ({ 77 return data.map(v => ({
78 url: WEBSERVER.URL + '/videos/watch/' + v.uuid, 78 url: WEBSERVER.URL + '/w/' + v.uuid,
79 video: [ 79 video: [
80 { 80 {
81 title: v.name, 81 title: v.name,
diff --git a/server/controllers/client.ts b/server/controllers/client.ts
index 35e5af9d1..fcccc48e0 100644
--- a/server/controllers/client.ts
+++ b/server/controllers/client.ts
@@ -19,8 +19,8 @@ const testEmbedPath = join(distPath, 'standalone', 'videos', 'test-embed.html')
19 19
20// Special route that add OpenGraph and oEmbed tags 20// Special route that add OpenGraph and oEmbed tags
21// Do not use a template engine for a so little thing 21// Do not use a template engine for a so little thing
22clientsRouter.use('/videos/watch/playlist/:id', asyncMiddleware(generateWatchPlaylistHtmlPage)) 22clientsRouter.use([ '/w/p/:id', '/videos/watch/playlist/:id' ], asyncMiddleware(generateWatchPlaylistHtmlPage))
23clientsRouter.use('/videos/watch/:id', asyncMiddleware(generateWatchHtmlPage)) 23clientsRouter.use([ '/w/:id', '/videos/watch/:id' ], asyncMiddleware(generateWatchHtmlPage))
24clientsRouter.use([ '/accounts/:nameWithHost', '/a/:nameWithHost' ], asyncMiddleware(generateAccountHtmlPage)) 24clientsRouter.use([ '/accounts/:nameWithHost', '/a/:nameWithHost' ], asyncMiddleware(generateAccountHtmlPage))
25clientsRouter.use([ '/video-channels/:nameWithHost', '/c/:nameWithHost' ], asyncMiddleware(generateVideoChannelHtmlPage)) 25clientsRouter.use([ '/video-channels/:nameWithHost', '/c/:nameWithHost' ], asyncMiddleware(generateVideoChannelHtmlPage))
26clientsRouter.use('/@:nameWithHost', asyncMiddleware(generateActorHtmlPage)) 26clientsRouter.use('/@:nameWithHost', asyncMiddleware(generateActorHtmlPage))
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts
index f0717bbbc..865f5c2a1 100644
--- a/server/controllers/feeds.ts
+++ b/server/controllers/feeds.ts
@@ -293,7 +293,7 @@ function addVideosToFeed (feed, videos: VideoModel[]) {
293 feed.addItem({ 293 feed.addItem({
294 title: video.name, 294 title: video.name,
295 id: video.url, 295 id: video.url,
296 link: WEBSERVER.URL + '/videos/watch/' + video.uuid, 296 link: WEBSERVER.URL + '/w/' + video.uuid,
297 description: video.getTruncatedDescription(), 297 description: video.getTruncatedDescription(),
298 content: video.description, 298 content: video.description,
299 author: [ 299 author: [
diff --git a/server/middlewares/validators/oembed.ts b/server/middlewares/validators/oembed.ts
index 2a7dc257b..165eda6d5 100644
--- a/server/middlewares/validators/oembed.ts
+++ b/server/middlewares/validators/oembed.ts
@@ -4,15 +4,29 @@ import { join } from 'path'
4import { fetchVideo } from '@server/helpers/video' 4import { fetchVideo } from '@server/helpers/video'
5import { VideoPlaylistModel } from '@server/models/video/video-playlist' 5import { VideoPlaylistModel } from '@server/models/video/video-playlist'
6import { VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models' 6import { VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models'
7import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
7import { isTestInstance } from '../../helpers/core-utils' 8import { isTestInstance } from '../../helpers/core-utils'
8import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' 9import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
9import { logger } from '../../helpers/logger' 10import { logger } from '../../helpers/logger'
10import { WEBSERVER } from '../../initializers/constants' 11import { WEBSERVER } from '../../initializers/constants'
11import { areValidationErrors } from './utils' 12import { areValidationErrors } from './utils'
12import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
13 13
14const startVideoPlaylistsURL = WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, 'videos', 'watch', 'playlist') + '/' 14const playlistPaths = [
15const startVideosURL = WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, 'videos', 'watch') + '/' 15 join('videos', 'watch', 'playlist'),
16 join('w', 'p')
17]
18
19const videoPaths = [
20 join('videos', 'watch'),
21 'w'
22]
23
24function buildUrls (paths: string[]) {
25 return paths.map(p => WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, p) + '/')
26}
27
28const startPlaylistURLs = buildUrls(playlistPaths)
29const startVideoURLs = buildUrls(videoPaths)
16 30
17const watchRegex = /([^/]+)$/ 31const watchRegex = /([^/]+)$/
18const isURLOptions = { 32const isURLOptions = {
@@ -43,8 +57,8 @@ const oembedValidator = [
43 57
44 const url = req.query.url as string 58 const url = req.query.url as string
45 59
46 const isPlaylist = url.startsWith(startVideoPlaylistsURL) 60 const isPlaylist = startPlaylistURLs.some(u => url.startsWith(u))
47 const isVideo = isPlaylist ? false : url.startsWith(startVideosURL) 61 const isVideo = isPlaylist ? false : startVideoURLs.some(u => url.startsWith(u))
48 62
49 const startIsOk = isVideo || isPlaylist 63 const startIsOk = isVideo || isPlaylist
50 64
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts
index c293287d3..98cea1b64 100644
--- a/server/models/video/video-playlist.ts
+++ b/server/models/video/video-playlist.ts
@@ -496,7 +496,7 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli
496 } 496 }
497 497
498 getWatchUrl () { 498 getWatchUrl () {
499 return WEBSERVER.URL + '/videos/watch/playlist/' + this.uuid 499 return WEBSERVER.URL + '/w/p/' + this.uuid
500 } 500 }
501 501
502 getEmbedStaticPath () { 502 getEmbedStaticPath () {
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index d4a258187..5af907533 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1920,7 +1920,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1920 } 1920 }
1921 1921
1922 getWatchStaticPath () { 1922 getWatchStaticPath () {
1923 return '/videos/watch/' + this.uuid 1923 return '/w/' + this.uuid
1924 } 1924 }
1925 1925
1926 getEmbedStaticPath () { 1926 getEmbedStaticPath () {
diff --git a/server/tests/api/server/services.ts b/server/tests/api/server/services.ts
index f0fa91674..ea64e4040 100644
--- a/server/tests/api/server/services.ts
+++ b/server/tests/api/server/services.ts
@@ -67,61 +67,67 @@ describe('Test services', function () {
67 }) 67 })
68 68
69 it('Should have a valid oEmbed video response', async function () { 69 it('Should have a valid oEmbed video response', async function () {
70 const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + video.uuid 70 for (const basePath of [ '/videos/watch/', '/w/' ]) {
71 71 const oembedUrl = 'http://localhost:' + server.port + basePath + video.uuid
72 const res = await getOEmbed(server.url, oembedUrl) 72
73 const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' + 73 const res = await getOEmbed(server.url, oembedUrl)
74 `title="${video.name}" src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` + 74 const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' +
75 'frameborder="0" allowfullscreen></iframe>' 75 `title="${video.name}" src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` +
76 const expectedThumbnailUrl = 'http://localhost:' + server.port + video.previewPath 76 'frameborder="0" allowfullscreen></iframe>'
77 77 const expectedThumbnailUrl = 'http://localhost:' + server.port + video.previewPath
78 expect(res.body.html).to.equal(expectedHtml) 78
79 expect(res.body.title).to.equal(video.name) 79 expect(res.body.html).to.equal(expectedHtml)
80 expect(res.body.author_name).to.equal(server.videoChannel.displayName) 80 expect(res.body.title).to.equal(video.name)
81 expect(res.body.width).to.equal(560) 81 expect(res.body.author_name).to.equal(server.videoChannel.displayName)
82 expect(res.body.height).to.equal(315) 82 expect(res.body.width).to.equal(560)
83 expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl) 83 expect(res.body.height).to.equal(315)
84 expect(res.body.thumbnail_width).to.equal(850) 84 expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl)
85 expect(res.body.thumbnail_height).to.equal(480) 85 expect(res.body.thumbnail_width).to.equal(850)
86 expect(res.body.thumbnail_height).to.equal(480)
87 }
86 }) 88 })
87 89
88 it('Should have a valid playlist oEmbed response', async function () { 90 it('Should have a valid playlist oEmbed response', async function () {
89 const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/playlist/' + playlistUUID 91 for (const basePath of [ '/videos/watch/playlist/', '/w/p/' ]) {
90 92 const oembedUrl = 'http://localhost:' + server.port + basePath + playlistUUID
91 const res = await getOEmbed(server.url, oembedUrl) 93
92 const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' + 94 const res = await getOEmbed(server.url, oembedUrl)
93 `title="${playlistDisplayName}" src="http://localhost:${server.port}/video-playlists/embed/${playlistUUID}" ` + 95 const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' +
94 'frameborder="0" allowfullscreen></iframe>' 96 `title="${playlistDisplayName}" src="http://localhost:${server.port}/video-playlists/embed/${playlistUUID}" ` +
95 97 'frameborder="0" allowfullscreen></iframe>'
96 expect(res.body.html).to.equal(expectedHtml) 98
97 expect(res.body.title).to.equal('The Life and Times of Scrooge McDuck') 99 expect(res.body.html).to.equal(expectedHtml)
98 expect(res.body.author_name).to.equal(server.videoChannel.displayName) 100 expect(res.body.title).to.equal('The Life and Times of Scrooge McDuck')
99 expect(res.body.width).to.equal(560) 101 expect(res.body.author_name).to.equal(server.videoChannel.displayName)
100 expect(res.body.height).to.equal(315) 102 expect(res.body.width).to.equal(560)
101 expect(res.body.thumbnail_url).exist 103 expect(res.body.height).to.equal(315)
102 expect(res.body.thumbnail_width).to.equal(280) 104 expect(res.body.thumbnail_url).exist
103 expect(res.body.thumbnail_height).to.equal(157) 105 expect(res.body.thumbnail_width).to.equal(280)
106 expect(res.body.thumbnail_height).to.equal(157)
107 }
104 }) 108 })
105 109
106 it('Should have a valid oEmbed response with small max height query', async function () { 110 it('Should have a valid oEmbed response with small max height query', async function () {
107 const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + video.uuid 111 for (const basePath of [ '/videos/watch/', '/w/' ]) {
108 const format = 'json' 112 const oembedUrl = 'http://localhost:' + server.port + basePath + video.uuid
109 const maxHeight = 50 113 const format = 'json'
110 const maxWidth = 50 114 const maxHeight = 50
111 115 const maxWidth = 50
112 const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth) 116
113 const expectedHtml = '<iframe width="50" height="50" sandbox="allow-same-origin allow-scripts" ' + 117 const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth)
114 `title="${video.name}" src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` + 118 const expectedHtml = '<iframe width="50" height="50" sandbox="allow-same-origin allow-scripts" ' +
115 'frameborder="0" allowfullscreen></iframe>' 119 `title="${video.name}" src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` +
116 120 'frameborder="0" allowfullscreen></iframe>'
117 expect(res.body.html).to.equal(expectedHtml) 121
118 expect(res.body.title).to.equal(video.name) 122 expect(res.body.html).to.equal(expectedHtml)
119 expect(res.body.author_name).to.equal(server.videoChannel.displayName) 123 expect(res.body.title).to.equal(video.name)
120 expect(res.body.height).to.equal(50) 124 expect(res.body.author_name).to.equal(server.videoChannel.displayName)
121 expect(res.body.width).to.equal(50) 125 expect(res.body.height).to.equal(50)
122 expect(res.body).to.not.have.property('thumbnail_url') 126 expect(res.body.width).to.equal(50)
123 expect(res.body).to.not.have.property('thumbnail_width') 127 expect(res.body).to.not.have.property('thumbnail_url')
124 expect(res.body).to.not.have.property('thumbnail_height') 128 expect(res.body).to.not.have.property('thumbnail_width')
129 expect(res.body).to.not.have.property('thumbnail_height')
130 }
125 }) 131 })
126 132
127 after(async function () { 133 after(async function () {
diff --git a/server/tests/client.ts b/server/tests/client.ts
index f33e5c1da..253a95624 100644
--- a/server/tests/client.ts
+++ b/server/tests/client.ts
@@ -54,6 +54,9 @@ describe('Test a client controllers', function () {
54 54
55 const channelDescription = 'my super channel description' 55 const channelDescription = 'my super channel description'
56 56
57 const watchVideoBasePaths = [ '/videos/watch/', '/w/' ]
58 const watchPlaylistBasePaths = [ '/videos/watch/playlist/', '/w/p/' ]
59
57 before(async function () { 60 before(async function () {
58 this.timeout(120000) 61 this.timeout(120000)
59 62
@@ -111,35 +114,40 @@ describe('Test a client controllers', function () {
111 }) 114 })
112 115
113 describe('oEmbed', function () { 116 describe('oEmbed', function () {
117
114 it('Should have valid oEmbed discovery tags for videos', async function () { 118 it('Should have valid oEmbed discovery tags for videos', async function () {
115 const path = '/videos/watch/' + servers[0].video.uuid 119 for (const basePath of watchVideoBasePaths) {
116 const res = await request(servers[0].url) 120 const path = basePath + servers[0].video.uuid
117 .get(path) 121 const res = await request(servers[0].url)
118 .set('Accept', 'text/html') 122 .get(path)
119 .expect(HttpStatusCode.OK_200) 123 .set('Accept', 'text/html')
124 .expect(HttpStatusCode.OK_200)
120 125
121 const port = servers[0].port 126 const port = servers[0].port
122 127
123 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' + 128 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
124 `url=http%3A%2F%2Flocalhost%3A${port}%2Fvideos%2Fwatch%2F${servers[0].video.uuid}" ` + 129 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2F${servers[0].video.uuid}" ` +
125 `title="${servers[0].video.name}" />` 130 `title="${servers[0].video.name}" />`
126 131
127 expect(res.text).to.contain(expectedLink) 132 expect(res.text).to.contain(expectedLink)
133 }
128 }) 134 })
129 135
130 it('Should have valid oEmbed discovery tags for a playlist', async function () { 136 it('Should have valid oEmbed discovery tags for a playlist', async function () {
131 const res = await request(servers[0].url) 137 for (const basePath of watchPlaylistBasePaths) {
132 .get('/videos/watch/playlist/' + playlistUUID) 138 const res = await request(servers[0].url)
133 .set('Accept', 'text/html') 139 .get(basePath + playlistUUID)
134 .expect(HttpStatusCode.OK_200) 140 .set('Accept', 'text/html')
141 .expect(HttpStatusCode.OK_200)
135 142
136 const port = servers[0].port 143 const port = servers[0].port
137 144
138 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' + 145 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
139 `url=http%3A%2F%2Flocalhost%3A${port}%2Fvideos%2Fwatch%2Fplaylist%2F${playlistUUID}" ` + 146 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2Fp%2F${playlistUUID}" ` +
140 `title="${playlistName}" />` 147 `title="${playlistName}" />`
141 148
142 expect(res.text).to.contain(expectedLink) 149 expect(res.text).to.contain(expectedLink)
150 }
143 }) 151 })
144 }) 152 })
145 153
@@ -165,6 +173,26 @@ describe('Test a client controllers', function () {
165 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].videoChannel.name}" />`) 173 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].videoChannel.name}" />`)
166 } 174 }
167 175
176 async function watchVideoPageTest (path: string) {
177 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
178 const text = res.text
179
180 expect(text).to.contain(`<meta property="og:title" content="${videoName}" />`)
181 expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
182 expect(text).to.contain('<meta property="og:type" content="video" />')
183 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/${servers[0].video.uuid}" />`)
184 }
185
186 async function watchPlaylistPageTest (path: string) {
187 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
188 const text = res.text
189
190 expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
191 expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
192 expect(text).to.contain('<meta property="og:type" content="video" />')
193 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlistUUID}" />`)
194 }
195
168 it('Should have valid Open Graph tags on the account page', async function () { 196 it('Should have valid Open Graph tags on the account page', async function () {
169 await accountPageTest('/accounts/' + servers[0].user.username) 197 await accountPageTest('/accounts/' + servers[0].user.username)
170 await accountPageTest('/a/' + servers[0].user.username) 198 await accountPageTest('/a/' + servers[0].user.username)
@@ -177,40 +205,16 @@ describe('Test a client controllers', function () {
177 await channelPageTest('/@' + servers[0].videoChannel.name) 205 await channelPageTest('/@' + servers[0].videoChannel.name)
178 }) 206 })
179 207
180 it('Should have valid Open Graph tags on the watch page with video id', async function () { 208 it('Should have valid Open Graph tags on the watch page', async function () {
181 const res = await request(servers[0].url) 209 await watchVideoPageTest('/videos/watch/' + servers[0].video.id)
182 .get('/videos/watch/' + servers[0].video.id) 210 await watchVideoPageTest('/videos/watch/' + servers[0].video.uuid)
183 .set('Accept', 'text/html') 211 await watchVideoPageTest('/w/' + servers[0].video.uuid)
184 .expect(HttpStatusCode.OK_200) 212 await watchVideoPageTest('/w/' + servers[0].video.id)
185
186 expect(res.text).to.contain(`<meta property="og:title" content="${videoName}" />`)
187 expect(res.text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
188 expect(res.text).to.contain('<meta property="og:type" content="video" />')
189 expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`)
190 })
191
192 it('Should have valid Open Graph tags on the watch page with video uuid', async function () {
193 const res = await request(servers[0].url)
194 .get('/videos/watch/' + servers[0].video.uuid)
195 .set('Accept', 'text/html')
196 .expect(HttpStatusCode.OK_200)
197
198 expect(res.text).to.contain(`<meta property="og:title" content="${videoName}" />`)
199 expect(res.text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
200 expect(res.text).to.contain('<meta property="og:type" content="video" />')
201 expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`)
202 }) 213 })
203 214
204 it('Should have valid Open Graph tags on the watch playlist page', async function () { 215 it('Should have valid Open Graph tags on the watch playlist page', async function () {
205 const res = await request(servers[0].url) 216 await watchPlaylistPageTest('/videos/watch/playlist/' + playlistUUID)
206 .get('/videos/watch/playlist/' + playlistUUID) 217 await watchPlaylistPageTest('/w/p/' + playlistUUID)
207 .set('Accept', 'text/html')
208 .expect(HttpStatusCode.OK_200)
209
210 expect(res.text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
211 expect(res.text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
212 expect(res.text).to.contain('<meta property="og:type" content="video" />')
213 expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/videos/watch/playlist/${playlistUUID}" />`)
214 }) 218 })
215 }) 219 })
216 220
@@ -238,28 +242,36 @@ describe('Test a client controllers', function () {
238 expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`) 242 expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
239 } 243 }
240 244
241 it('Should have valid twitter card on the watch video page', async function () { 245 async function watchVideoPageTest (path: string) {
242 const res = await request(servers[0].url) 246 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
243 .get('/videos/watch/' + servers[0].video.uuid) 247 const text = res.text
244 .set('Accept', 'text/html')
245 .expect(HttpStatusCode.OK_200)
246 248
247 expect(res.text).to.contain('<meta property="twitter:card" content="summary_large_image" />') 249 expect(text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
248 expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />') 250 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
249 expect(res.text).to.contain(`<meta property="twitter:title" content="${videoName}" />`) 251 expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
250 expect(res.text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`) 252 expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
253 }
254
255 async function watchPlaylistPageTest (path: string) {
256 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
257 const text = res.text
258
259 expect(text).to.contain('<meta property="twitter:card" content="summary" />')
260 expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
261 expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
262 expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
263 }
264
265 it('Should have valid twitter card on the watch video page', async function () {
266 await watchVideoPageTest('/videos/watch/' + servers[0].video.id)
267 await watchVideoPageTest('/videos/watch/' + servers[0].video.uuid)
268 await watchVideoPageTest('/w/' + servers[0].video.uuid)
269 await watchVideoPageTest('/w/' + servers[0].video.id)
251 }) 270 })
252 271
253 it('Should have valid twitter card on the watch playlist page', async function () { 272 it('Should have valid twitter card on the watch playlist page', async function () {
254 const res = await request(servers[0].url) 273 await watchPlaylistPageTest('/videos/watch/playlist/' + playlistUUID)
255 .get('/videos/watch/playlist/' + playlistUUID) 274 await watchPlaylistPageTest('/w/p/' + playlistUUID)
256 .set('Accept', 'text/html')
257 .expect(HttpStatusCode.OK_200)
258
259 expect(res.text).to.contain('<meta property="twitter:card" content="summary" />')
260 expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
261 expect(res.text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
262 expect(res.text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
263 }) 275 })
264 276
265 it('Should have valid twitter card on the account page', async function () { 277 it('Should have valid twitter card on the account page', async function () {
@@ -304,24 +316,32 @@ describe('Test a client controllers', function () {
304 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />') 316 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
305 } 317 }
306 318
307 it('Should have valid twitter card on the watch video page', async function () { 319 async function watchVideoPageTest (path: string) {
308 const res = await request(servers[0].url) 320 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
309 .get('/videos/watch/' + servers[0].video.uuid) 321 const text = res.text
310 .set('Accept', 'text/html')
311 .expect(HttpStatusCode.OK_200)
312 322
313 expect(res.text).to.contain('<meta property="twitter:card" content="player" />') 323 expect(text).to.contain('<meta property="twitter:card" content="player" />')
314 expect(res.text).to.contain('<meta property="twitter:site" content="@Kuja" />') 324 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
325 }
326
327 async function watchPlaylistPageTest (path: string) {
328 const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
329 const text = res.text
330
331 expect(text).to.contain('<meta property="twitter:card" content="player" />')
332 expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
333 }
334
335 it('Should have valid twitter card on the watch video page', async function () {
336 await watchVideoPageTest('/videos/watch/' + servers[0].video.id)
337 await watchVideoPageTest('/videos/watch/' + servers[0].video.uuid)
338 await watchVideoPageTest('/w/' + servers[0].video.uuid)
339 await watchVideoPageTest('/w/' + servers[0].video.id)
315 }) 340 })
316 341
317 it('Should have valid twitter card on the watch playlist page', async function () { 342 it('Should have valid twitter card on the watch playlist page', async function () {
318 const res = await request(servers[0].url) 343 await watchPlaylistPageTest('/videos/watch/playlist/' + playlistUUID)
319 .get('/videos/watch/playlist/' + playlistUUID) 344 await watchPlaylistPageTest('/w/p/' + playlistUUID)
320 .set('Accept', 'text/html')
321 .expect(HttpStatusCode.OK_200)
322
323 expect(res.text).to.contain('<meta property="twitter:card" content="player" />')
324 expect(res.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
325 }) 345 })
326 346
327 it('Should have valid twitter card on the account page', async function () { 347 it('Should have valid twitter card on the account page', async function () {
@@ -378,8 +398,10 @@ describe('Test a client controllers', function () {
378 }) 398 })
379 399
380 it('Should use the original video URL for the canonical tag', async function () { 400 it('Should use the original video URL for the canonical tag', async function () {
381 const res = await makeHTMLRequest(servers[1].url, '/videos/watch/' + servers[0].video.uuid) 401 for (const basePath of watchVideoBasePaths) {
382 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`) 402 const res = await makeHTMLRequest(servers[1].url, basePath + servers[0].video.uuid)
403 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`)
404 }
383 }) 405 })
384 406
385 it('Should use the original account URL for the canonical tag', async function () { 407 it('Should use the original account URL for the canonical tag', async function () {
@@ -403,8 +425,10 @@ describe('Test a client controllers', function () {
403 }) 425 })
404 426
405 it('Should use the original playlist URL for the canonical tag', async function () { 427 it('Should use the original playlist URL for the canonical tag', async function () {
406 const res = await makeHTMLRequest(servers[1].url, '/videos/watch/playlist/' + playlistUUID) 428 for (const basePath of watchPlaylistBasePaths) {
407 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlistUUID}" />`) 429 const res = await makeHTMLRequest(servers[1].url, basePath + playlistUUID)
430 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlistUUID}" />`)
431 }
408 }) 432 })
409 }) 433 })
410 434
diff --git a/server/tools/peertube-watch.ts b/server/tools/peertube-watch.ts
index 6d9cfa3b7..3ca3e242a 100644
--- a/server/tools/peertube-watch.ts
+++ b/server/tools/peertube-watch.ts
@@ -30,7 +30,7 @@ function run (url: string, options: program.OptionValues) {
30 30
31 const cmd = 'node ' + join(__dirname, 'node_modules', 'webtorrent-hybrid', 'bin', 'cmd.js') 31 const cmd = 'node ' + join(__dirname, 'node_modules', 'webtorrent-hybrid', 'bin', 'cmd.js')
32 const args = ` --${options.gui} ` + 32 const args = ` --${options.gui} ` +
33 url.replace('videos/watch', 'download/torrents') + 33 url.replace(/(\/videos\/watch\/)|\/w\//, '/download/torrents/') +
34 `-${options.resolution}.torrent` 34 `-${options.resolution}.torrent`
35 35
36 try { 36 try {