Handle playlist oembed
This commit is contained in:
parent
a75292db78
commit
6fad8e51c4
|
@ -438,7 +438,7 @@ export class PeerTubeEmbed {
|
||||||
return videoInfo
|
return videoInfo
|
||||||
})
|
})
|
||||||
|
|
||||||
const [ videoInfo, serverTranslations, captionsResponse, config, PeertubePlayerManagerModule ] = await Promise.all([
|
const [ videoInfoTmp, serverTranslations, captionsResponse, config, PeertubePlayerManagerModule ] = await Promise.all([
|
||||||
videoInfoPromise,
|
videoInfoPromise,
|
||||||
this.translationsPromise,
|
this.translationsPromise,
|
||||||
captionsPromise,
|
captionsPromise,
|
||||||
|
@ -446,6 +446,8 @@ export class PeerTubeEmbed {
|
||||||
this.PeertubePlayerManagerModulePromise
|
this.PeertubePlayerManagerModulePromise
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const videoInfo: VideoDetails = videoInfoTmp
|
||||||
|
|
||||||
const PeertubePlayerManager = PeertubePlayerManagerModule.PeertubePlayerManager
|
const PeertubePlayerManager = PeertubePlayerManagerModule.PeertubePlayerManager
|
||||||
const videoCaptions = await this.buildCaptions(serverTranslations, captionsResponse)
|
const videoCaptions = await this.buildCaptions(serverTranslations, captionsResponse)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
import { EMBED_SIZE, PREVIEWS_SIZE, WEBSERVER } from '../initializers/constants'
|
import { EMBED_SIZE, PREVIEWS_SIZE, WEBSERVER, THUMBNAILS_SIZE } from '../initializers/constants'
|
||||||
import { asyncMiddleware, oembedValidator } from '../middlewares'
|
import { asyncMiddleware, oembedValidator } from '../middlewares'
|
||||||
import { accountNameWithHostGetValidator } from '../middlewares/validators'
|
import { accountNameWithHostGetValidator } from '../middlewares/validators'
|
||||||
|
import { MChannelSummary } from '@server/types/models'
|
||||||
|
|
||||||
const servicesRouter = express.Router()
|
const servicesRouter = express.Router()
|
||||||
|
|
||||||
|
@ -23,23 +24,73 @@ export {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function generateOEmbed (req: express.Request, res: express.Response) {
|
function generateOEmbed (req: express.Request, res: express.Response) {
|
||||||
|
if (res.locals.videoAll) return generateVideoOEmbed(req, res)
|
||||||
|
|
||||||
|
return generatePlaylistOEmbed(req, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePlaylistOEmbed (req: express.Request, res: express.Response) {
|
||||||
|
const playlist = res.locals.videoPlaylistSummary
|
||||||
|
|
||||||
|
const json = buildOEmbed({
|
||||||
|
channel: playlist.VideoChannel,
|
||||||
|
title: playlist.name,
|
||||||
|
embedPath: playlist.getEmbedStaticPath(),
|
||||||
|
previewPath: playlist.getThumbnailStaticPath(),
|
||||||
|
previewSize: THUMBNAILS_SIZE,
|
||||||
|
req
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.json(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateVideoOEmbed (req: express.Request, res: express.Response) {
|
||||||
const video = res.locals.videoAll
|
const video = res.locals.videoAll
|
||||||
|
|
||||||
|
const json = buildOEmbed({
|
||||||
|
channel: video.VideoChannel,
|
||||||
|
title: video.name,
|
||||||
|
embedPath: video.getEmbedStaticPath(),
|
||||||
|
previewPath: video.getPreviewStaticPath(),
|
||||||
|
previewSize: PREVIEWS_SIZE,
|
||||||
|
req
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.json(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildOEmbed (options: {
|
||||||
|
req: express.Request
|
||||||
|
title: string
|
||||||
|
channel: MChannelSummary
|
||||||
|
previewPath: string | null
|
||||||
|
embedPath: string
|
||||||
|
previewSize: {
|
||||||
|
height: number
|
||||||
|
width: number
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
const { req, previewSize, previewPath, title, channel, embedPath } = options
|
||||||
|
|
||||||
const webserverUrl = WEBSERVER.URL
|
const webserverUrl = WEBSERVER.URL
|
||||||
const maxHeight = parseInt(req.query.maxheight, 10)
|
const maxHeight = parseInt(req.query.maxheight, 10)
|
||||||
const maxWidth = parseInt(req.query.maxwidth, 10)
|
const maxWidth = parseInt(req.query.maxwidth, 10)
|
||||||
|
|
||||||
const embedUrl = webserverUrl + video.getEmbedStaticPath()
|
const embedUrl = webserverUrl + embedPath
|
||||||
let thumbnailUrl = webserverUrl + video.getPreviewStaticPath()
|
|
||||||
let embedWidth = EMBED_SIZE.width
|
let embedWidth = EMBED_SIZE.width
|
||||||
let embedHeight = EMBED_SIZE.height
|
let embedHeight = EMBED_SIZE.height
|
||||||
|
|
||||||
|
let thumbnailUrl = previewPath
|
||||||
|
? webserverUrl + previewPath
|
||||||
|
: undefined
|
||||||
|
|
||||||
if (maxHeight < embedHeight) embedHeight = maxHeight
|
if (maxHeight < embedHeight) embedHeight = maxHeight
|
||||||
if (maxWidth < embedWidth) embedWidth = maxWidth
|
if (maxWidth < embedWidth) embedWidth = maxWidth
|
||||||
|
|
||||||
// Our thumbnail is too big for the consumer
|
// Our thumbnail is too big for the consumer
|
||||||
if (
|
if (
|
||||||
(maxHeight !== undefined && maxHeight < PREVIEWS_SIZE.height) ||
|
(maxHeight !== undefined && maxHeight < previewSize.height) ||
|
||||||
(maxWidth !== undefined && maxWidth < PREVIEWS_SIZE.width)
|
(maxWidth !== undefined && maxWidth < previewSize.width)
|
||||||
) {
|
) {
|
||||||
thumbnailUrl = undefined
|
thumbnailUrl = undefined
|
||||||
}
|
}
|
||||||
|
@ -53,20 +104,20 @@ function generateOEmbed (req: express.Request, res: express.Response) {
|
||||||
html,
|
html,
|
||||||
width: embedWidth,
|
width: embedWidth,
|
||||||
height: embedHeight,
|
height: embedHeight,
|
||||||
title: video.name,
|
title: title,
|
||||||
author_name: video.VideoChannel.Account.name,
|
author_name: channel.name,
|
||||||
author_url: video.VideoChannel.Account.Actor.url,
|
author_url: channel.Actor.url,
|
||||||
provider_name: 'PeerTube',
|
provider_name: 'PeerTube',
|
||||||
provider_url: webserverUrl
|
provider_url: webserverUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thumbnailUrl !== undefined) {
|
if (thumbnailUrl !== undefined) {
|
||||||
json.thumbnail_url = thumbnailUrl
|
json.thumbnail_url = thumbnailUrl
|
||||||
json.thumbnail_width = PREVIEWS_SIZE.width
|
json.thumbnail_width = previewSize.width
|
||||||
json.thumbnail_height = PREVIEWS_SIZE.height
|
json.thumbnail_height = previewSize.height
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json(json)
|
return json
|
||||||
}
|
}
|
||||||
|
|
||||||
function redirectToAccountUrl (req: express.Request, res: express.Response, next: express.NextFunction) {
|
function redirectToAccountUrl (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
|
|
|
@ -23,6 +23,33 @@ import { CONFIG } from '../initializers/config'
|
||||||
import { logger } from '../helpers/logger'
|
import { logger } from '../helpers/logger'
|
||||||
import { MAccountActor, MChannelActor } from '../types/models'
|
import { MAccountActor, MChannelActor } from '../types/models'
|
||||||
|
|
||||||
|
type Tags = {
|
||||||
|
ogType: string
|
||||||
|
twitterCard: string
|
||||||
|
schemaType: string
|
||||||
|
|
||||||
|
list?: {
|
||||||
|
numberOfItems: number
|
||||||
|
}
|
||||||
|
|
||||||
|
title: string
|
||||||
|
url: string
|
||||||
|
description: string
|
||||||
|
|
||||||
|
embed?: {
|
||||||
|
url: string
|
||||||
|
createdAt: string
|
||||||
|
duration?: string
|
||||||
|
views?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
image: {
|
||||||
|
url: string
|
||||||
|
width?: number
|
||||||
|
height?: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class ClientHtml {
|
export class ClientHtml {
|
||||||
|
|
||||||
private static htmlCache: { [path: string]: string } = {}
|
private static htmlCache: { [path: string]: string } = {}
|
||||||
|
@ -118,15 +145,20 @@ export class ClientHtml {
|
||||||
url: videoPlaylist.getThumbnailUrl()
|
url: videoPlaylist.getThumbnailUrl()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const embed = {
|
||||||
|
url: WEBSERVER.URL + videoPlaylist.getEmbedStaticPath(),
|
||||||
|
createdAt: videoPlaylist.createdAt.toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
const list = {
|
const list = {
|
||||||
numberOfItems: videoPlaylist.get('videosLength')
|
numberOfItems: videoPlaylist.get('videosLength') as number
|
||||||
}
|
}
|
||||||
|
|
||||||
const ogType = 'video'
|
const ogType = 'video'
|
||||||
const twitterCard = 'summary'
|
const twitterCard = CONFIG.SERVICES.TWITTER.WHITELISTED ? 'player' : 'summary'
|
||||||
const schemaType = 'ItemList'
|
const schemaType = 'ItemList'
|
||||||
|
|
||||||
customHtml = ClientHtml.addTags(customHtml, { url, title, description, image, list, ogType, twitterCard, schemaType })
|
customHtml = ClientHtml.addTags(customHtml, { url, embed, title, description, image, list, ogType, twitterCard, schemaType })
|
||||||
|
|
||||||
return customHtml
|
return customHtml
|
||||||
}
|
}
|
||||||
|
@ -268,7 +300,7 @@ export class ClientHtml {
|
||||||
return htmlStringPage.replace('</head>', linkTag + '</head>')
|
return htmlStringPage.replace('</head>', linkTag + '</head>')
|
||||||
}
|
}
|
||||||
|
|
||||||
private static generateOpenGraphMetaTags (tags) {
|
private static generateOpenGraphMetaTags (tags: Tags) {
|
||||||
const metaTags = {
|
const metaTags = {
|
||||||
'og:type': tags.ogType,
|
'og:type': tags.ogType,
|
||||||
'og:title': tags.title,
|
'og:title': tags.title,
|
||||||
|
@ -294,7 +326,7 @@ export class ClientHtml {
|
||||||
return metaTags
|
return metaTags
|
||||||
}
|
}
|
||||||
|
|
||||||
private static generateStandardMetaTags (tags) {
|
private static generateStandardMetaTags (tags: Tags) {
|
||||||
return {
|
return {
|
||||||
name: tags.title,
|
name: tags.title,
|
||||||
description: tags.description,
|
description: tags.description,
|
||||||
|
@ -302,7 +334,7 @@ export class ClientHtml {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static generateTwitterCardMetaTags (tags) {
|
private static generateTwitterCardMetaTags (tags: Tags) {
|
||||||
const metaTags = {
|
const metaTags = {
|
||||||
'twitter:card': tags.twitterCard,
|
'twitter:card': tags.twitterCard,
|
||||||
'twitter:site': CONFIG.SERVICES.TWITTER.USERNAME,
|
'twitter:site': CONFIG.SERVICES.TWITTER.USERNAME,
|
||||||
|
@ -319,7 +351,7 @@ export class ClientHtml {
|
||||||
return metaTags
|
return metaTags
|
||||||
}
|
}
|
||||||
|
|
||||||
private static generateSchemaTags (tags) {
|
private static generateSchemaTags (tags: Tags) {
|
||||||
const schema = {
|
const schema = {
|
||||||
'@context': 'http://schema.org',
|
'@context': 'http://schema.org',
|
||||||
'@type': tags.schemaType,
|
'@type': tags.schemaType,
|
||||||
|
@ -337,8 +369,10 @@ export class ClientHtml {
|
||||||
if (tags.embed) {
|
if (tags.embed) {
|
||||||
schema['embedUrl'] = tags.embed.url
|
schema['embedUrl'] = tags.embed.url
|
||||||
schema['uploadDate'] = tags.embed.createdAt
|
schema['uploadDate'] = tags.embed.createdAt
|
||||||
schema['duration'] = tags.embed.duration
|
|
||||||
schema['iterationCount'] = tags.embed.views
|
if (tags.embed.duration) schema['duration'] = tags.embed.duration
|
||||||
|
if (tags.embed.views) schema['iterationCount'] = tags.embed.views
|
||||||
|
|
||||||
schema['thumbnailUrl'] = tags.image.url
|
schema['thumbnailUrl'] = tags.image.url
|
||||||
schema['contentUrl'] = tags.url
|
schema['contentUrl'] = tags.url
|
||||||
}
|
}
|
||||||
|
@ -346,7 +380,7 @@ export class ClientHtml {
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
|
|
||||||
private static addTags (htmlStringPage: string, tagsValues: any) {
|
private static addTags (htmlStringPage: string, tagsValues: Tags) {
|
||||||
const openGraphMetaTags = this.generateOpenGraphMetaTags(tagsValues)
|
const openGraphMetaTags = this.generateOpenGraphMetaTags(tagsValues)
|
||||||
const standardMetaTags = this.generateStandardMetaTags(tagsValues)
|
const standardMetaTags = this.generateStandardMetaTags(tagsValues)
|
||||||
const twitterCardMetaTags = this.generateTwitterCardMetaTags(tagsValues)
|
const twitterCardMetaTags = this.generateTwitterCardMetaTags(tagsValues)
|
||||||
|
@ -354,7 +388,7 @@ export class ClientHtml {
|
||||||
|
|
||||||
const { url, title, embed } = tagsValues
|
const { url, title, embed } = tagsValues
|
||||||
|
|
||||||
const oembedLinkTags = []
|
const oembedLinkTags: { type: string, href: string, title: string }[] = []
|
||||||
|
|
||||||
if (embed) {
|
if (embed) {
|
||||||
oembedLinkTags.push({
|
oembedLinkTags.push({
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
import { query } from 'express-validator'
|
import { query } from 'express-validator'
|
||||||
import { join } from 'path'
|
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 { isTestInstance } from '../../helpers/core-utils'
|
import { isTestInstance } from '../../helpers/core-utils'
|
||||||
import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
|
import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
import { areValidationErrors } from './utils'
|
|
||||||
import { WEBSERVER } from '../../initializers/constants'
|
import { WEBSERVER } from '../../initializers/constants'
|
||||||
import { doesVideoExist } from '../../helpers/middlewares'
|
import { areValidationErrors } from './utils'
|
||||||
|
|
||||||
const urlShouldStartWith = WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, 'videos', 'watch') + '/'
|
const startVideoPlaylistsURL = WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, 'videos', 'watch', 'playlist') + '/'
|
||||||
const videoWatchRegex = new RegExp('([^/]+)$')
|
const startVideosURL = WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, 'videos', 'watch') + '/'
|
||||||
|
|
||||||
|
const watchRegex = new RegExp('([^/]+)$')
|
||||||
const isURLOptions = {
|
const isURLOptions = {
|
||||||
require_host: true,
|
require_host: true,
|
||||||
require_tld: true
|
require_tld: true
|
||||||
|
@ -34,31 +38,62 @@ const oembedValidator = [
|
||||||
if (req.query.format !== undefined && req.query.format !== 'json') {
|
if (req.query.format !== undefined && req.query.format !== 'json') {
|
||||||
return res.status(501)
|
return res.status(501)
|
||||||
.json({ error: 'Requested format is not implemented on server.' })
|
.json({ error: 'Requested format is not implemented on server.' })
|
||||||
.end()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = req.query.url as string
|
const url = req.query.url as string
|
||||||
|
|
||||||
const startIsOk = url.startsWith(urlShouldStartWith)
|
const isPlaylist = url.startsWith(startVideoPlaylistsURL)
|
||||||
const matches = videoWatchRegex.exec(url)
|
const isVideo = isPlaylist ? false : url.startsWith(startVideosURL)
|
||||||
|
|
||||||
|
const startIsOk = isVideo || isPlaylist
|
||||||
|
|
||||||
|
const matches = watchRegex.exec(url)
|
||||||
|
|
||||||
if (startIsOk === false || matches === null) {
|
if (startIsOk === false || matches === null) {
|
||||||
return res.status(400)
|
return res.status(400)
|
||||||
.json({ error: 'Invalid url.' })
|
.json({ error: 'Invalid url.' })
|
||||||
.end()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const videoId = matches[1]
|
const elementId = matches[1]
|
||||||
if (isIdOrUUIDValid(videoId) === false) {
|
if (isIdOrUUIDValid(elementId) === false) {
|
||||||
return res.status(400)
|
return res.status(400)
|
||||||
.json({ error: 'Invalid video id.' })
|
.json({ error: 'Invalid video or playlist id.' })
|
||||||
.end()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await doesVideoExist(videoId, res)) return
|
if (isVideo) {
|
||||||
|
const video = await fetchVideo(elementId, 'all')
|
||||||
|
|
||||||
|
if (!video) {
|
||||||
|
return res.status(404)
|
||||||
|
.json({ error: 'Video not found' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video.privacy !== VideoPrivacy.PUBLIC) {
|
||||||
|
return res.status(403)
|
||||||
|
.json({ error: 'Video is not public' })
|
||||||
|
}
|
||||||
|
|
||||||
|
res.locals.videoAll = video
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Is playlist
|
||||||
|
|
||||||
|
const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannelSummary(elementId, undefined)
|
||||||
|
if (!videoPlaylist) {
|
||||||
|
return res.status(404)
|
||||||
|
.json({ error: 'Video playlist not found' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoPlaylist.privacy !== VideoPlaylistPrivacy.PUBLIC) {
|
||||||
|
return res.status(403)
|
||||||
|
.json({ error: 'Playlist is not public' })
|
||||||
|
}
|
||||||
|
|
||||||
|
res.locals.videoPlaylistSummary = videoPlaylist
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
@ -494,6 +494,10 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
||||||
return WEBSERVER.URL + '/videos/watch/playlist/' + this.uuid
|
return WEBSERVER.URL + '/videos/watch/playlist/' + this.uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEmbedStaticPath () {
|
||||||
|
return '/video-playlists/embed/' + this.uuid
|
||||||
|
}
|
||||||
|
|
||||||
setAsRefreshed () {
|
setAsRefreshed () {
|
||||||
this.changed('updatedAt', true)
|
this.changed('updatedAt', true)
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,15 @@ import {
|
||||||
makeGetRequest,
|
makeGetRequest,
|
||||||
ServerInfo,
|
ServerInfo,
|
||||||
setAccessTokensToServers,
|
setAccessTokensToServers,
|
||||||
uploadVideo
|
uploadVideo,
|
||||||
|
createVideoPlaylist,
|
||||||
|
setDefaultVideoChannel
|
||||||
} from '../../../../shared/extra-utils'
|
} from '../../../../shared/extra-utils'
|
||||||
|
import { VideoPlaylistPrivacy } from '@shared/models'
|
||||||
|
|
||||||
describe('Test services API validators', function () {
|
describe('Test services API validators', function () {
|
||||||
let server: ServerInfo
|
let server: ServerInfo
|
||||||
|
let playlistUUID: string
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -21,9 +25,26 @@ describe('Test services API validators', function () {
|
||||||
|
|
||||||
server = await flushAndRunServer(1)
|
server = await flushAndRunServer(1)
|
||||||
await setAccessTokensToServers([ server ])
|
await setAccessTokensToServers([ server ])
|
||||||
|
await setDefaultVideoChannel([ server ])
|
||||||
|
|
||||||
|
{
|
||||||
const res = await uploadVideo(server.url, server.accessToken, { name: 'my super name' })
|
const res = await uploadVideo(server.url, server.accessToken, { name: 'my super name' })
|
||||||
server.video = res.body.video
|
server.video = res.body.video
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const res = await createVideoPlaylist({
|
||||||
|
url: server.url,
|
||||||
|
token: server.accessToken,
|
||||||
|
playlistAttrs: {
|
||||||
|
displayName: 'super playlist',
|
||||||
|
privacy: VideoPlaylistPrivacy.PUBLIC,
|
||||||
|
videoChannelId: server.videoChannel.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
playlistUUID = res.body.videoPlaylist.uuid
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Test oEmbed API validators', function () {
|
describe('Test oEmbed API validators', function () {
|
||||||
|
@ -38,12 +59,12 @@ describe('Test services API validators', function () {
|
||||||
await checkParamEmbed(server, embedUrl)
|
await checkParamEmbed(server, embedUrl)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should fail with an invalid video id', async function () {
|
it('Should fail with an invalid element id', async function () {
|
||||||
const embedUrl = `http://localhost:${server.port}/videos/watch/blabla`
|
const embedUrl = `http://localhost:${server.port}/videos/watch/blabla`
|
||||||
await checkParamEmbed(server, embedUrl)
|
await checkParamEmbed(server, embedUrl)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should fail with an unknown video', async function () {
|
it('Should fail with an unknown element', async function () {
|
||||||
const embedUrl = `http://localhost:${server.port}/videos/watch/88fc0165-d1f0-4a35-a51a-3b47f668689c`
|
const embedUrl = `http://localhost:${server.port}/videos/watch/88fc0165-d1f0-4a35-a51a-3b47f668689c`
|
||||||
await checkParamEmbed(server, embedUrl, 404)
|
await checkParamEmbed(server, embedUrl, 404)
|
||||||
})
|
})
|
||||||
|
@ -78,7 +99,7 @@ describe('Test services API validators', function () {
|
||||||
await checkParamEmbed(server, embedUrl, 501, { format: 'xml' })
|
await checkParamEmbed(server, embedUrl, 501, { format: 'xml' })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should succeed with the correct params', async function () {
|
it('Should succeed with the correct params with a video', async function () {
|
||||||
const embedUrl = `http://localhost:${server.port}/videos/watch/${server.video.uuid}`
|
const embedUrl = `http://localhost:${server.port}/videos/watch/${server.video.uuid}`
|
||||||
const query = {
|
const query = {
|
||||||
format: 'json',
|
format: 'json',
|
||||||
|
@ -88,6 +109,17 @@ describe('Test services API validators', function () {
|
||||||
|
|
||||||
await checkParamEmbed(server, embedUrl, 200, query)
|
await checkParamEmbed(server, embedUrl, 200, query)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should succeed with the correct params with a playlist', async function () {
|
||||||
|
const embedUrl = `http://localhost:${server.port}/videos/watch/playlist/${playlistUUID}`
|
||||||
|
const query = {
|
||||||
|
format: 'json',
|
||||||
|
maxheight: 400,
|
||||||
|
maxwidth: 400
|
||||||
|
}
|
||||||
|
|
||||||
|
await checkParamEmbed(server, embedUrl, 200, query)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
after(async function () {
|
after(async function () {
|
||||||
|
|
|
@ -1,14 +1,25 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||||
|
|
||||||
import * as chai from 'chai'
|
|
||||||
import 'mocha'
|
import 'mocha'
|
||||||
import { getOEmbed, getVideosList, ServerInfo, setAccessTokensToServers, uploadVideo } from '../../../../shared/extra-utils/index'
|
import * as chai from 'chai'
|
||||||
|
import {
|
||||||
|
getOEmbed,
|
||||||
|
getVideosList,
|
||||||
|
ServerInfo,
|
||||||
|
setAccessTokensToServers,
|
||||||
|
setDefaultVideoChannel,
|
||||||
|
uploadVideo,
|
||||||
|
createVideoPlaylist,
|
||||||
|
addVideoInPlaylist
|
||||||
|
} from '../../../../shared/extra-utils'
|
||||||
import { cleanupTests, flushAndRunServer } from '../../../../shared/extra-utils/server/servers'
|
import { cleanupTests, flushAndRunServer } from '../../../../shared/extra-utils/server/servers'
|
||||||
|
import { VideoPlaylistPrivacy } from '@shared/models'
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
|
||||||
describe('Test services', function () {
|
describe('Test services', function () {
|
||||||
let server: ServerInfo = null
|
let server: ServerInfo = null
|
||||||
|
let playlistUUID: string
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
this.timeout(30000)
|
this.timeout(30000)
|
||||||
|
@ -16,7 +27,9 @@ describe('Test services', function () {
|
||||||
server = await flushAndRunServer(1)
|
server = await flushAndRunServer(1)
|
||||||
|
|
||||||
await setAccessTokensToServers([ server ])
|
await setAccessTokensToServers([ server ])
|
||||||
|
await setDefaultVideoChannel([ server ])
|
||||||
|
|
||||||
|
{
|
||||||
const videoAttributes = {
|
const videoAttributes = {
|
||||||
name: 'my super name'
|
name: 'my super name'
|
||||||
}
|
}
|
||||||
|
@ -24,9 +37,33 @@ describe('Test services', function () {
|
||||||
|
|
||||||
const res = await getVideosList(server.url)
|
const res = await getVideosList(server.url)
|
||||||
server.video = res.body.data[0]
|
server.video = res.body.data[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const res = await createVideoPlaylist({
|
||||||
|
url: server.url,
|
||||||
|
token: server.accessToken,
|
||||||
|
playlistAttrs: {
|
||||||
|
displayName: 'The Life and Times of Scrooge McDuck',
|
||||||
|
privacy: VideoPlaylistPrivacy.PUBLIC,
|
||||||
|
videoChannelId: server.videoChannel.id
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should have a valid oEmbed response', async function () {
|
playlistUUID = res.body.videoPlaylist.uuid
|
||||||
|
|
||||||
|
await addVideoInPlaylist({
|
||||||
|
url: server.url,
|
||||||
|
token: server.accessToken,
|
||||||
|
playlistId: res.body.videoPlaylist.id,
|
||||||
|
elementAttrs: {
|
||||||
|
videoId: server.video.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have a valid oEmbed video response', async function () {
|
||||||
const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + server.video.uuid
|
const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + server.video.uuid
|
||||||
|
|
||||||
const res = await getOEmbed(server.url, oembedUrl)
|
const res = await getOEmbed(server.url, oembedUrl)
|
||||||
|
@ -37,7 +74,7 @@ describe('Test services', function () {
|
||||||
|
|
||||||
expect(res.body.html).to.equal(expectedHtml)
|
expect(res.body.html).to.equal(expectedHtml)
|
||||||
expect(res.body.title).to.equal(server.video.name)
|
expect(res.body.title).to.equal(server.video.name)
|
||||||
expect(res.body.author_name).to.equal(server.video.account.name)
|
expect(res.body.author_name).to.equal(server.videoChannel.displayName)
|
||||||
expect(res.body.width).to.equal(560)
|
expect(res.body.width).to.equal(560)
|
||||||
expect(res.body.height).to.equal(315)
|
expect(res.body.height).to.equal(315)
|
||||||
expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl)
|
expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl)
|
||||||
|
@ -45,6 +82,24 @@ describe('Test services', function () {
|
||||||
expect(res.body.thumbnail_height).to.equal(480)
|
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
|
||||||
|
|
||||||
|
const res = await getOEmbed(server.url, oembedUrl)
|
||||||
|
const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' +
|
||||||
|
`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(223)
|
||||||
|
expect(res.body.thumbnail_height).to.equal(122)
|
||||||
|
})
|
||||||
|
|
||||||
it('Should have a valid oEmbed response with small max height query', async function () {
|
it('Should have a valid oEmbed response with small max height query', async function () {
|
||||||
const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + server.video.uuid
|
const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + server.video.uuid
|
||||||
const format = 'json'
|
const format = 'json'
|
||||||
|
@ -58,7 +113,7 @@ describe('Test services', function () {
|
||||||
|
|
||||||
expect(res.body.html).to.equal(expectedHtml)
|
expect(res.body.html).to.equal(expectedHtml)
|
||||||
expect(res.body.title).to.equal(server.video.name)
|
expect(res.body.title).to.equal(server.video.name)
|
||||||
expect(res.body.author_name).to.equal(server.video.account.name)
|
expect(res.body.author_name).to.equal(server.videoChannel.displayName)
|
||||||
expect(res.body.height).to.equal(50)
|
expect(res.body.height).to.equal(50)
|
||||||
expect(res.body.width).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_url')
|
||||||
|
|
|
@ -94,6 +94,65 @@ describe('Test a client controllers', function () {
|
||||||
account = resAccountRequest.body
|
account = resAccountRequest.body
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('oEmbed', function () {
|
||||||
|
it('Should have valid oEmbed discovery tags for videos', async function () {
|
||||||
|
const path = '/videos/watch/' + server.video.uuid
|
||||||
|
const res = await request(server.url)
|
||||||
|
.get(path)
|
||||||
|
.set('Accept', 'text/html')
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
const port = server.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${server.video.uuid}" ` +
|
||||||
|
`title="${server.video.name}" />`
|
||||||
|
|
||||||
|
expect(res.text).to.contain(expectedLink)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have valid oEmbed discovery tags for a playlist', async function () {
|
||||||
|
const res = await request(server.url)
|
||||||
|
.get('/videos/watch/playlist/' + playlistUUID)
|
||||||
|
.set('Accept', 'text/html')
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
const port = server.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}" />`
|
||||||
|
|
||||||
|
expect(res.text).to.contain(expectedLink)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Open Graph', function () {
|
||||||
|
|
||||||
|
it('Should have valid Open Graph tags on the account page', async function () {
|
||||||
|
const res = await request(server.url)
|
||||||
|
.get('/accounts/' + server.user.username)
|
||||||
|
.set('Accept', 'text/html')
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(res.text).to.contain(`<meta property="og:title" content="${account.displayName}" />`)
|
||||||
|
expect(res.text).to.contain(`<meta property="og:description" content="${account.description}" />`)
|
||||||
|
expect(res.text).to.contain('<meta property="og:type" content="website" />')
|
||||||
|
expect(res.text).to.contain(`<meta property="og:url" content="${server.url}/accounts/${server.user.username}" />`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have valid Open Graph tags on the channel page', async function () {
|
||||||
|
const res = await request(server.url)
|
||||||
|
.get('/video-channels/' + server.videoChannel.name)
|
||||||
|
.set('Accept', 'text/html')
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(res.text).to.contain(`<meta property="og:title" content="${server.videoChannel.displayName}" />`)
|
||||||
|
expect(res.text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
|
||||||
|
expect(res.text).to.contain('<meta property="og:type" content="website" />')
|
||||||
|
expect(res.text).to.contain(`<meta property="og:url" content="${server.url}/video-channels/${server.videoChannel.name}" />`)
|
||||||
|
})
|
||||||
|
|
||||||
it('Should have valid Open Graph tags on the watch page with video id', async function () {
|
it('Should have valid Open Graph tags on the watch page with video id', async function () {
|
||||||
const res = await request(server.url)
|
const res = await request(server.url)
|
||||||
.get('/videos/watch/' + server.video.id)
|
.get('/videos/watch/' + server.video.id)
|
||||||
|
@ -129,46 +188,9 @@ describe('Test a client controllers', function () {
|
||||||
expect(res.text).to.contain('<meta property="og:type" content="video" />')
|
expect(res.text).to.contain('<meta property="og:type" content="video" />')
|
||||||
expect(res.text).to.contain(`<meta property="og:url" content="${server.url}/videos/watch/playlist/${playlistUUID}" />`)
|
expect(res.text).to.contain(`<meta property="og:url" content="${server.url}/videos/watch/playlist/${playlistUUID}" />`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should have valid Open Graph tags on the account page', async function () {
|
|
||||||
const res = await request(server.url)
|
|
||||||
.get('/accounts/' + server.user.username)
|
|
||||||
.set('Accept', 'text/html')
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
expect(res.text).to.contain(`<meta property="og:title" content="${account.displayName}" />`)
|
|
||||||
expect(res.text).to.contain(`<meta property="og:description" content="${account.description}" />`)
|
|
||||||
expect(res.text).to.contain('<meta property="og:type" content="website" />')
|
|
||||||
expect(res.text).to.contain(`<meta property="og:url" content="${server.url}/accounts/${server.user.username}" />`)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should have valid Open Graph tags on the channel page', async function () {
|
describe('Twitter card', async function () {
|
||||||
const res = await request(server.url)
|
|
||||||
.get('/video-channels/' + server.videoChannel.name)
|
|
||||||
.set('Accept', 'text/html')
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
expect(res.text).to.contain(`<meta property="og:title" content="${server.videoChannel.displayName}" />`)
|
|
||||||
expect(res.text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
|
|
||||||
expect(res.text).to.contain('<meta property="og:type" content="website" />')
|
|
||||||
expect(res.text).to.contain(`<meta property="og:url" content="${server.url}/video-channels/${server.videoChannel.name}" />`)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should have valid oEmbed discovery tags', async function () {
|
|
||||||
const path = '/videos/watch/' + server.video.uuid
|
|
||||||
const res = await request(server.url)
|
|
||||||
.get(path)
|
|
||||||
.set('Accept', 'text/html')
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
const port = server.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${server.video.uuid}" ` +
|
|
||||||
`title="${server.video.name}" />`
|
|
||||||
|
|
||||||
expect(res.text).to.contain(expectedLink)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should have valid twitter card on the watch video page', async function () {
|
it('Should have valid twitter card on the watch video page', async function () {
|
||||||
const res = await request(server.url)
|
const res = await request(server.url)
|
||||||
|
@ -240,7 +262,7 @@ describe('Test a client controllers', function () {
|
||||||
.set('Accept', 'text/html')
|
.set('Accept', 'text/html')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(resVideoPlaylistRequest.text).to.contain('<meta property="twitter:card" content="summary" />')
|
expect(resVideoPlaylistRequest.text).to.contain('<meta property="twitter:card" content="player" />')
|
||||||
expect(resVideoPlaylistRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
|
expect(resVideoPlaylistRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
|
||||||
|
|
||||||
const resAccountRequest = await request(server.url)
|
const resAccountRequest = await request(server.url)
|
||||||
|
@ -259,6 +281,9 @@ describe('Test a client controllers', function () {
|
||||||
expect(resChannelRequest.text).to.contain('<meta property="twitter:card" content="summary" />')
|
expect(resChannelRequest.text).to.contain('<meta property="twitter:card" content="summary" />')
|
||||||
expect(resChannelRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
|
expect(resChannelRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Index HTML', function () {
|
||||||
|
|
||||||
it('Should have valid index html tags (title, description...)', async function () {
|
it('Should have valid index html tags (title, description...)', async function () {
|
||||||
const res = await makeHTMLRequest(server.url, '/videos/trending')
|
const res = await makeHTMLRequest(server.url, '/videos/trending')
|
||||||
|
@ -293,6 +318,7 @@ describe('Test a client controllers', function () {
|
||||||
|
|
||||||
checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }')
|
checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }')
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
after(async function () {
|
after(async function () {
|
||||||
await cleanupTests([ server ])
|
await cleanupTests([ server ])
|
||||||
|
|
Loading…
Reference in New Issue