This commit is contained in:
Eduard Prigoana 2026-03-26 20:02:41 +02:00
commit 346dfca843
3 changed files with 86 additions and 215 deletions

View file

@ -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': '*',
};
}

29
package-lock.json generated
View file

@ -104,7 +104,6 @@
"version": "7.29.0",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@ -2025,7 +2024,6 @@
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.2.0.tgz",
"integrity": "sha512-oKaoNeNtH2iIZMDFVrb1atoyRECDGHcfLMunJ5KWN8DtvpVBeeA4c41e20NTuhMxw1cSYbpq2PV2hb+/9CJxlQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
@ -2156,7 +2154,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
@ -2201,7 +2198,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
}
@ -4032,7 +4028,9 @@
"license": "MIT"
},
"node_modules/@rollup/pluginutils/node_modules/picomatch": {
"version": "2.3.1",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
@ -4670,7 +4668,6 @@
"version": "25.5.0",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.18.0"
}
@ -4747,7 +4744,6 @@
"version": "8.16.0",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@ -4803,7 +4799,6 @@
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@ -5260,7 +5255,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@ -7204,7 +7198,6 @@
"version": "9.39.4",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@ -9703,7 +9696,6 @@
"integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@keyv/serialize": "^1.1.1"
}
@ -10007,7 +9999,9 @@
}
},
"node_modules/micromatch/node_modules/picomatch": {
"version": "2.3.1",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
@ -12127,7 +12121,6 @@
"version": "4.0.3",
"inBundle": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@ -12485,7 +12478,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.3",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@ -12558,7 +12553,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@ -12632,7 +12626,6 @@
"version": "7.1.1",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@ -13594,7 +13587,6 @@
"integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"rollup": "dist/bin/rollup"
},
@ -14411,7 +14403,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-syntax-patches-for-csstree": "^1.0.19",
@ -14805,7 +14796,6 @@
"version": "5.46.1",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.15.0",
@ -15113,7 +15103,6 @@
"version": "5.9.3",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"

View file

@ -1,4 +1,70 @@
[
{
"type": "album",
"id": 18083938,
"title": "The Glow, Pt. 2",
"artist": { "id": 3941394, "name": "The Microphones" },
"releaseDate": "2001-09-25",
"cover": "ec648c22-9140-41d3-a7ae-0ba69ef0420e",
"explicit": false,
"audioQuality": "LOSSLESS",
"mediaMetadata": { "tags": ["LOSSLESS"] }
},
{
"type": "album",
"id": 382839956,
"title": "my anti-aircraft friend",
"artist": { "id": 19359095, "name": "julie" },
"releaseDate": "2024-09-13",
"cover": "ac790b52-61cc-460c-9277-bdac88722cc3",
"explicit": false,
"audioQuality": "LOSSLESS",
"mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] }
},
{
"type": "album",
"id": 456475370,
"title": "It Aint Nun",
"artist": { "id": 9981740, "name": "CHRIST DILLINGER" },
"releaseDate": "2024-09-12",
"cover": "3ffc27f6-a77c-4e68-ba44-e04f034783be",
"explicit": true,
"audioQuality": "LOSSLESS",
"mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] }
},
{
"type": "album",
"id": 112547160,
"title": "R F Y",
"artist": { "id": 36042731, "name": "RFY" },
"releaseDate": "2019-06-27",
"cover": "ffe3f6f9-5bc8-4b53-99c3-9b83676c099a",
"explicit": true,
"audioQuality": "LOSSLESS",
"mediaMetadata": { "tags": ["LOSSLESS"] }
},
{
"type": "album",
"id": 18658568,
"title": "100% Ghetto 4",
"artist": { "id": 4191963, "name": "DJ Clent" },
"releaseDate": "2010-10-02",
"cover": "82b839bd-7cf6-4650-bade-192f47301ffd",
"explicit": true,
"audioQuality": "LOSSLESS",
"mediaMetadata": { "tags": ["LOSSLESS"] }
},
{
"type": "album",
"id": 504004321,
"title": "Half Blood (BloodLuxe)",
"artist": { "id": 50799233, "name": "slayr" },
"releaseDate": "2025-11-05",
"cover": "2767cc63-7e92-4a48-aa4b-806a3ea7ec1c",
"explicit": true,
"audioQuality": "LOSSLESS",
"mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] }
},
{
"type": "album",
"id": 89313048,
@ -164,6 +230,17 @@
"audioQuality": "LOSSLESS",
"mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] }
},
{
"type": "album",
"id": 365819314,
"title": "One Life",
"artist": { "id": "17300439", "name": "1oneam" },
"releaseDate": "2024-05-30",
"cover": "eb5d74f6-7403-4404-8452-9b68713445fe",
"explicit": true,
"audioQuality": "LOSSLESS",
"mediaMetadata": { "tags": ["LOSSLESS"] }
},
{
"type": "album",
"id": 379517195,