179 lines
4.4 KiB
TypeScript

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 { 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 (!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
);
await context.env.DATA.put(
`reportprocessing_${reportId}`,
context.data.current_user.id,
{ expirationTtl: 3600 }
);
await context.env.DATA.put(
`report_${reportId}`,
JSON.stringify({
attachment: `${fileKey}.${
["mkv", "mov", "wmv"].includes(fileExt.toLowerCase()) ? "mp4" : fileExt
}`,
reporter: context.data.current_user,
target_ids: metaIDs,
target_usernames: metaNames,
}),
{
metadata: {
i: context.data.current_user.id,
r: metaIDs.toString(),
p: true,
s: `${context.data.current_user.username}#${context.data.current_user.discriminator}`,
u: metaNames.toString(),
},
}
);
return new Response(JSON.stringify({ id: reportId, upload_url: uploadUrl }), {
headers: {
"content-type": "application/json",
},
});
}