aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKim <1877318+kimsible@users.noreply.github.com>2020-07-31 11:29:15 +0200
committerGitHub <noreply@github.com>2020-07-31 11:29:15 +0200
commit8d987ec63e6888c839ad55938d45809869c517c6 (patch)
treed6a82b9254c1c473094ee9371688661f2ae6eef3
parent7b3909644dd7cb8be1caad537bb40605e5f059d4 (diff)
downloadPeerTube-8d987ec63e6888c839ad55938d45809869c517c6.tar.gz
PeerTube-8d987ec63e6888c839ad55938d45809869c517c6.tar.zst
PeerTube-8d987ec63e6888c839ad55938d45809869c517c6.zip
Add fcbk open-graph and twitter-card metas for accounts, video-channels, playlists urls (#2996)
* Add open-graph and twitter-card metas to accounts and video-channels * Add open-graph and twitter-card to video-playlists watch view * Refactor meta-tags creation server-side * Add client.ts tests for account, channel and playlist tags * Correct lint forbidden spaces * Correct test regression on client.ts Co-authored-by: kimsible <kimsible@users.noreply.github.com>
-rw-r--r--server/controllers/client.ts7
-rw-r--r--server/lib/client-html.ts258
-rw-r--r--server/models/video/video-playlist.ts4
-rw-r--r--server/tests/client.ts181
4 files changed, 371 insertions, 79 deletions
diff --git a/server/controllers/client.ts b/server/controllers/client.ts
index 88f51907b..8c7f881a9 100644
--- a/server/controllers/client.ts
+++ b/server/controllers/client.ts
@@ -17,6 +17,7 @@ const testEmbedPath = join(distPath, 'standalone', 'videos', 'test-embed.html')
17 17
18// Special route that add OpenGraph and oEmbed tags 18// Special route that add OpenGraph and oEmbed tags
19// Do not use a template engine for a so little thing 19// Do not use a template engine for a so little thing
20clientsRouter.use('/videos/watch/playlist/:id', asyncMiddleware(generateWatchPlaylistHtmlPage))
20clientsRouter.use('/videos/watch/:id', asyncMiddleware(generateWatchHtmlPage)) 21clientsRouter.use('/videos/watch/:id', asyncMiddleware(generateWatchHtmlPage))
21clientsRouter.use('/accounts/:nameWithHost', asyncMiddleware(generateAccountHtmlPage)) 22clientsRouter.use('/accounts/:nameWithHost', asyncMiddleware(generateAccountHtmlPage))
22clientsRouter.use('/video-channels/:nameWithHost', asyncMiddleware(generateVideoChannelHtmlPage)) 23clientsRouter.use('/video-channels/:nameWithHost', asyncMiddleware(generateVideoChannelHtmlPage))
@@ -134,6 +135,12 @@ async function generateWatchHtmlPage (req: express.Request, res: express.Respons
134 return sendHTML(html, res) 135 return sendHTML(html, res)
135} 136}
136 137
138async function generateWatchPlaylistHtmlPage (req: express.Request, res: express.Response) {
139 const html = await ClientHtml.getWatchPlaylistHTMLPage(req.params.id + '', req, res)
140
141 return sendHTML(html, res)
142}
143
137async function generateAccountHtmlPage (req: express.Request, res: express.Response) { 144async function generateAccountHtmlPage (req: express.Request, res: express.Response) {
138 const html = await ClientHtml.getAccountHTMLPage(req.params.nameWithHost, req, res) 145 const html = await ClientHtml.getAccountHTMLPage(req.params.nameWithHost, req, res)
139 146
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts
index ca76825cd..ffe53d0d5 100644
--- a/server/lib/client-html.ts
+++ b/server/lib/client-html.ts
@@ -1,11 +1,19 @@
1import * as express from 'express' 1import * as express from 'express'
2import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n' 2import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n'
3import { CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE, PLUGIN_GLOBAL_CSS_PATH, WEBSERVER, FILES_CONTENT_HASH } from '../initializers/constants' 3import {
4 AVATARS_SIZE,
5 CUSTOM_HTML_TAG_COMMENTS,
6 EMBED_SIZE,
7 PLUGIN_GLOBAL_CSS_PATH,
8 WEBSERVER,
9 FILES_CONTENT_HASH
10} from '../initializers/constants'
4import { join } from 'path' 11import { join } from 'path'
5import { escapeHTML, sha256 } from '../helpers/core-utils' 12import { escapeHTML, sha256 } from '../helpers/core-utils'
6import { VideoModel } from '../models/video/video' 13import { VideoModel } from '../models/video/video'
14import { VideoPlaylistModel } from '../models/video/video-playlist'
7import validator from 'validator' 15import validator from 'validator'
8import { VideoPrivacy } from '../../shared/models/videos' 16import { VideoPrivacy, VideoPlaylistPrivacy } from '../../shared/models/videos'
9import { readFile } from 'fs-extra' 17import { readFile } from 'fs-extra'
10import { getActivityStreamDuration } from '../models/video/video-format-utils' 18import { getActivityStreamDuration } from '../models/video/video-format-utils'
11import { AccountModel } from '../models/account/account' 19import { AccountModel } from '../models/account/account'
@@ -13,7 +21,7 @@ import { VideoChannelModel } from '../models/video/video-channel'
13import * as Bluebird from 'bluebird' 21import * as Bluebird from 'bluebird'
14import { CONFIG } from '../initializers/config' 22import { CONFIG } from '../initializers/config'
15import { logger } from '../helpers/logger' 23import { logger } from '../helpers/logger'
16import { MAccountActor, MChannelActor, MVideo } from '../types/models' 24import { MAccountActor, MChannelActor } from '../types/models'
17 25
18export class ClientHtml { 26export class ClientHtml {
19 27
@@ -56,7 +64,69 @@ export class ClientHtml {
56 64
57 let customHtml = ClientHtml.addTitleTag(html, escapeHTML(video.name)) 65 let customHtml = ClientHtml.addTitleTag(html, escapeHTML(video.name))
58 customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(video.description)) 66 customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(video.description))
59 customHtml = ClientHtml.addVideoOpenGraphAndOEmbedTags(customHtml, video) 67
68 const url = WEBSERVER.URL + video.getWatchStaticPath()
69 const title = escapeHTML(video.name)
70 const description = escapeHTML(video.description)
71
72 const image = {
73 url: WEBSERVER.URL + video.getPreviewStaticPath()
74 }
75
76 const embed = {
77 url: WEBSERVER.URL + video.getEmbedStaticPath(),
78 createdAt: video.createdAt.toISOString(),
79 duration: getActivityStreamDuration(video.duration),
80 views: video.views
81 }
82
83 const ogType = 'video'
84 const twitterCard = CONFIG.SERVICES.TWITTER.WHITELISTED ? 'player' : 'summary_large_image'
85 const schemaType = 'VideoObject'
86
87 customHtml = ClientHtml.addTags(customHtml, { url, title, description, image, embed, ogType, twitterCard, schemaType })
88
89 return customHtml
90 }
91
92 static async getWatchPlaylistHTMLPage (videoPlaylistId: string, req: express.Request, res: express.Response) {
93 // Let Angular application handle errors
94 if (!validator.isInt(videoPlaylistId) && !validator.isUUID(videoPlaylistId, 4)) {
95 res.status(404)
96 return ClientHtml.getIndexHTML(req, res)
97 }
98
99 const [ html, videoPlaylist ] = await Promise.all([
100 ClientHtml.getIndexHTML(req, res),
101 VideoPlaylistModel.loadWithAccountAndChannel(videoPlaylistId, null)
102 ])
103
104 // Let Angular application handle errors
105 if (!videoPlaylist || videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) {
106 res.status(404)
107 return html
108 }
109
110 let customHtml = ClientHtml.addTitleTag(html, escapeHTML(videoPlaylist.name))
111 customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(videoPlaylist.description))
112
113 const url = videoPlaylist.getWatchUrl()
114 const title = escapeHTML(videoPlaylist.name)
115 const description = escapeHTML(videoPlaylist.description)
116
117 const image = {
118 url: videoPlaylist.getThumbnailUrl()
119 }
120
121 const list = {
122 numberOfItems: videoPlaylist.get('videosLength')
123 }
124
125 const ogType = 'video'
126 const twitterCard = 'summary'
127 const schemaType = 'ItemList'
128
129 customHtml = ClientHtml.addTags(customHtml, { url, title, description, image, list, ogType, twitterCard, schemaType })
60 130
61 return customHtml 131 return customHtml
62 } 132 }
@@ -87,7 +157,22 @@ export class ClientHtml {
87 157
88 let customHtml = ClientHtml.addTitleTag(html, escapeHTML(entity.getDisplayName())) 158 let customHtml = ClientHtml.addTitleTag(html, escapeHTML(entity.getDisplayName()))
89 customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(entity.description)) 159 customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(entity.description))
90 customHtml = ClientHtml.addAccountOrChannelMetaTags(customHtml, entity) 160
161 const url = entity.Actor.url
162 const title = escapeHTML(entity.getDisplayName())
163 const description = escapeHTML(entity.description)
164
165 const image = {
166 url: entity.Actor.getAvatarUrl(),
167 width: AVATARS_SIZE.width,
168 height: AVATARS_SIZE.height
169 }
170
171 const ogType = 'website'
172 const twitterCard = 'summary'
173 const schemaType = 'ProfilePage'
174
175 customHtml = ClientHtml.addTags(customHtml, { url, title, description, image, ogType, twitterCard, schemaType })
91 176
92 return customHtml 177 return customHtml
93 } 178 }
@@ -183,60 +268,100 @@ export class ClientHtml {
183 return htmlStringPage.replace('</head>', linkTag + '</head>') 268 return htmlStringPage.replace('</head>', linkTag + '</head>')
184 } 269 }
185 270
186 private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: MVideo) { 271 private static generateOpenGraphMetaTags (tags) {
187 const previewUrl = WEBSERVER.URL + video.getPreviewStaticPath() 272 const metaTags = {
188 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() 273 'og:type': tags.ogType,
274 'og:title': tags.title,
275 'og:image': tags.image.url
276 }
277
278 if (tags.image.width && tags.image.height) {
279 metaTags['og:image:width'] = tags.image.width
280 metaTags['og:image:height'] = tags.image.height
281 }
189 282
190 const videoNameEscaped = escapeHTML(video.name) 283 metaTags['og:url'] = tags.url
191 const videoDescriptionEscaped = escapeHTML(video.description) 284 metaTags['og:description'] = tags.description
192 const embedUrl = WEBSERVER.URL + video.getEmbedStaticPath()
193 285
194 const openGraphMetaTags = { 286 if (tags.embed) {
195 'og:type': 'video', 287 metaTags['og:video:url'] = tags.embed.url
196 'og:title': videoNameEscaped, 288 metaTags['og:video:secure_url'] = tags.embed.url
197 'og:image': previewUrl, 289 metaTags['og:video:type'] = 'text/html'
198 'og:url': videoUrl, 290 metaTags['og:video:width'] = EMBED_SIZE.width
199 'og:description': videoDescriptionEscaped, 291 metaTags['og:video:height'] = EMBED_SIZE.height
292 }
200 293
201 'og:video:url': embedUrl, 294 return metaTags
202 'og:video:secure_url': embedUrl, 295 }
203 'og:video:type': 'text/html',
204 'og:video:width': EMBED_SIZE.width,
205 'og:video:height': EMBED_SIZE.height,
206 296
207 'name': videoNameEscaped, 297 private static generateStandardMetaTags (tags) {
208 'description': videoDescriptionEscaped, 298 return {
209 'image': previewUrl, 299 name: tags.title,
300 description: tags.description,
301 image: tags.image.url
302 }
303 }
210 304
211 'twitter:card': CONFIG.SERVICES.TWITTER.WHITELISTED ? 'player' : 'summary_large_image', 305 private static generateTwitterCardMetaTags (tags) {
306 const metaTags = {
307 'twitter:card': tags.twitterCard,
212 'twitter:site': CONFIG.SERVICES.TWITTER.USERNAME, 308 'twitter:site': CONFIG.SERVICES.TWITTER.USERNAME,
213 'twitter:title': videoNameEscaped, 309 'twitter:title': tags.title,
214 'twitter:description': videoDescriptionEscaped, 310 'twitter:description': tags.description,
215 'twitter:image': previewUrl, 311 'twitter:image': tags.image.url
216 'twitter:player': embedUrl,
217 'twitter:player:width': EMBED_SIZE.width,
218 'twitter:player:height': EMBED_SIZE.height
219 } 312 }
220 313
221 const oembedLinkTags = [ 314 if (tags.image.width && tags.image.height) {
222 { 315 metaTags['twitter:image:width'] = tags.image.width
223 type: 'application/json+oembed', 316 metaTags['twitter:image:height'] = tags.image.height
224 href: WEBSERVER.URL + '/services/oembed?url=' + encodeURIComponent(videoUrl), 317 }
225 title: videoNameEscaped 318
226 } 319 return metaTags
227 ] 320 }
228 321
229 const schemaTags = { 322 private static generateSchemaTags (tags) {
323 const schema = {
230 '@context': 'http://schema.org', 324 '@context': 'http://schema.org',
231 '@type': 'VideoObject', 325 '@type': tags.schemaType,
232 'name': videoNameEscaped, 326 'name': tags.title,
233 'description': videoDescriptionEscaped, 327 'description': tags.description,
234 'thumbnailUrl': previewUrl, 328 'image': tags.image.url,
235 'uploadDate': video.createdAt.toISOString(), 329 'url': tags.url
236 'duration': getActivityStreamDuration(video.duration), 330 }
237 'contentUrl': videoUrl, 331
238 'embedUrl': embedUrl, 332 if (tags.list) {
239 'interactionCount': video.views 333 schema['numberOfItems'] = tags.list.numberOfItems
334 schema['thumbnailUrl'] = tags.image.url
335 }
336
337 if (tags.embed) {
338 schema['embedUrl'] = tags.embed.url
339 schema['uploadDate'] = tags.embed.createdAt
340 schema['duration'] = tags.embed.duration
341 schema['iterationCount'] = tags.embed.views
342 schema['thumbnailUrl'] = tags.image.url
343 schema['contentUrl'] = tags.url
344 }
345
346 return schema
347 }
348
349 private static addTags (htmlStringPage: string, tagsValues: any) {
350 const openGraphMetaTags = this.generateOpenGraphMetaTags(tagsValues)
351 const standardMetaTags = this.generateStandardMetaTags(tagsValues)
352 const twitterCardMetaTags = this.generateTwitterCardMetaTags(tagsValues)
353 const schemaTags = this.generateSchemaTags(tagsValues)
354
355 const { url, title, embed } = tagsValues
356
357 const oembedLinkTags = []
358
359 if (embed) {
360 oembedLinkTags.push({
361 type: 'application/json+oembed',
362 href: WEBSERVER.URL + '/services/oembed?url=' + encodeURIComponent(url),
363 title
364 })
240 } 365 }
241 366
242 let tagsString = '' 367 let tagsString = ''
@@ -248,28 +373,33 @@ export class ClientHtml {
248 tagsString += `<meta property="${tagName}" content="${tagValue}" />` 373 tagsString += `<meta property="${tagName}" content="${tagValue}" />`
249 }) 374 })
250 375
376 // Standard
377 Object.keys(standardMetaTags).forEach(tagName => {
378 const tagValue = standardMetaTags[tagName]
379
380 tagsString += `<meta property="${tagName}" content="${tagValue}" />`
381 })
382
383 // Twitter card
384 Object.keys(twitterCardMetaTags).forEach(tagName => {
385 const tagValue = twitterCardMetaTags[tagName]
386
387 tagsString += `<meta property="${tagName}" content="${tagValue}" />`
388 })
389
251 // OEmbed 390 // OEmbed
252 for (const oembedLinkTag of oembedLinkTags) { 391 for (const oembedLinkTag of oembedLinkTags) {
253 tagsString += `<link rel="alternate" type="${oembedLinkTag.type}" href="${oembedLinkTag.href}" title="${oembedLinkTag.title}" />` 392 tagsString += `<link rel="alternate" type="${oembedLinkTag.type}" href="${oembedLinkTag.href}" title="${oembedLinkTag.title}" />`
254 } 393 }
255 394
256 // Schema.org 395 // Schema.org
257 tagsString += `<script type="application/ld+json">${JSON.stringify(schemaTags)}</script>` 396 if (schemaTags) {
258 397 tagsString += `<script type="application/ld+json">${JSON.stringify(schemaTags)}</script>`
259 // SEO, use origin video url so Google does not index remote videos 398 }
260 tagsString += `<link rel="canonical" href="${video.url}" />`
261
262 return this.addOpenGraphAndOEmbedTags(htmlStringPage, tagsString)
263 }
264
265 private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: MAccountActor | MChannelActor) {
266 // SEO, use origin account or channel URL
267 const metaTags = `<link rel="canonical" href="${entity.Actor.url}" />`
268 399
269 return this.addOpenGraphAndOEmbedTags(htmlStringPage, metaTags) 400 // SEO, use origin URL
270 } 401 tagsString += `<link rel="canonical" href="${url}" />`
271 402
272 private static addOpenGraphAndOEmbedTags (htmlStringPage: string, metaTags: string) { 403 return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.META_TAGS, tagsString)
273 return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.META_TAGS, metaTags)
274 } 404 }
275} 405}
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts
index 51fe04fc4..b38cf9c6a 100644
--- a/server/models/video/video-playlist.ts
+++ b/server/models/video/video-playlist.ts
@@ -490,6 +490,10 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
490 return join(STATIC_PATHS.THUMBNAILS, this.Thumbnail.filename) 490 return join(STATIC_PATHS.THUMBNAILS, this.Thumbnail.filename)
491 } 491 }
492 492
493 getWatchUrl () {
494 return WEBSERVER.URL + '/videos/watch/playlist/' + this.uuid
495 }
496
493 setAsRefreshed () { 497 setAsRefreshed () {
494 this.changed('updatedAt', true) 498 this.changed('updatedAt', true)
495 499
diff --git a/server/tests/client.ts b/server/tests/client.ts
index 670bc6701..648d46414 100644
--- a/server/tests/client.ts
+++ b/server/tests/client.ts
@@ -13,8 +13,14 @@ import {
13 serverLogin, 13 serverLogin,
14 updateCustomConfig, 14 updateCustomConfig,
15 updateCustomSubConfig, 15 updateCustomSubConfig,
16 uploadVideo 16 uploadVideo,
17 createVideoPlaylist,
18 addVideoInPlaylist,
19 getAccount,
20 addVideoChannel
17} from '../../shared/extra-utils' 21} from '../../shared/extra-utils'
22import { VideoPlaylistPrivacy } from '@shared/models'
23import { MVideoPlaylist, MAccount, MChannel } from '@server/types/models'
18 24
19const expect = chai.expect 25const expect = chai.expect
20 26
@@ -26,6 +32,11 @@ function checkIndexTags (html: string, title: string, description: string, css:
26 32
27describe('Test a client controllers', function () { 33describe('Test a client controllers', function () {
28 let server: ServerInfo 34 let server: ServerInfo
35 let videoPlaylist: MVideoPlaylist
36 let account: MAccount
37 let videoChannel: MChannel
38 const name = 'my super name for server 1'
39 const description = 'my super description for server 1'
29 40
30 before(async function () { 41 before(async function () {
31 this.timeout(120000) 42 this.timeout(120000)
@@ -33,18 +44,56 @@ describe('Test a client controllers', function () {
33 server = await flushAndRunServer(1) 44 server = await flushAndRunServer(1)
34 server.accessToken = await serverLogin(server) 45 server.accessToken = await serverLogin(server)
35 46
36 const videoAttributes = { 47 // Video
37 name: 'my super name for server 1', 48
38 description: 'my super description for server 1' 49 const videoAttributes = { name, description }
39 } 50
40 await uploadVideo(server.url, server.accessToken, videoAttributes) 51 await uploadVideo(server.url, server.accessToken, videoAttributes)
41 52
42 const res = await getVideosList(server.url) 53 const resVideosRequest = await getVideosList(server.url)
43 const videos = res.body.data 54
55 const videos = resVideosRequest.body.data
44 56
45 expect(videos.length).to.equal(1) 57 expect(videos.length).to.equal(1)
46 58
47 server.video = videos[0] 59 server.video = videos[0]
60
61 // Playlist
62
63 const playlistAttrs = {
64 displayName: name,
65 description,
66 privacy: VideoPlaylistPrivacy.PUBLIC
67 }
68
69 const resVideoPlaylistRequest = await createVideoPlaylist({ url: server.url, token: server.accessToken, playlistAttrs })
70
71 videoPlaylist = resVideoPlaylistRequest.body.videoPlaylist
72
73 await addVideoInPlaylist({
74 url: server.url,
75 token: server.accessToken,
76 playlistId: videoPlaylist.id,
77 elementAttrs: { videoId: server.video.id }
78 })
79
80 // Account
81
82 const resAccountRequest = await getAccount(server.url, `${server.user.username}@${server.host}:${server.port}`)
83
84 account = resAccountRequest.body.account
85
86 // Channel
87
88 const videoChannelAttributesArg = {
89 name: `${server.user.username}_channel`,
90 displayName: name,
91 description
92 }
93
94 const resChannelRequest = await addVideoChannel(server.url, server.accessToken, videoChannelAttributesArg)
95
96 videoChannel = resChannelRequest.body.videoChannel
48 }) 97 })
49 98
50 it('Should have valid Open Graph tags on the watch page with video id', async function () { 99 it('Should have valid Open Graph tags on the watch page with video id', async function () {
@@ -53,8 +102,10 @@ describe('Test a client controllers', function () {
53 .set('Accept', 'text/html') 102 .set('Accept', 'text/html')
54 .expect(200) 103 .expect(200)
55 104
56 expect(res.text).to.contain('<meta property="og:title" content="my super name for server 1" />') 105 expect(res.text).to.contain(`<meta property="og:title" content="${name}" />`)
57 expect(res.text).to.contain('<meta property="og:description" content="my super description for server 1" />') 106 expect(res.text).to.contain(`<meta property="og:description" content="${description}" />`)
107 expect(res.text).to.contain('<meta property="og:type" content="video" />')
108 expect(res.text).to.contain(`<meta property="og:url" content="${server.url}/videos/watch/${server.video.uuid}" />`)
58 }) 109 })
59 110
60 it('Should have valid Open Graph tags on the watch page with video uuid', async function () { 111 it('Should have valid Open Graph tags on the watch page with video uuid', async function () {
@@ -63,8 +114,46 @@ describe('Test a client controllers', function () {
63 .set('Accept', 'text/html') 114 .set('Accept', 'text/html')
64 .expect(200) 115 .expect(200)
65 116
66 expect(res.text).to.contain('<meta property="og:title" content="my super name for server 1" />') 117 expect(res.text).to.contain(`<meta property="og:title" content="${name}" />`)
67 expect(res.text).to.contain('<meta property="og:description" content="my super description for server 1" />') 118 expect(res.text).to.contain(`<meta property="og:description" content="${description}" />`)
119 expect(res.text).to.contain('<meta property="og:type" content="video" />')
120 expect(res.text).to.contain(`<meta property="og:url" content="${server.url}/videos/watch/${server.video.uuid}" />`)
121 })
122
123 it('Should have valid Open Graph tags on the watch playlist page', async function () {
124 const res = await request(server.url)
125 .get('/videos/watch/playlist/' + videoPlaylist.uuid)
126 .set('Accept', 'text/html')
127 .expect(200)
128
129 expect(res.text).to.contain(`<meta property="og:title" content="${videoPlaylist.name}" />`)
130 expect(res.text).to.contain(`<meta property="og:description" content="${videoPlaylist.description}" />`)
131 expect(res.text).to.contain('<meta property="og:type" content="video" />')
132 expect(res.text).to.contain(`<meta property="og:url" content="${server.url}/videos/watch/playlist/${videoPlaylist.uuid}" />`)
133 })
134
135 it('Should have valid Open Graph tags on the account page', async function () {
136 const res = await request(server.url)
137 .get('/accounts/' + server.user.username)
138 .set('Accept', 'text/html')
139 .expect(200)
140
141 expect(res.text).to.contain(`<meta property="og:title" content="${account.getDisplayName()}" />`)
142 expect(res.text).to.contain(`<meta property="og:description" content="${account.description}" />`)
143 expect(res.text).to.contain('<meta property="og:type" content="website" />')
144 expect(res.text).to.contain(`<meta property="og:url" content="${server.url}/accounts/${server.user.username}" />`)
145 })
146
147 it('Should have valid Open Graph tags on the channel page', async function () {
148 const res = await request(server.url)
149 .get('/video-channels/' + videoChannel.name)
150 .set('Accept', 'text/html')
151 .expect(200)
152
153 expect(res.text).to.contain(`<meta property="og:title" content="${videoChannel.getDisplayName()}" />`)
154 expect(res.text).to.contain(`<meta property="og:description" content="${videoChannel.description}" />`)
155 expect(res.text).to.contain('<meta property="og:type" content="website" />')
156 expect(res.text).to.contain(`<meta property="og:url" content="${server.url}/video-channels/${videoChannel.name}" />`)
68 }) 157 })
69 158
70 it('Should have valid oEmbed discovery tags', async function () { 159 it('Should have valid oEmbed discovery tags', async function () {
@@ -81,7 +170,7 @@ describe('Test a client controllers', function () {
81 expect(res.text).to.contain(expectedLink) 170 expect(res.text).to.contain(expectedLink)
82 }) 171 })
83 172
84 it('Should have valid twitter card', async function () { 173 it('Should have valid twitter card on the whatch video page', async function () {
85 const res = await request(server.url) 174 const res = await request(server.url)
86 .get('/videos/watch/' + server.video.uuid) 175 .get('/videos/watch/' + server.video.uuid)
87 .set('Accept', 'text/html') 176 .set('Accept', 'text/html')
@@ -89,6 +178,44 @@ describe('Test a client controllers', function () {
89 178
90 expect(res.text).to.contain('<meta property="twitter:card" content="summary_large_image" />') 179 expect(res.text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
91 expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />') 180 expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
181 expect(res.text).to.contain(`<meta property="twitter:title" content="${name}" />`)
182 expect(res.text).to.contain(`<meta property="twitter:description" content="${description}" />`)
183 })
184
185 it('Should have valid twitter card on the watch playlist page', async function () {
186 const res = await request(server.url)
187 .get('/videos/watch/playlist/' + videoPlaylist.uuid)
188 .set('Accept', 'text/html')
189 .expect(200)
190
191 expect(res.text).to.contain('<meta property="twitter:card" content="summary" />')
192 expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
193 expect(res.text).to.contain(`<meta property="twitter:title" content="${videoPlaylist.name}" />`)
194 expect(res.text).to.contain(`<meta property="twitter:description" content="${videoPlaylist.description}" />`)
195 })
196
197 it('Should have valid twitter card on the account page', async function () {
198 const res = await request(server.url)
199 .get('/accounts/' + account.name)
200 .set('Accept', 'text/html')
201 .expect(200)
202
203 expect(res.text).to.contain('<meta property="twitter:card" content="summary" />')
204 expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
205 expect(res.text).to.contain(`<meta property="twitter:title" content="${account.name}" />`)
206 expect(res.text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
207 })
208
209 it('Should have valid twitter card on the channel page', async function () {
210 const res = await request(server.url)
211 .get('/video-channels/' + videoChannel.name)
212 .set('Accept', 'text/html')
213 .expect(200)
214
215 expect(res.text).to.contain('<meta property="twitter:card" content="summary" />')
216 expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
217 expect(res.text).to.contain(`<meta property="twitter:title" content="${videoChannel.name}" />`)
218 expect(res.text).to.contain(`<meta property="twitter:description" content="${videoChannel.description}" />`)
92 }) 219 })
93 220
94 it('Should have valid twitter card if Twitter is whitelisted', async function () { 221 it('Should have valid twitter card if Twitter is whitelisted', async function () {
@@ -100,13 +227,37 @@ describe('Test a client controllers', function () {
100 } 227 }
101 await updateCustomConfig(server.url, server.accessToken, config) 228 await updateCustomConfig(server.url, server.accessToken, config)
102 229
103 const res = await request(server.url) 230 const resVideoRequest = await request(server.url)
104 .get('/videos/watch/' + server.video.uuid) 231 .get('/videos/watch/' + server.video.uuid)
105 .set('Accept', 'text/html') 232 .set('Accept', 'text/html')
106 .expect(200) 233 .expect(200)
107 234
108 expect(res.text).to.contain('<meta property="twitter:card" content="player" />') 235 expect(resVideoRequest.text).to.contain('<meta property="twitter:card" content="player" />')
109 expect(res.text).to.contain('<meta property="twitter:site" content="@Kuja" />') 236 expect(resVideoRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
237
238 const resVideoPlaylistRequest = await request(server.url)
239 .get('/videos/watch/playlist/' + videoPlaylist.uuid)
240 .set('Accept', 'text/html')
241 .expect(200)
242
243 expect(resVideoPlaylistRequest.text).to.contain('<meta property="twitter:card" content="player" />')
244 expect(resVideoPlaylistRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
245
246 const resAccountRequest = await request(server.url)
247 .get('/accounts/' + account.name)
248 .set('Accept', 'text/html')
249 .expect(200)
250
251 expect(resAccountRequest.text).to.contain('<meta property="twitter:card" content="player" />')
252 expect(resAccountRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
253
254 const resChannelRequest = await request(server.url)
255 .get('/video-channels/' + videoChannel.name)
256 .set('Accept', 'text/html')
257 .expect(200)
258
259 expect(resChannelRequest.text).to.contain('<meta property="twitter:card" content="player" />')
260 expect(resChannelRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
110 }) 261 })
111 262
112 it('Should have valid index html tags (title, description...)', async function () { 263 it('Should have valid index html tags (title, description...)', async function () {