Created
March 5, 2025 15:24
-
-
Save StoneyEagle/42c142142502411a6e8d23e6a7bc5c30 to your computer and use it in GitHub Desktop.
SPA TMDB Inject Cloudflare Worker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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