Support '/w/' and '/w/p/' for watch page

And use them as default in client
This commit is contained in:
Chocobozzz
2021-05-28 11:36:33 +02:00
parent 012580d98f
commit a1eda903a4
36 changed files with 268 additions and 210 deletions

View File

@@ -75,7 +75,7 @@ async function getSitemapLocalVideoUrls () {
})
return data.map(v => ({
url: WEBSERVER.URL + '/videos/watch/' + v.uuid,
url: WEBSERVER.URL + '/w/' + v.uuid,
video: [
{
title: v.name,

View File

@@ -19,8 +19,8 @@ const testEmbedPath = join(distPath, 'standalone', 'videos', 'test-embed.html')
// Special route that add OpenGraph and oEmbed tags
// Do not use a template engine for a so little thing
clientsRouter.use('/videos/watch/playlist/:id', asyncMiddleware(generateWatchPlaylistHtmlPage))
clientsRouter.use('/videos/watch/:id', asyncMiddleware(generateWatchHtmlPage))
clientsRouter.use([ '/w/p/:id', '/videos/watch/playlist/:id' ], asyncMiddleware(generateWatchPlaylistHtmlPage))
clientsRouter.use([ '/w/:id', '/videos/watch/:id' ], asyncMiddleware(generateWatchHtmlPage))
clientsRouter.use([ '/accounts/:nameWithHost', '/a/:nameWithHost' ], asyncMiddleware(generateAccountHtmlPage))
clientsRouter.use([ '/video-channels/:nameWithHost', '/c/:nameWithHost' ], asyncMiddleware(generateVideoChannelHtmlPage))
clientsRouter.use('/@:nameWithHost', asyncMiddleware(generateActorHtmlPage))

View File

@@ -293,7 +293,7 @@ function addVideosToFeed (feed, videos: VideoModel[]) {
feed.addItem({
title: video.name,
id: video.url,
link: WEBSERVER.URL + '/videos/watch/' + video.uuid,
link: WEBSERVER.URL + '/w/' + video.uuid,
description: video.getTruncatedDescription(),
content: video.description,
author: [

View File

@@ -4,15 +4,29 @@ import { join } from 'path'
import { fetchVideo } from '@server/helpers/video'
import { VideoPlaylistModel } from '@server/models/video/video-playlist'
import { VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models'
import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
import { isTestInstance } from '../../helpers/core-utils'
import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
import { logger } from '../../helpers/logger'
import { WEBSERVER } from '../../initializers/constants'
import { areValidationErrors } from './utils'
import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
const startVideoPlaylistsURL = WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, 'videos', 'watch', 'playlist') + '/'
const startVideosURL = WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, 'videos', 'watch') + '/'
const playlistPaths = [
join('videos', 'watch', 'playlist'),
join('w', 'p')
]
const videoPaths = [
join('videos', 'watch'),
'w'
]
function buildUrls (paths: string[]) {
return paths.map(p => WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, p) + '/')
}
const startPlaylistURLs = buildUrls(playlistPaths)
const startVideoURLs = buildUrls(videoPaths)
const watchRegex = /([^/]+)$/
const isURLOptions = {
@@ -43,8 +57,8 @@ const oembedValidator = [
const url = req.query.url as string
const isPlaylist = url.startsWith(startVideoPlaylistsURL)
const isVideo = isPlaylist ? false : url.startsWith(startVideosURL)
const isPlaylist = startPlaylistURLs.some(u => url.startsWith(u))
const isVideo = isPlaylist ? false : startVideoURLs.some(u => url.startsWith(u))
const startIsOk = isVideo || isPlaylist

View File

@@ -496,7 +496,7 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli
}
getWatchUrl () {
return WEBSERVER.URL + '/videos/watch/playlist/' + this.uuid
return WEBSERVER.URL + '/w/p/' + this.uuid
}
getEmbedStaticPath () {

View File

@@ -1920,7 +1920,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
}
getWatchStaticPath () {
return '/videos/watch/' + this.uuid
return '/w/' + this.uuid
}
getEmbedStaticPath () {

View File

@@ -67,61 +67,67 @@ describe('Test services', function () {
})
it('Should have a valid oEmbed video response', async function () {
const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + video.uuid
for (const basePath of [ '/videos/watch/', '/w/' ]) {
const oembedUrl = 'http://localhost:' + server.port + basePath + video.uuid
const res = await getOEmbed(server.url, oembedUrl)
const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' +
`title="${video.name}" src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` +
'frameborder="0" allowfullscreen></iframe>'
const expectedThumbnailUrl = 'http://localhost:' + server.port + video.previewPath
const res = await getOEmbed(server.url, oembedUrl)
const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' +
`title="${video.name}" src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` +
'frameborder="0" allowfullscreen></iframe>'
const expectedThumbnailUrl = 'http://localhost:' + server.port + video.previewPath
expect(res.body.html).to.equal(expectedHtml)
expect(res.body.title).to.equal(video.name)
expect(res.body.author_name).to.equal(server.videoChannel.displayName)
expect(res.body.width).to.equal(560)
expect(res.body.height).to.equal(315)
expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl)
expect(res.body.thumbnail_width).to.equal(850)
expect(res.body.thumbnail_height).to.equal(480)
expect(res.body.html).to.equal(expectedHtml)
expect(res.body.title).to.equal(video.name)
expect(res.body.author_name).to.equal(server.videoChannel.displayName)
expect(res.body.width).to.equal(560)
expect(res.body.height).to.equal(315)
expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl)
expect(res.body.thumbnail_width).to.equal(850)
expect(res.body.thumbnail_height).to.equal(480)
}
})
it('Should have a valid playlist oEmbed response', async function () {
const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/playlist/' + playlistUUID
for (const basePath of [ '/videos/watch/playlist/', '/w/p/' ]) {
const oembedUrl = 'http://localhost:' + server.port + basePath + playlistUUID
const res = await getOEmbed(server.url, oembedUrl)
const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' +
`title="${playlistDisplayName}" src="http://localhost:${server.port}/video-playlists/embed/${playlistUUID}" ` +
'frameborder="0" allowfullscreen></iframe>'
const res = await getOEmbed(server.url, oembedUrl)
const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' +
`title="${playlistDisplayName}" src="http://localhost:${server.port}/video-playlists/embed/${playlistUUID}" ` +
'frameborder="0" allowfullscreen></iframe>'
expect(res.body.html).to.equal(expectedHtml)
expect(res.body.title).to.equal('The Life and Times of Scrooge McDuck')
expect(res.body.author_name).to.equal(server.videoChannel.displayName)
expect(res.body.width).to.equal(560)
expect(res.body.height).to.equal(315)
expect(res.body.thumbnail_url).exist
expect(res.body.thumbnail_width).to.equal(280)
expect(res.body.thumbnail_height).to.equal(157)
expect(res.body.html).to.equal(expectedHtml)
expect(res.body.title).to.equal('The Life and Times of Scrooge McDuck')
expect(res.body.author_name).to.equal(server.videoChannel.displayName)
expect(res.body.width).to.equal(560)
expect(res.body.height).to.equal(315)
expect(res.body.thumbnail_url).exist
expect(res.body.thumbnail_width).to.equal(280)
expect(res.body.thumbnail_height).to.equal(157)
}
})
it('Should have a valid oEmbed response with small max height query', async function () {
const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + video.uuid
const format = 'json'
const maxHeight = 50
const maxWidth = 50
for (const basePath of [ '/videos/watch/', '/w/' ]) {
const oembedUrl = 'http://localhost:' + server.port + basePath + video.uuid
const format = 'json'
const maxHeight = 50
const maxWidth = 50
const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth)
const expectedHtml = '<iframe width="50" height="50" sandbox="allow-same-origin allow-scripts" ' +
`title="${video.name}" src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` +
'frameborder="0" allowfullscreen></iframe>'
const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth)
const expectedHtml = '<iframe width="50" height="50" sandbox="allow-same-origin allow-scripts" ' +
`title="${video.name}" src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` +
'frameborder="0" allowfullscreen></iframe>'
expect(res.body.html).to.equal(expectedHtml)
expect(res.body.title).to.equal(video.name)
expect(res.body.author_name).to.equal(server.videoChannel.displayName)
expect(res.body.height).to.equal(50)
expect(res.body.width).to.equal(50)
expect(res.body).to.not.have.property('thumbnail_url')
expect(res.body).to.not.have.property('thumbnail_width')
expect(res.body).to.not.have.property('thumbnail_height')
expect(res.body.html).to.equal(expectedHtml)
expect(res.body.title).to.equal(video.name)
expect(res.body.author_name).to.equal(server.videoChannel.displayName)
expect(res.body.height).to.equal(50)
expect(res.body.width).to.equal(50)
expect(res.body).to.not.have.property('thumbnail_url')
expect(res.body).to.not.have.property('thumbnail_width')
expect(res.body).to.not.have.property('thumbnail_height')
}
})
after(async function () {

View File

@@ -54,6 +54,9 @@ describe('Test a client controllers', function () {
const channelDescription = 'my super channel description'
const watchVideoBasePaths = [ '/videos/watch/', '/w/' ]
const watchPlaylistBasePaths = [ '/videos/watch/playlist/', '/w/p/' ]
before(async function () {
this.timeout(120000)
@@ -111,35 +114,40 @@ describe('Test a client controllers', function () {
})
describe('oEmbed', function () {
it('Should have valid oEmbed discovery tags for videos', async function () {
const path = '/videos/watch/' + servers[0].video.uuid
const res = await request(servers[0].url)
.get(path)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200)
for (const basePath of watchVideoBasePaths) {
const path = basePath + servers[0].video.uuid
const res = await request(servers[0].url)
.get(path)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200)
const port = servers[0].port
const port = servers[0].port
const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
`url=http%3A%2F%2Flocalhost%3A${port}%2Fvideos%2Fwatch%2F${servers[0].video.uuid}" ` +
`title="${servers[0].video.name}" />`
const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
`url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2F${servers[0].video.uuid}" ` +
`title="${servers[0].video.name}" />`
expect(res.text).to.contain(expectedLink)
expect(res.text).to.contain(expectedLink)
}
})
it('Should have valid oEmbed discovery tags for a playlist', async function () {
const res = await request(servers[0].url)
.get('/videos/watch/playlist/' + playlistUUID)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200)
for (const basePath of watchPlaylistBasePaths) {
const res = await request(servers[0].url)
.get(basePath + playlistUUID)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200)
const port = servers[0].port
const port = servers[0].port
const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
`url=http%3A%2F%2Flocalhost%3A${port}%2Fvideos%2Fwatch%2Fplaylist%2F${playlistUUID}" ` +
`title="${playlistName}" />`
const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
`url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2Fp%2F${playlistUUID}" ` +
`title="${playlistName}" />`
expect(res.text).to.contain(expectedLink)
expect(res.text).to.contain(expectedLink)
}
})
})
@@ -165,6 +173,26 @@ describe('Test a client controllers', function () {
expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].videoChannel.name}" />`)
}
async function watchVideoPageTest (path: string) {
const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
const text = res.text
expect(text).to.contain(`<meta property="og:title" content="${videoName}" />`)
expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
expect(text).to.contain('<meta property="og:type" content="video" />')
expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/${servers[0].video.uuid}" />`)
}
async function watchPlaylistPageTest (path: string) {
const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
const text = res.text
expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
expect(text).to.contain('<meta property="og:type" content="video" />')
expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlistUUID}" />`)
}
it('Should have valid Open Graph tags on the account page', async function () {
await accountPageTest('/accounts/' + servers[0].user.username)
await accountPageTest('/a/' + servers[0].user.username)
@@ -177,40 +205,16 @@ describe('Test a client controllers', function () {
await channelPageTest('/@' + servers[0].videoChannel.name)
})
it('Should have valid Open Graph tags on the watch page with video id', async function () {
const res = await request(servers[0].url)
.get('/videos/watch/' + servers[0].video.id)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200)
expect(res.text).to.contain(`<meta property="og:title" content="${videoName}" />`)
expect(res.text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
expect(res.text).to.contain('<meta property="og:type" content="video" />')
expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`)
})
it('Should have valid Open Graph tags on the watch page with video uuid', async function () {
const res = await request(servers[0].url)
.get('/videos/watch/' + servers[0].video.uuid)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200)
expect(res.text).to.contain(`<meta property="og:title" content="${videoName}" />`)
expect(res.text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
expect(res.text).to.contain('<meta property="og:type" content="video" />')
expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`)
it('Should have valid Open Graph tags on the watch page', async function () {
await watchVideoPageTest('/videos/watch/' + servers[0].video.id)
await watchVideoPageTest('/videos/watch/' + servers[0].video.uuid)
await watchVideoPageTest('/w/' + servers[0].video.uuid)
await watchVideoPageTest('/w/' + servers[0].video.id)
})
it('Should have valid Open Graph tags on the watch playlist page', async function () {
const res = await request(servers[0].url)
.get('/videos/watch/playlist/' + playlistUUID)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200)
expect(res.text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
expect(res.text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
expect(res.text).to.contain('<meta property="og:type" content="video" />')
expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/videos/watch/playlist/${playlistUUID}" />`)
await watchPlaylistPageTest('/videos/watch/playlist/' + playlistUUID)
await watchPlaylistPageTest('/w/p/' + playlistUUID)
})
})
@@ -238,28 +242,36 @@ describe('Test a client controllers', function () {
expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
}
it('Should have valid twitter card on the watch video page', async function () {
const res = await request(servers[0].url)
.get('/videos/watch/' + servers[0].video.uuid)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200)
async function watchVideoPageTest (path: string) {
const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
const text = res.text
expect(res.text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
expect(res.text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
expect(res.text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
expect(text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
}
async function watchPlaylistPageTest (path: string) {
const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
const text = res.text
expect(text).to.contain('<meta property="twitter:card" content="summary" />')
expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
}
it('Should have valid twitter card on the watch video page', async function () {
await watchVideoPageTest('/videos/watch/' + servers[0].video.id)
await watchVideoPageTest('/videos/watch/' + servers[0].video.uuid)
await watchVideoPageTest('/w/' + servers[0].video.uuid)
await watchVideoPageTest('/w/' + servers[0].video.id)
})
it('Should have valid twitter card on the watch playlist page', async function () {
const res = await request(servers[0].url)
.get('/videos/watch/playlist/' + playlistUUID)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200)
expect(res.text).to.contain('<meta property="twitter:card" content="summary" />')
expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
expect(res.text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
expect(res.text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
await watchPlaylistPageTest('/videos/watch/playlist/' + playlistUUID)
await watchPlaylistPageTest('/w/p/' + playlistUUID)
})
it('Should have valid twitter card on the account page', async function () {
@@ -304,24 +316,32 @@ describe('Test a client controllers', function () {
expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
}
it('Should have valid twitter card on the watch video page', async function () {
const res = await request(servers[0].url)
.get('/videos/watch/' + servers[0].video.uuid)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200)
async function watchVideoPageTest (path: string) {
const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
const text = res.text
expect(res.text).to.contain('<meta property="twitter:card" content="player" />')
expect(res.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
expect(text).to.contain('<meta property="twitter:card" content="player" />')
expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
}
async function watchPlaylistPageTest (path: string) {
const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
const text = res.text
expect(text).to.contain('<meta property="twitter:card" content="player" />')
expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
}
it('Should have valid twitter card on the watch video page', async function () {
await watchVideoPageTest('/videos/watch/' + servers[0].video.id)
await watchVideoPageTest('/videos/watch/' + servers[0].video.uuid)
await watchVideoPageTest('/w/' + servers[0].video.uuid)
await watchVideoPageTest('/w/' + servers[0].video.id)
})
it('Should have valid twitter card on the watch playlist page', async function () {
const res = await request(servers[0].url)
.get('/videos/watch/playlist/' + playlistUUID)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200)
expect(res.text).to.contain('<meta property="twitter:card" content="player" />')
expect(res.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
await watchPlaylistPageTest('/videos/watch/playlist/' + playlistUUID)
await watchPlaylistPageTest('/w/p/' + playlistUUID)
})
it('Should have valid twitter card on the account page', async function () {
@@ -378,8 +398,10 @@ describe('Test a client controllers', function () {
})
it('Should use the original video URL for the canonical tag', async function () {
const res = await makeHTMLRequest(servers[1].url, '/videos/watch/' + servers[0].video.uuid)
expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`)
for (const basePath of watchVideoBasePaths) {
const res = await makeHTMLRequest(servers[1].url, basePath + servers[0].video.uuid)
expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`)
}
})
it('Should use the original account URL for the canonical tag', async function () {
@@ -403,8 +425,10 @@ describe('Test a client controllers', function () {
})
it('Should use the original playlist URL for the canonical tag', async function () {
const res = await makeHTMLRequest(servers[1].url, '/videos/watch/playlist/' + playlistUUID)
expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlistUUID}" />`)
for (const basePath of watchPlaylistBasePaths) {
const res = await makeHTMLRequest(servers[1].url, basePath + playlistUUID)
expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlistUUID}" />`)
}
})
})

View File

@@ -30,7 +30,7 @@ function run (url: string, options: program.OptionValues) {
const cmd = 'node ' + join(__dirname, 'node_modules', 'webtorrent-hybrid', 'bin', 'cmd.js')
const args = ` --${options.gui} ` +
url.replace('videos/watch', 'download/torrents') +
url.replace(/(\/videos\/watch\/)|\/w\//, '/download/torrents/') +
`-${options.resolution}.torrent`
try {