diff --git a/functions/upload.js b/functions/upload.js deleted file mode 100644 index 31a1183..0000000 --- a/functions/upload.js +++ /dev/null @@ -1,195 +0,0 @@ -const API_URL = 'https://catbox.moe/user/api.php'; -const R2_PUBLIC_URL = 'https://cucks.qzz.io'; - -const R2_ENDPOINT = 'https://faae2f5c0a232c7f3733ef597c55bd69.r2.cloudflarestorage.com'; -const R2_BUCKET = 'monochrome-image-uploads'; - -async function hmac(key, data) { - const cryptoKey = await crypto.subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-1' }, false, ['sign']); - return new Uint8Array(await crypto.subtle.sign('HMAC', cryptoKey, data)); -} - -async function sha256(data) { - return new Uint8Array(await crypto.subtle.digest('SHA-256', data)); -} - -function buf2hex(buffer) { - return Array.from(buffer) - .map((b) => b.toString(16).padStart(2, '0')) - .join(''); -} - -async function signature(secretKey, dateStamp, region, service, stringToSign) { - const kDate = await hmac(new TextEncoder().encode('AWS4' + secretKey), new TextEncoder().encode(dateStamp)); - const kRegion = await hmac(kDate, new TextEncoder().encode(region)); - const kService = await hmac(kRegion, new TextEncoder().encode(service)); - const kSigning = await hmac(kService, new TextEncoder().encode('aws4_request')); - const sig = await hmac(kSigning, new TextEncoder().encode(stringToSign)); - return buf2hex(sig); -} - -async function createSignature(method, path, headers, payloadHash, accessKeyId, secretAccessKey, amzDate, dateStamp) { - const region = 'auto'; - const service = 's3'; - - const signedHeaders = Object.keys(headers).sort().join(';'); - const canonicalHeaders = - Object.entries(headers) - .sort((a, b) => a[0].localeCompare(b[0])) - .map(([k, v]) => `${k.toLowerCase()}:${v}`) - .join('\n') + '\n'; - - const canonicalRequest = `${method}\n${path}\n\n${canonicalHeaders}\n${signedHeaders}\n${payloadHash}`; - const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`; - const stringToSign = `AWS4-HMAC-SHA256\n${amzDate}\n${credentialScope}\n${buf2hex(await sha256(new TextEncoder().encode(canonicalRequest)))}`; - - const sig = await signature(secretAccessKey, dateStamp, region, service, stringToSign); - return `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${sig}`; -} - -export async function onRequest(context) { - const { request, env } = context; - - if (request.method === 'OPTIONS') { - return new Response(null, { status: 204, headers: corsHeaders() }); - } - - if (request.method !== 'POST') { - return jsonError('Method not allowed', 405); - } - - const useR2 = env.R2_ENABLED === 'true'; - - try { - const contentType = request.headers.get('content-type') || ''; - let file; - let fileName; - let fileType; - - if (contentType.includes('application/json')) { - const body = await request.json(); - if (!body.fileUrl) return jsonError('No fileUrl provided', 400); - - const res = await fetch(body.fileUrl); - if (!res.ok) throw new Error('Failed to fetch remote file'); - - file = await res.arrayBuffer(); - fileName = body.fileName || body.fileUrl.split('/').pop(); - fileType = res.headers.get('content-type') || 'application/octet-stream'; - } else { - const form = await request.formData(); - const uploaded = form.get('file'); - if (!uploaded) return jsonError('No file provided', 400); - - if (uploaded.size > 10 * 1024 * 1024) { - return jsonError('File exceeds 10MB', 400); - } - - file = await uploaded.arrayBuffer(); - fileName = uploaded.name; - fileType = uploaded.type || 'application/octet-stream'; - } - - let url; - - if (useR2) { - try { - const key = `${Date.now()}-${fileName}`; - const now = new Date(); - const amzDate = - now - .toISOString() - .replace(/[:-]|\.\d{3}/g, '') - .slice(0, 15) + 'Z'; - const dateStamp = now - .toISOString() - .replace(/[:-]|\.\d{3}/g, '') - .slice(0, 8); - const payloadHash = buf2hex(await sha256(file)); - - const host = new URL(R2_ENDPOINT).host; - const headers = { - 'Content-Type': fileType, - 'Content-Length': file.byteLength, - 'x-amz-date': amzDate, - 'x-amz-content-sha256': payloadHash, - Host: host, - }; - - const auth = await createSignature( - 'PUT', - `/${R2_BUCKET}/${key}`, - headers, - payloadHash, - env.R2_ACCESS_KEY_ID, - env.R2_SECRET_ACCESS_KEY, - amzDate, - dateStamp - ); - headers['Authorization'] = auth; - - const res = await fetch(`${R2_ENDPOINT}/${R2_BUCKET}/${key}`, { - method: 'PUT', - headers, - body: new Uint8Array(file), - }); - - if (!res.ok) { - const err = await res.text(); - throw new Error(`R2 error: ${res.status} - ${err}`); - } - - url = `${R2_PUBLIC_URL}/${key}`; - } catch (r2Err) { - console.error('R2 upload error:', r2Err); - return jsonError(`R2 upload failed: ${r2Err.message}`, 500); - } - } else { - const formData = new FormData(); - formData.append('reqtype', 'fileupload'); - formData.append('fileToUpload', new Blob([file], { type: fileType }), fileName); - - const response = await fetch(API_URL, { - method: 'POST', - body: formData, - }); - - const responseText = await response.text(); - - if (!response.ok) { - throw new Error(`Upload failed: ${responseText}`); - } - - url = responseText.trim(); - } - - return jsonResponse({ - success: true, - url: url, - }); - } catch (err) { - return jsonError(err.message, 500); - } -} - -function jsonResponse(obj, status = 200) { - return new Response(JSON.stringify(obj), { - status, - headers: { - 'Content-Type': 'application/json', - ...corsHeaders(), - }, - }); -} - -function jsonError(message, status) { - return jsonResponse({ success: false, error: message }, status); -} - -function corsHeaders() { - return { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'POST, OPTIONS', - 'Access-Control-Allow-Headers': '*', - }; -}