function arrBufToB64Url(buf: ArrayBuffer) { const b64data = btoa(String.fromCharCode(...new Uint8Array(buf))); return b64data.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); } function stringToBuffer(str: string) { const buffer = new ArrayBuffer(str.length); const ui8 = new Uint8Array(buffer); for (let i = 0, bufLen = str.length; i < bufLen; i++) { ui8[i] = str.charCodeAt(i); } return buffer; } export async function GenerateUploadURL( env: Env, path: string, size: number, fileExt: string, origin: string, ): Promise { const accessToken = await GetAccessToken(env); const contentTypes: { [k: string]: string } = { gif: "image/gif", m4v: "video/x-m4v", mkv: "video/x-matroska", mov: "video/mp4", mp4: "video/mp4", webm: "video/webm", wmv: "video/x-ms-wmv", }; const resumableUploadReq = await fetch( `https://storage.googleapis.com/upload/storage/v1/b/portal-carcrushers-cc/o?uploadType=resumable&name=${encodeURIComponent( path, )}`, { headers: { authorization: `Bearer ${accessToken}`, origin, "x-upload-content-type": contentTypes[fileExt], "x-upload-content-length": size.toString(), }, method: "POST", }, ); if (!resumableUploadReq.ok) throw new Error( `Failed to create resumable upload: ${await resumableUploadReq.text()}`, ); const url = resumableUploadReq.headers.get("location"); if (!url) throw new Error("No location header returned"); return url; } export async function GetAccessToken(env: Env): Promise { const claimSet = btoa( JSON.stringify({ aud: "https://oauth2.googleapis.com/token", exp: Math.floor(Date.now() / 1000) + 120, iat: Math.floor(Date.now() / 1000), iss: env.WORKER_GSERVICEACCOUNT, scope: "https://www.googleapis.com/auth/cloud-platform", }), ) .replaceAll("+", "-") .replaceAll("/", "_") .replaceAll("=", ""); const signingKey = await crypto.subtle.importKey( "pkcs8", stringToBuffer(atob(env.STORAGE_ACCOUNT_KEY.replace(/(\r\n|\n|\r)/gm, ""))), { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["sign"], ); const signature = await crypto.subtle.sign( { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, signingKey, stringToBuffer(`eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.${claimSet}`), ); const assertion = `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.${claimSet}.${arrBufToB64Url( signature, )}`; const tokenRequest = await fetch("https://oauth2.googleapis.com/token", { body: `grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=${assertion}`, headers: { "content-type": "application/x-www-form-urlencoded", }, method: "POST", }); if (!tokenRequest.ok) throw new Error(`Failed to get access token: ${await tokenRequest.text()}`); return ((await tokenRequest.json()) as { [k: string]: any }).access_token; } async function getKeyIDs( access_token: string, projectId: string, keys: { partitionId: { projectId: string }; path: { kind: string }[] }[], ) { const keyRequest = await fetch( `https://datastore.googleapis.com/v1/projects/${projectId}:allocateIds`, { body: JSON.stringify({ keys }), headers: { authorization: `Bearer ${access_token}`, "content-type": "application/json", }, method: "POST", }, ); if (!keyRequest.ok) { console.log(await keyRequest.json()); throw new Error("Failed to allocate key IDs"); } return ((await keyRequest.json()) as { keys: { [k: string]: any }[] }).keys; } export async function insertLogs( userActionMap: { [k: string]: number }, reportId: string, context: RequestContext, ) { const accessToken = await GetAccessToken(context.env); const actionBaseURLs: { [k: number]: string } = { 1: "https://portal.carcrushers.cc/view-closed-report/", 2: "https://portal.carcrushers.cc/view-closed-report/", 3: "", 4: "https://portal.carcrushers.cc/game-appeals/", }; const actionIntegers: { [k: number]: string } = { 1: "blacklist", 2: "ban", 3: "revoke", 4: "accept_appeal", }; const incompleteLogKey = { partitionId: { projectId: context.env.DATASTORE_PROJECT, }, path: [ { kind: "log", }, ], }; const payload: { mode: string; mutations: { [k: string]: any }[] } = { mode: "NON_TRANSACTIONAL", mutations: [], }; const preAllocatedLogKeys = []; while (preAllocatedLogKeys.length < Object.keys(userActionMap).length) preAllocatedLogKeys.push(incompleteLogKey); const keys = await getKeyIDs( accessToken, context.env.DATASTORE_PROJECT, preAllocatedLogKeys, ); for (const [user, action] of Object.entries(userActionMap)) { payload.mutations.push({ insert: { key: keys.pop(), properties: { action: { stringValue: actionIntegers[action], }, evidence: { stringValue: actionBaseURLs[action] + reportId, }, executed_at: { integerValue: Date.now(), }, executor: { stringValue: context.data.current_user.id, }, target: { integerValue: user, }, }, }, }); } const mutationRequest = await fetch( `https://datastore.googleapis.com/v1/projects/${context.env.DATASTORE_PROJECT}:commit`, { body: JSON.stringify(payload), headers: { authorization: `Bearer ${accessToken}`, "content-type": "application/json", }, method: "POST", }, ); if (!mutationRequest.ok) { console.log(await mutationRequest.json()); throw new Error("Failed to commit mutation"); } return await mutationRequest.json(); } export async function queryLogs(user: number, context: RequestContext) { const accessToken = await GetAccessToken(context.env); const queryRequest = await fetch( `https://datastore.googleapis.com/v1/projects/${context.env.DATASTORE_PROJECT}:runQuery`, { body: JSON.stringify({ partitionId: { projectId: context.env.DATASTORE_PROJECT, }, query: { filter: { propertyFilter: { op: "EQUAL", property: { name: "target", }, value: { integerValue: user.toString(), }, }, }, kind: [ { name: "log", }, ], }, }), headers: { authorization: `Bearer ${accessToken}`, "content-type": "application/json", }, method: "POST", }, ); if (!queryRequest.ok) { console.log(await queryRequest.json()); throw new Error("Failed to query logs"); } return ( ( (await queryRequest.json()) as { batch: { entityResults: { cursor: string; entity: { [k: string]: any }; version: string; }[]; }; } ).batch?.entityResults ?? [] ); } export async function sendPushNotification( env: Env, title: string, body: string, token?: string, ) { const message = JSON.stringify({ notification: { body, title, }, token, }); const notifResp = await fetch( "https://fcm.googleapis.com/v1/projects/car-crushers-mobile/messages:send", { body: JSON.stringify({ message }), headers: { authorization: `Bearer ${await GetAccessToken(env)}`, "content-type": "application/json", }, method: "POST", }, ); if (!notifResp.ok) throw new Error(await notifResp.json()); }