diff options
| author | Alex Pooley (@zuedev) <zuedev@gmail.com> | 2025-10-31 00:01:55 +0000 |
|---|---|---|
| committer | Alex Pooley (@zuedev) <zuedev@gmail.com> | 2025-10-31 00:01:55 +0000 |
| commit | 0f4cb710dacd43eac6874e1deb0084c234f76c94 (patch) | |
| tree | 411b124f6a2e3f201a06fe54546ac0fb70100560 /projects/www/server/index.js | |
| parent | cba7362d2a7a888aae0bd203f89745bc6ba559b0 (diff) | |
| download | zue.dev-0f4cb710dacd43eac6874e1deb0084c234f76c94.tar zue.dev-0f4cb710dacd43eac6874e1deb0084c234f76c94.tar.gz zue.dev-0f4cb710dacd43eac6874e1deb0084c234f76c94.tar.bz2 zue.dev-0f4cb710dacd43eac6874e1deb0084c234f76c94.tar.xz zue.dev-0f4cb710dacd43eac6874e1deb0084c234f76c94.zip | |
merge api with root
Diffstat (limited to 'projects/www/server/index.js')
| -rw-r--r-- | projects/www/server/index.js | 379 |
1 files changed, 365 insertions, 14 deletions
diff --git a/projects/www/server/index.js b/projects/www/server/index.js index 10151ea..c8230ec 100644 --- a/projects/www/server/index.js +++ b/projects/www/server/index.js @@ -1,25 +1,376 @@ +import puppeteer from "@cloudflare/puppeteer"; + export default { - async fetch(request, env) { + /* + Fetch event handler, this function will be called whenever a request is made to the worker. + The function will parse the request and return a response based on the request path. + + @param {Request} request - the incoming request object + @param {Environment} environment - the environment object + @param {Context} context - the context object + + @returns {Response} a new Response object + */ + async fetch(request, environment, context) { const url = new URL(request.url); if (url.pathname.startsWith("/api")) { - const apiPath = url.pathname.split("/").filter(Boolean); + const pathname = url.pathname.replace("/api", ""); + + // modify Response to handle CORS defaults + class Response extends globalThis.Response { + constructor(body, init) { + super(body, init); + this.headers.set("Access-Control-Allow-Origin", "*"); + this.headers.set("Access-Control-Allow-Methods", "*"); + this.headers.set("Access-Control-Allow-Headers", "*"); + } + } + + switch (pathname) { + case "/": + return (() => { + return new Response( + JSON.stringify({ + message: "Hello, World! :3", + random: `${Math.random()}`, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + })(); + case "/status": + return (() => { + const url = new URL(request.url); + const service = url.searchParams.get("service"); + + const acceptedServices = [ + "dns", + "load-balancer", + "cdn", + "functions", + "mysql-cluster", + "mongodb-cluster", + "redis-cluster", + "elasticsearch-cluster", + "git-connector", + "job-runners", + "container-registry", + "kubernetes-cluster", + "bare-metal-servers", + "game-server-api", + "anti-ddos-protection", + "anti-cheat-api", + ]; + + if (acceptedServices.includes(service)) + return new Response( + JSON.stringify({ + status: "ok", + service, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + + return new Response( + JSON.stringify({ + error: `service not found`, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + })(); + case "/96/twitch/streaming": + return (async () => { + /* + Checks if a twitch channel is live by fetching the "live" preview image of the channel, + if the image is fetched successfully, then the channel is live, otherwise it's offline. + + @param {string} channel - the twitch channel name + @returns {boolean} true if the channel is live, false otherwise + */ + async function isTwitchChannelLive(channel) { + // construct preview image url with channel name + const livePreviewUrl = `https://static-cdn.jtvnw.net/previews-ttv/live_user_${channel}-320x180.jpg`; + + // fetch the preview image, don't follow redirects + const response = await fetch(livePreviewUrl, { + redirect: "manual", + }); + + // check if the image was fetched successfully + return response.ok; + } + + const url = new URL(request.url); + const channel = url.searchParams.get("channel"); + + if (!channel) + return new Response( + JSON.stringify({ + error: `channel not provided`, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + + const whitelist = [ + "zuedev", + ...[ + "vtsweets", + "bunnibana", + "yayjaybae", + "justawoney", + "tygiwygi", + ], + ]; + + if (!whitelist.includes(channel)) + return new Response( + JSON.stringify({ + error: `channel not whitelisted`, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + + const channelLive = await isTwitchChannelLive(channel); + + if (channelLive) + return new Response( + JSON.stringify({ + status: "live", + channel, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + + return new Response( + JSON.stringify({ + status: "offline", + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + })(); + case "/96/youtube/latest-video": + return (async () => { + const url = new URL(request.url); + const channelHandle = url.searchParams.get("channelHandle"); + + if (!channelHandle) + return new Response( + JSON.stringify({ + error: `channel not provided`, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + + const channelHandleWhitelist = ["@vtsweets"]; + + if (!channelHandleWhitelist.includes(channelHandle)) + return new Response( + JSON.stringify({ + error: `channel not allowed`, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); - if (!apiPath[1]) apiPath[1] = "index"; + let API_Key; - switch (apiPath[1]) { - case "index": - return new Response(JSON.stringify({ message: "Hello, World! :3" }), { - headers: { "Content-Type": "application/json" }, - }); + try { + API_Key = await environment.GOOGLE_API_KEY_UNRESTRICTED.get(); + } catch (error) { + API_Key = environment.GOOGLE_API_KEY_UNRESTRICTED; + } + + if (!API_Key) + return new Response( + JSON.stringify({ + error: `API Key not found`, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + + const youtubeChannelsListResponse = await fetch( + `https://www.googleapis.com/youtube/v3/channels?part=contentDetails&forHandle=${channelHandle}&key=${API_Key}` + ); + + const youtubeChannelsList = + await youtubeChannelsListResponse.json(); + + if (youtubeChannelsList.items.length === 0) { + return new Response( + JSON.stringify({ + error: `channel not found`, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + } + + const uploadsPlaylistId = + youtubeChannelsList.items[0].contentDetails.relatedPlaylists + .uploads; + + const youtubePlaylistItemsList = await fetch( + `https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=${uploadsPlaylistId}&maxResults=1&key=${API_Key}` + ); + + const youtubePlaylistItemsListData = + await youtubePlaylistItemsList.json(); + + if (youtubePlaylistItemsListData.items.length === 0) { + return new Response( + JSON.stringify({ + error: `no videos found in the channel`, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); + } + + return new Response( + youtubePlaylistItemsListData.items[0].snippet.resourceId.videoId + ); + })(); + case "/browser-rendering/screenshot": + return (async () => { + const { searchParams } = new URL(request.url); + const url = searchParams.get("url"); + const type = searchParams.get("type") || "webp"; + const fullPage = searchParams.get("fullPage") !== null; + const width = parseInt(searchParams.get("width"), 10) || 1920; + const height = parseInt(searchParams.get("height"), 10) || 1080; + const delay = parseInt(searchParams.get("delay"), 10) || 0; + + if (!url) + return router.respond({ + error: "URL parameter is required", + }); + + if (!/^https?:\/\//.test(url)) + return router.respond({ + error: "Invalid URL format", + }); + + const browser = await puppeteer.launch(environment.MYBROWSER); + const page = await browser.newPage(); + await page.setViewport({ width, height }); + await page.goto(url, { + waitUntil: "networkidle2", + }); + if (delay > 0) { + await new Promise((resolve) => setTimeout(resolve, delay)); + } + const screenshot = await page.screenshot({ + type, + fullPage, + }); + await browser.close(); + + return new Response(screenshot, { + headers: { + "content-type": `image/${type}`, + }, + }); + })(); default: - return new Response(JSON.stringify({ message: "Not Found" }), { - status: 404, - headers: { "Content-Type": "application/json" }, - }); + return new Response( + JSON.stringify({ + error: `path not found`, + }), + { + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }, + } + ); } } - return env.ASSETS.fetch(request); + return environment.ASSETS.fetch(request); + }, + + /* + Email event handler, this function will be called whenever an email is sent to the worker. + The function will parse the email message and forward it to a specified email address. + + @param {Message} message - the incoming email message object + @param {Environment} environment - the environment object + @param {Context} context - the context object + + @returns {void} + */ + async email(message, environment, context) { + message.forward("alex@zue.dev"); + }, + + /* + Scheduled event handler, this function will be called whenever a scheduled event is triggered. + The function will perform a task and return a response based on the task outcome. + + @param {Event} event - the incoming event object + @param {Environment} environment - the environment object + @param {Context} context - the context object + + @returns {void} + */ + async scheduled(event, environment, context) { + console.log("Scheduled event triggered!"); }, -};
\ No newline at end of file +}; |
