Skip to content
Permalink
84e7df6c30
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
221 lines (184 sloc) 5.9 KB
import { jsonError, jsonResponse } from "../../common.js";
import upload from "../../upload.js";
export async function onRequestPost(context: RequestContext) {
const { description, files, senderTokenId, turnstileResponse, usernames } =
context.data.body;
if (!context.data.current_user) {
if (typeof turnstileResponse !== "string")
return jsonError("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 jsonError("Captcha test failed", 403);
}
if (!Array.isArray(usernames))
return jsonError("Usernames must be type of array", 400);
if (
!["string", "undefined"].includes(typeof description) ||
description?.length > 512
)
return jsonError("Invalid description", 400);
if (
!Array.isArray(files) ||
files.find((file) => {
const keys = Object.keys(file);
return !keys.includes("name") || !keys.includes("size");
})
)
return jsonError("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 jsonError("One or more files contain an invalid name or size", 400);
if (!usernames.length || usernames.length > 20)
return jsonError(
"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 ||
username.match(/\W/)
)
return jsonError(`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 jsonError(
"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 jsonError(
`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>[] = [];
const filesToProcess = [];
for (const file of files) {
const fileParts = file.name.split(".");
let fileExten = fileParts.at(-1).toLowerCase();
if (fileExten === "mov") fileExten = "mp4";
if (
fileParts.length < 2 ||
!["mkv", "mp4", "wmv", "m4v", "gif", "webm"].includes(fileExten)
)
return jsonError(
`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(
upload(
context.env,
["mp4", "m4v", "webm"].includes(fileExten)
? fileUploadKey
: `t/${fileUploadKey}`,
file.size,
fileExten,
),
);
if (!["mp4", "m4v", "webm"].includes(fileExten)) {
filesToProcess.push(fileUploadKey);
}
}
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;
if (filesToProcess.length)
await context.env.DATA.put(
`coconutdata_${reportId}`,
JSON.stringify({
attachments: filesToProcess,
}),
{
expirationTtl: 1800,
},
);
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 jsonError("Failed to generate upload url", 500);
const attachments: string[] = [];
const uploadUrls: string[] = [];
for (const urlResult of uploadUrlResults as PromiseFulfilledResult<string>[]) {
uploadUrls.push(urlResult.value);
attachments.push(new URL(urlResult.value).pathname.replace(/^\/?t?\//, ""));
}
await context.env.D1.prepare(
"INSERT INTO reports (attachments, created_at, id, open, target_ids, target_usernames, user) VALUES (?, ?, ?, 1, ?, ?, ?);",
)
.bind(
JSON.stringify(attachments),
Date.now(),
reportId,
JSON.stringify(metaIDs),
JSON.stringify(metaNames),
currentUser ? JSON.stringify(currentUser) : null,
)
.run();
if (typeof senderTokenId === "string")
await context.env.D1.prepare(
"INSERT INTO push_notifications (created_at, event_id, event_type, token) VALUES (?, ?, ?, ?);",
)
.bind(Date.now(), reportId, "report", senderTokenId)
.run();
return jsonResponse(
JSON.stringify({ id: reportId, upload_urls: uploadUrls }),
);
}