Add social media sharing previews for videos
Implement a server-side API to generate dynamic social media shareable image thumbnails for videos and update the client to utilize this new API for open graph and Twitter meta tags. Replit-Commit-Author: Agent Replit-Commit-Session-Id: ab9cd02a-d0b2-4288-9ceb-1964d0059648 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/ab9cd02a-d0b2-4288-9ceb-1964d0059648/mbX83Mu
This commit is contained in:
@@ -2,6 +2,9 @@ import express, { type Request, Response, NextFunction } from "express";
|
||||
import { registerRoutes } from "./routes";
|
||||
import { videoSyncService } from "./videoSync";
|
||||
import { setupVite, serveStatic, log } from "./vite";
|
||||
import { storage } from "./storage";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
@@ -41,6 +44,94 @@ app.use((req, res, next) => {
|
||||
// Initialize video sync service for automatic Bunny.net updates
|
||||
await videoSyncService.initialize();
|
||||
|
||||
// Social media prerendering middleware - mora biti PRED setupVite
|
||||
app.get('/video/:videoId', async (req, res, next) => {
|
||||
const { videoId } = req.params;
|
||||
const userAgent = req.get('User-Agent') || '';
|
||||
|
||||
// Če je to Facebook, Twitter, WhatsApp ali podoben scraper
|
||||
const isSocialBot = /facebookexternalhit|twitterbot|whatsapp|telegrambot|discordbot|slackbot|linkedinbot/i.test(userAgent);
|
||||
|
||||
if (isSocialBot) {
|
||||
try {
|
||||
const video = await storage.getVideo(videoId);
|
||||
|
||||
if (!video) {
|
||||
return next(); // Če video ne obstaja, preusmerimo na običajno SPA
|
||||
}
|
||||
|
||||
// Preberemo osnovni HTML template
|
||||
const clientTemplate = path.resolve(import.meta.dirname, "..", "client", "index.html");
|
||||
let template = await fs.promises.readFile(clientTemplate, "utf-8");
|
||||
|
||||
// Escape special characters v naslovih in opisih
|
||||
const escapeHtml = (text: string) => text.replace(/"/g, '"').replace(/'/g, ''');
|
||||
|
||||
// Zamenimo meta oznake z video specifičnimi
|
||||
const baseUrl = req.protocol + '://' + req.get('host');
|
||||
const videoUrl = `${baseUrl}/video/${video.id}`;
|
||||
const thumbnailUrl = `${baseUrl}/api/video-thumbnail/${video.id}`;
|
||||
|
||||
// Zamenjamo osnovne meta oznake
|
||||
template = template.replace(
|
||||
/<title>.*<\/title>/,
|
||||
`<title>${escapeHtml(video.title)} | go4.video</title>`
|
||||
);
|
||||
|
||||
template = template.replace(
|
||||
/<meta name="description" content="[^"]*"/,
|
||||
`<meta name="description" content="${escapeHtml(video.description || `Watch ${video.title} on go4.video`)}"`
|
||||
);
|
||||
|
||||
// Zamenjamo Open Graph meta oznake
|
||||
template = template.replace(
|
||||
/<meta property="og:title" content="[^"]*"/,
|
||||
`<meta property="og:title" content="${escapeHtml(video.title)}"`
|
||||
);
|
||||
|
||||
template = template.replace(
|
||||
/<meta property="og:description" content="[^"]*"/,
|
||||
`<meta property="og:description" content="${escapeHtml(video.description || `Watch ${video.title} on go4.video`)}"`
|
||||
);
|
||||
|
||||
template = template.replace(
|
||||
/<meta property="og:image" content="[^"]*"/,
|
||||
`<meta property="og:image" content="${thumbnailUrl}"`
|
||||
);
|
||||
|
||||
// Dodamo dodatne OG oznake za video
|
||||
const additionalMeta = `
|
||||
<meta property="og:url" content="${videoUrl}">
|
||||
<meta property="og:type" content="video.other">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<meta property="og:image:type" content="image/png">
|
||||
<meta property="og:video:duration" content="${video.duration}">
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="${escapeHtml(video.title)}">
|
||||
<meta name="twitter:description" content="${escapeHtml(video.description || `Watch ${video.title} on go4.video`)}">
|
||||
<meta name="twitter:image" content="${thumbnailUrl}">
|
||||
<meta name="twitter:image:width" content="1200">
|
||||
<meta name="twitter:image:height" content="630">`;
|
||||
|
||||
template = template.replace('</head>', `${additionalMeta}\n</head>`);
|
||||
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.send(template);
|
||||
return;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error rendering video page for social bot:', error);
|
||||
// Če se zgodi napaka, nadaljujemo z običajno SPA
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
// Za običajne uporabnike nadaljujemo z SPA
|
||||
next();
|
||||
});
|
||||
|
||||
const server = await registerRoutes(app);
|
||||
|
||||
app.use((err: any, _req: Request, res: Response, _next: NextFunction) => {
|
||||
|
||||
Reference in New Issue
Block a user