Merge branch 'main' of https://github.com/monochrome-music/monochrome
This commit is contained in:
commit
346dfca843
3 changed files with 86 additions and 215 deletions
|
|
@ -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
29
package-lock.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue