video sekcija: strani /video/<slug> z 20s napovednikom (predvajljiv na strani, VideoObject) in gumbom na YouTube; API /api/yt-videos; ingest skripta za FOLX YouTube kanal
This commit is contained in:
+81
-1
@@ -80,6 +80,86 @@ export function serveStatic(app: Express) {
|
||||
const protocol = req.get("x-forwarded-proto") || "https";
|
||||
const baseUrl = `${protocol}://${host}`;
|
||||
|
||||
// ---- /video/<slug>: napovednik je predvajljiv na strani, cel video na YouTube ----
|
||||
const ytVideoMatch = url.match(/^\/video\/([^?#\/]+)/);
|
||||
if (ytVideoMatch) {
|
||||
try {
|
||||
const slug = decodeURIComponent(ytVideoMatch[1]);
|
||||
const idxFile = path.resolve(process.cwd(), "client/public/uploads/video/index.json");
|
||||
const list: any[] = JSON.parse(await fs.promises.readFile(idxFile, "utf-8")).items || [];
|
||||
const v = list.find((i) => i.slug === slug || i.ytId === slug);
|
||||
let template = await fs.promises.readFile(indexPath, "utf-8");
|
||||
template = stripExistingMeta(template);
|
||||
if (!v) {
|
||||
template = template.replace(/<title>[^<]*<\/title>/, [
|
||||
'<meta name="robots" content="noindex, follow" />',
|
||||
'<title>Video nicht gefunden | Folx Music Television</title>',
|
||||
].join("\n "));
|
||||
res.status(404).set({ "Content-Type": "text/html" }).end(template);
|
||||
return;
|
||||
}
|
||||
const pageUrl = `${canonicalBase}/video/${v.slug}`;
|
||||
const poster = `${canonicalBase}${v.posterUrl}`;
|
||||
const desc = (v.teaser || v.shortTitle || v.title).slice(0, 300);
|
||||
const mins = Math.floor(v.previewS / 60), secs = v.previewS % 60;
|
||||
const videoLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "VideoObject",
|
||||
"name": `${v.shortTitle} \u2013 Vorschau`,
|
||||
"description": desc,
|
||||
"thumbnailUrl": [poster],
|
||||
"uploadDate": v.publishedAt,
|
||||
"duration": `PT${mins}M${secs}S`,
|
||||
"contentUrl": `${canonicalBase}${v.previewUrl}`,
|
||||
"isFamilyFriendly": true,
|
||||
"inLanguage": "de",
|
||||
"publisher": {
|
||||
"@type": "Organization",
|
||||
"name": "Folx Music Television",
|
||||
"logo": { "@type": "ImageObject", "url": `${canonicalBase}/og-image.jpg` },
|
||||
},
|
||||
"mainEntityOfPage": { "@type": "WebPage", "@id": pageUrl },
|
||||
};
|
||||
const tags = [
|
||||
`<meta property="og:title" content="${escapeHtml(v.shortTitle)}" />`,
|
||||
`<meta property="og:description" content="${escapeHtml(desc)}" />`,
|
||||
`<meta property="og:type" content="video.other" />`,
|
||||
`<meta property="og:url" content="${escapeHtml(pageUrl)}" />`,
|
||||
`<meta property="og:image" content="${escapeHtml(poster)}" />`,
|
||||
`<meta property="og:image:width" content="1280" />`,
|
||||
`<meta property="og:image:height" content="720" />`,
|
||||
`<meta property="og:site_name" content="Folx Music Television" />`,
|
||||
`<meta property="og:locale" content="de_DE" />`,
|
||||
`<meta name="twitter:card" content="summary_large_image" />`,
|
||||
`<meta name="twitter:title" content="${escapeHtml(v.shortTitle)}" />`,
|
||||
`<meta name="twitter:description" content="${escapeHtml(desc)}" />`,
|
||||
`<meta name="twitter:image" content="${escapeHtml(poster)}" />`,
|
||||
`<meta name="description" content="${escapeHtml(desc)}" />`,
|
||||
`<link rel="canonical" href="${escapeHtml(pageUrl)}" />`,
|
||||
`<title>${escapeHtml(v.shortTitle)} | ${escapeHtml(v.rubrik)} | Folx Music Television</title>`,
|
||||
`<script type="application/ld+json">${JSON.stringify(videoLd).replace(/</g, "\\u003c")}</script>`,
|
||||
].join("\n ");
|
||||
template = template.replace(/<title>[^<]*<\/title>/, tags);
|
||||
|
||||
const chapterHtml = (v.chapters || []).length
|
||||
? `<h2>Kapitel</h2><ul>${v.chapters.map((c: any) => `<li>${escapeHtml(c.t)} – ${escapeHtml(c.label)}</li>`).join("")}</ul>`
|
||||
: "";
|
||||
const ssr = [
|
||||
'<article class="ssr-content">',
|
||||
`<h1>${escapeHtml(v.shortTitle)}</h1>`,
|
||||
`<video controls playsinline preload="metadata" poster="${escapeHtml(v.posterUrl)}" style="max-width:100%;height:auto;" src="${escapeHtml(v.previewUrl)}"></video>`,
|
||||
`<p><strong>${escapeHtml(desc)}</strong></p>`,
|
||||
chapterHtml,
|
||||
`<p><a href="${escapeHtml(v.youtubeUrl)}" rel="noopener">Das ganze Video auf YouTube ansehen</a></p>`,
|
||||
"</article>",
|
||||
].join("\n");
|
||||
template = template.replace('<div id="root"></div>', () => `<div id="root">${ssr}</div>`);
|
||||
res.status(200).set({ "Content-Type": "text/html" }).end(template);
|
||||
return;
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
const articleMatch = url.match(/^\/article\/([^?#]+)/);
|
||||
if (articleMatch) {
|
||||
try {
|
||||
@@ -318,7 +398,7 @@ export function serveStatic(app: Express) {
|
||||
// z naslovnico). Seznam mora ustrezati potem v client/src/App.tsx.
|
||||
const KNOWN = [
|
||||
/^\/$/, /^\/search\/?$/, /^\/article\//, /^\/category\//, /^\/aktuelles\/?$/,
|
||||
/^\/videos\/?$/, /^\/gallery\/?$/, /^\/horoskop(\/|$)/, /^\/rezepte\/?$/,
|
||||
/^\/videos\/?$/, /^\/video\/[^/]+\/?$/, /^\/gallery\/?$/, /^\/horoskop(\/|$)/, /^\/rezepte\/?$/,
|
||||
/^\/empfang-folx-tv\/?$/, /^\/ueber-uns\/?$/, /^\/impressum\/?$/,
|
||||
/^\/datenschutz\/?$/, /^\/kontakt\/?$/, /^\/admin(\/|$)/,
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user