Skip to content
Permalink
2127574a33
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
262 lines (218 sloc) 6.59 KB
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, description, files, 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({
remoteip: context.request.headers.get("CF-Connecting-IP"),
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);
}
const origin = context.request.headers.get("Origin");
if (!origin) return errorResponse("No origin header", 400);
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 (
!["string", "undefined"].includes(typeof description) ||
description?.length > 512
)
return errorResponse("Invalid description", 400);
if (
!Array.isArray(files) ||
files.find((file) => {
const keys = Object.keys(file);
return !keys.includes("name") || !keys.includes("size");
})
)
return errorResponse("File list missing name(s) and/or size(s)", 400);
if (
files.find(
(file) =>
typeof file.name !== "string" ||
typeof file.size !== "number" ||
file.size < 0 ||
file.size > 536870912,
)
)
return errorResponse(
"One or more files contain an invalid name or 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 uploadUrlPromises: Promise<string>[] = [];
for (const file of files) {
const fileParts = file.name.split(".");
let fileExten = fileParts.at(-1);
if (fileExten.toLowerCase() === "mov")
fileExten = "mp4"
if (
fileParts.length < 2 ||
![
"mkv",
"mp4",
"wmv",
"jpg",
"png",
"m4v",
"jpeg",
"jfif",
"gif",
"webm",
"heif",
"heic",
"webp",
].includes(fileExten.toLowerCase())
)
return errorResponse(
`File ${file.name} cannot be uploaded as it is unsupported`,
415,
);
const fileUploadKey = `${crypto.randomUUID().replaceAll("-", "")}/${crypto
.randomUUID()
.replaceAll("-", "")}${context.request.headers.get(
"cf-ray",
)}${Date.now()}`;
uploadUrlPromises.push(
GenerateUploadURL(
context.env,
`t/${fileUploadKey}`,
file.size,
fileExten,
origin,
),
);
}
const uploadUrlResults = await Promise.allSettled(uploadUrlPromises);
const reportId = `${Date.now()}${context.request.headers.get(
"cf-ray",
)}${crypto.randomUUID().replaceAll("-", "")}`;
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 (uploadUrlResults.find((uploadUrl) => uploadUrl.status === "rejected"))
return errorResponse("Failed to generate upload url", 500);
const attachments: string[] = [];
const uploadUrls: string[] = [];
for (const urlResult of uploadUrlResults as PromiseFulfilledResult<string>[]) {
uploadUrls.push(urlResult.value);
let url = urlResult.value.replace("t/", "");
const extension = (url.split(".").at(-1) as string).toLowerCase();
if (["mkv", "wmv"].includes(extension)) {
await context.env.DATA.put(`videoprocessing_${url.replace(`.${extension}`, ".mp4")}`, "1", {
expirationTtl: 600,
});
url = url.replace(`.${extension}`, ".mp4");
}
attachments.push(url);
}
await context.env.DATA.put(
`report_${reportId}`,
JSON.stringify({
attachments,
id: reportId,
open: !bypass,
user: currentUser
? {
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_urls: uploadUrls }),
{
headers: {
"content-type": "application/json",
},
},
);
}