import { GenerateUploadURL } from "../../gcloud.js"; function errorResponse(error: string, status: number): Response { return new Response(JSON.stringify({ error }), { headers: { "content-type": "application/json", }, status, }); } export async function onRequestPost(context: RequestContext) { const { actions, bypass, filename, filesize, turnstileResponse, usernames } = context.data.body; if (!context.data.current_user) { if (typeof turnstileResponse !== "string") return errorResponse("You must complete the captcha", 401); const turnstileAPIResponse = await fetch( "https://challenges.cloudflare.com/turnstile/v0/siteverify", { body: JSON.stringify({ response: turnstileResponse, secret: context.env.TURNSTILE_SECRETKEY, }), headers: { "content-type": "application/json", }, method: "POST", } ); const { success }: { success: boolean } = await turnstileAPIResponse.json(); if (!success) return errorResponse("Captcha test failed", 403); } if (bypass && !(context.data.current_user?.permissions & (1 << 5))) return errorResponse("Bypass directive cannot be used", 403); if (typeof bypass !== "boolean") return errorResponse("Bypass must be a boolean", 400); if (!Array.isArray(usernames)) return errorResponse("Usernames must be type of array", 400); if (typeof filename !== "string") return errorResponse("Invalid file name", 400); if (typeof filesize !== "number" || filesize < 0 || filesize > 536870912) return errorResponse("Invalid file size", 400); if (!usernames.length || usernames.length > 20) return errorResponse( "Number of usernames provided must be between 1 and 20", 400 ); for (const username of usernames) { if ( username.length < 3 || username.length > 20 || username.match(/_/g)?.length > 1 ) return errorResponse(`Username "${username}" is invalid`, 400); } const rbxSearchReq = await fetch( "https://users.roblox.com/v1/usernames/users", { body: JSON.stringify({ usernames, excludeBannedUsers: true, }), headers: { "content-type": "application/json", }, method: "POST", } ); if (!rbxSearchReq.ok) return errorResponse( "Failed to locate Roblox users due to upstream error", 500 ); const rbxSearchData: { data: { [k: string]: any }[] } = await rbxSearchReq.json(); if (rbxSearchData.data.length < usernames.length) { const missingUsers = []; for (const userData of rbxSearchData.data) { if (!usernames.includes(userData.requestedUsername)) missingUsers.push(userData.requestedUsername); } return errorResponse( `The following users do not exist or are banned from Roblox: ${missingUsers.toString()}`, 400 ); } const metaIDs = []; const metaNames = []; for (const data of rbxSearchData.data) { metaIDs.push(data.id); metaNames.push(data.name); } const fileParts = filename.split("."); let fileExt = fileParts[fileParts.length - 1]; if ( fileParts.length < 2 || ![ "mkv", "mp4", "wmv", "jpg", "png", "m4v", "jpeg", "jfif", "gif", "webm", "heif", "heic", "webp", "mov", ].includes(fileExt.toLowerCase()) ) return errorResponse("This type of file cannot be uploaded", 415); const fileKey = `${crypto.randomUUID().replaceAll("-", "")}/${crypto .randomUUID() .replaceAll("-", "")}${context.request.headers.get("cf-ray")}${Date.now()}`; const reportId = `${Date.now()}${context.request.headers.get( "cf-ray" )}${crypto.randomUUID().replaceAll("-", "")}`; const uploadUrl = await GenerateUploadURL( context.env, fileKey, filesize, fileExt ); const { current_user: currentUser } = context.data; await context.env.DATA.put( `reportprocessing_${reportId}`, currentUser?.id || context.request.headers.get("CF-Connecting-IP"), { expirationTtl: 3600 } ); if (["mkv", "mov", "wmv"].includes(fileExt.toLowerCase())) await context.env.DATA.put(`videoprocessing_${fileKey}.${fileExt}`, "1", { expirationTtl: 3600, }); await context.env.DATA.put( `report_${reportId}`, JSON.stringify({ attachment: `${fileKey}.${ ["mkv", "mov", "wmv"].includes(fileExt.toLowerCase()) ? "mp4" : fileExt }`, id: reportId, open: !bypass, user: currentUser ? { discriminator: currentUser.discriminator, email: currentUser.email, id: currentUser.id, username: currentUser.username, } : null, target_ids: metaIDs, target_usernames: metaNames, }) ); try { await context.env.D1.prepare( "INSERT INTO reports (created_at, id, open, user) VALUES (?, ?, ?, ?);" ) .bind(Date.now(), reportId, Number(!bypass), currentUser?.id || null) .run(); } catch {} return new Response(JSON.stringify({ id: reportId, upload_url: uploadUrl }), { headers: { "content-type": "application/json", }, }); }