Skip to content

Instantly share code, notes, and snippets.

@StoneyEagle
Created March 5, 2025 15:24
Show Gist options
  • Save StoneyEagle/42c142142502411a6e8d23e6a7bc5c30 to your computer and use it in GitHub Desktop.
Save StoneyEagle/42c142142502411a6e8d23e6a7bc5c30 to your computer and use it in GitHub Desktop.
SPA TMDB Inject Cloudflare Worker
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const url = new URL(request.url);
const path = url.pathname;
if (request.headers.get('Upgrade') === 'websocket') {
return fetch(request, {
headers: {
'User-Agent': 'Cloudflare Worker/1 ( [email protected] )'
}
}); // Pass through WebSocket requests
}
// Image Processing Logic
if (path.startsWith('/tmdb-images/')) {
return handleImageRequest(request);
}
// Meta Tag Injection Logic
if (path && (path.match(/^\/(movie|tv|person|collection)\/(\d+)(\/watch)?$/))) {
return handleMetaTagRequest(request);
}
if (!path.match(/^\/(@|node_modules|resources|assets|src|img|icons|js|css|fonts|manifest|sw)/)) {
// Default Meta Tags
return handleDefaultMetaTags(request);
}
return fetch(request, {
headers: {
'User-Agent': 'Cloudflare Worker/1 ( [email protected] )'
}
});
}
async function handleImageRequest(request) {
const url = new URL(request.url);
const path = url.pathname;
const query = url.searchParams;
const match = path.match(/^\/tmdb-images\/(.+)$/);
if (match) {
const imagePath = match[1];
const requestedWidth = query.get('width');
const type = query.get('type');
const aspectRatio = query.get('aspect_ratio');
const tmdbSize = getCommonSize(requestedWidth);
const tmdbImageUrl = `https://image.tmdb.org/t/p/${tmdbSize}/${imagePath}`;
try {
const response = await fetch(tmdbImageUrl, {
headers: {
'User-Agent': 'Cloudflare Worker/1 ( [email protected] )'
}
});
if (response.ok) {
return transformImage(response, requestedWidth, type, aspectRatio);
} else {
return new Response('Image not found', { status: 404 });
}
} catch (error) {
console.error('Error fetching image:', error);
return new Response('Internal server error', { status: 500 });
}
}
return fetch(request, {
headers: {
'User-Agent': 'Cloudflare Worker/1 ( [email protected] )'
}
});
}
function getCommonSize(requestedWidth) {
const commonSizes = ['w92', 'w185', 'w342', 'w500', 'w780', 'original'];
if (!requestedWidth) {
return 'original';
}
const requestedWidthNum = parseInt(requestedWidth);
let closestSize = commonSizes[0];
for (const size of commonSizes) {
if (size === 'original') return 'original';
const sizeNum = parseInt(size.replace('w', ''));
if (sizeNum >= requestedWidthNum) {
closestSize = size;
break;
}
}
return closestSize;
}
async function transformImage(response, width, type, ar, request) {
const imageOptions = {
fit: 'cover',
quality: 90,
};
if (width) {
imageOptions.width = parseInt(width);
}
if (type) {
imageOptions.format = type;
}
if (ar) {
imageOptions.aspectRatio = ar;
imageOptions.fit = 'contain';
}
const imageRequest = new Request(response.url, {
headers: response.headers,
});
console.log(imageOptions);
const transformedResponse = await fetch(imageRequest, {
cf: {
image: imageOptions,
},
});
response = new Response(transformedResponse.body, response);
response.headers.set("Access-Control-Allow-Origin", "*");
response.headers.append("Vary", "Origin");
// Check if the transformation was successful and if the format was actually changed
if (transformedResponse.ok && type) {
const transformedContentType = transformedResponse.headers.get('content-type');
if (transformedContentType && transformedContentType.startsWith(`image/${type}`)) {
return response;
} else {
console.warn(`Requested format ${type} not applied. Returning original.`);
return response; // Return the original response if the format change failed
}
}
return response;
}
async function handleMetaTagRequest(request) {
const url = new URL(request.url);
const path = url.pathname;
const language = request.headers.get('Accept-Language')?.split(',').at(0);
let match;
if (path && (match = path.match(/^\/(movie|tv|person|collection)\/(\d+)(\/watch)?$/))) {
const type = match[1];
const id = match[2];
const apiKey = await TMDB_API_KEY;
const apiUrl = `https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}&language=${language}`;
console.log(apiUrl);
const apiResponse = await fetch(apiUrl, {
headers: {
'User-Agent': 'Cloudflare Worker/1 ( [email protected] )'
}
});
if (apiResponse.ok) {
const data = await apiResponse.json();
const originalResponse = await fetch(new URL(url.origin + '/index.html'));
let html = await originalResponse.text();
html = injectMetaTags(html, type, data, url.href);
const res = new Response(html, {
headers: {
'Content-Type': 'text/html; charset=utf-8',
},
});
res.headers.set("Access-Control-Allow-Origin", url.origin);
return res;
}
}
return fetch(new URL(url.origin + '/index.html'), {
headers: {
'User-Agent': 'Cloudflare Worker/1 ( [email protected] )'
}
});
}
function injectMetaTags(html, type, data, url) {
let title, description, image, image2, ogType;
const favicon = "https://pub-a68768bb5b1045f296df9ea56bd53a7f.r2.dev/icons/favicon.ico";
switch (type) {
case 'movie':
case 'collection':
title = data.title;
description = data.overview;
image = `https://image.tmdb.org/t/p/w780${data.poster_path}`;
image2 = data.backdrop_path ? `https://image.tmdb.org/t/p/w780${data.backdrop_path}` : image;
ogType = "video.movie";
break;
case 'tv':
title = data.name;
description = data.overview;
image = `https://image.tmdb.org/t/p/w780${data.poster_path}`;
image2 = data.backdrop_path ? `https://image.tmdb.org/t/p/w780${data.backdrop_path}` : image;
ogType = "video.tv_show";
break;
case 'person':
title = data.name;
description = data.biography ? data.biography.substring(0, 200) : "No biography available.";
image = `https://image.tmdb.org/t/p/w780${data.profile_path}`;
image2 = '';
ogType = "profile"; // Or "article" depending on how you want to represent a person
break;
default:
ogType = "website";
}
html = html.replace(
'<head>',
`<head>
<title>${title}</title>
<meta name="title" content="${title}"/>
<meta name="description" content="${description}"/>
<meta name="application-name" content="NoMercy Tv"/>
<link rel="icon" href="${favicon}" type="image/x-icon">
<meta property="og:title" content="${title}">
<meta property="og:description" content="${description}">
<meta property="og:image" content="${image}">
${image2 ? `<meta property="og:image" content="${image2}">` : ''}
<meta property="og:image:width" content="780">
<meta property="og:image:height" content="439">
<meta property="og:type" content="${ogType}">
<meta property="og:locale" content="en_US">
<meta property="og:url" content="${url}">
<meta property="og:site_name" content="NoMercy Tv">
<meta property="fb:app_id" content="141280979243998">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${title}">
<meta name="twitter:description" content="${description}">
<meta name="twitter:image" content="${image}">
${image2 ? `<meta name="twitter:image" content="${image2}">` : ''}
<meta name="twitter:image:width" content="780">
<meta name="twitter:image:height" content="439">
<meta name="twitter:site" content="@nomercy_entertainment">
<meta name="twitter:url" content="${url}">
`
);
return html;
}
async function handleDefaultMetaTags(request) {
const url = new URL(request.url);
const originalResponse = await fetch(new URL(url.origin + '/index.html'), {
headers: {
'User-Agent': 'Cloudflare Worker/1 ( [email protected] )'
}
});
let html = await originalResponse.text(); // Get the HTML text
html = injectDefaultMetaTags(html, url.href); // Correctly call injectDefaultMetaTags
const res = new Response(html, {
headers: {
'Content-Type': 'text/html; charset=utf-8',
},
});
res.headers.set("Access-Control-Allow-Origin", url.origin);
return res;
}
function injectDefaultMetaTags(html, url) {
const title = "NoMercy TV";
const description = "The effortless video encoder.\nEncode and play your favorite movies and tv shows without any effort.";
const image = "https://pub-a68768bb5b1045f296df9ea56bd53a7f.r2.dev/icons/icon-192.png";
const favicon = "https://pub-a68768bb5b1045f296df9ea56bd53a7f.r2.dev/icons/favicon.ico";
html = html.replace(
'<head>',
`<head>
<title>${title}</title>
<meta name="title" content="${title}"/>
<meta name="description" content="${description}"/>
<meta name="application-name" content="NoMercy Tv"/>
<link rel="icon" href="${favicon}" type="image/x-icon">
<meta property="og:title" content="${title}">
<meta property="og:description" content="${description}">
<meta property="og:image" content="${image}">
<meta property="og:image:width" content="780">
<meta property="og:image:height" content="439">
<meta property="og:type" content="video.tv_show">
<meta property="og:locale" content="en_US">
<meta property="og:url" content="${url}">
<meta property="og:site_name" content="NoMercy Tv">
<meta property="fb:app_id" content="141280979243998">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${title}">
<meta name="twitter:description" content="${description}">
<meta name="twitter:image" content="${image}">
<meta name="twitter:image:width" content="780">
<meta name="twitter:image:height" content="439">
<meta name="twitter:site" content="@nomercy_entertainment">
<meta name="twitter:url" content="${url}">
`
);
return html;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment