mirror of
https://github.com/ZSCGR/CloudFlare-ImgBed.git
synced 2026-08-13 04:03:42 +08:00
297 lines
10 KiB
JavaScript
297 lines
10 KiB
JavaScript
import { S3Client, DeleteObjectCommand } from "@aws-sdk/client-s3";
|
|
import { purgeCFCache, purgeRandomFileListCache, purgePublicFileListCache } from "../../../utils/purgeCache";
|
|
import { removeFileFromIndex, batchRemoveFilesFromIndex } from "../../../utils/indexManager.js";
|
|
import { getDatabase } from '../../../utils/databaseAdapter.js';
|
|
import { DiscordAPI } from '../../../utils/storage/discordAPI.js';
|
|
import { HuggingFaceAPI } from '../../../utils/storage/huggingfaceAPI.js';
|
|
import { WebDAVAPI } from '../../../utils/storage/webdavAPI.js';
|
|
import { resolveWebDAVConfig } from '../../../utils/webdavConfig.js';
|
|
import {
|
|
resolveDiscordCredentials,
|
|
resolveHuggingFaceCredentials,
|
|
resolveS3Credentials,
|
|
} from '../../../utils/channelCredentials.js';
|
|
|
|
// CORS 跨域响应头
|
|
const corsHeaders = {
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Access-Control-Allow-Methods': 'GET, DELETE, OPTIONS',
|
|
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
'Access-Control-Max-Age': '86400',
|
|
};
|
|
|
|
export async function onRequest(context) {
|
|
const { request, env, params, waitUntil } = context;
|
|
|
|
const url = new URL(request.url);
|
|
|
|
// 读取folder参数,判断是否为文件夹删除请求
|
|
const folder = url.searchParams.get('folder');
|
|
if (folder === 'true') {
|
|
try {
|
|
params.path = decodeURIComponent(params.path);
|
|
// 使用队列存储需要处理的文件夹
|
|
const folderQueue = [{
|
|
path: params.path.split(',').join('/')
|
|
}];
|
|
|
|
const deletedFiles = [];
|
|
const failedFiles = [];
|
|
|
|
while (folderQueue.length > 0) {
|
|
const currentFolder = folderQueue.shift();
|
|
|
|
// 获取指定目录下的所有文件
|
|
const listUrl = new URL(`${url.origin}/api/manage/list?count=-1&dir=${currentFolder.path}`);
|
|
const listRequest = new Request(listUrl, {
|
|
headers: request.headers,
|
|
});
|
|
const listResponse = await fetch(listRequest);
|
|
const listData = await listResponse.json();
|
|
|
|
const files = listData.files;
|
|
|
|
// 处理当前文件夹下的所有文件
|
|
for (const file of files) {
|
|
const fileId = file.name;
|
|
const cdnUrl = `https://${url.hostname}/file/${fileId}`;
|
|
|
|
const success = await deleteFile(env, fileId, cdnUrl, url);
|
|
if (success) {
|
|
deletedFiles.push(fileId);
|
|
} else {
|
|
failedFiles.push(fileId);
|
|
}
|
|
}
|
|
|
|
// 将子文件夹添加到队列
|
|
const directories = listData.directories;
|
|
for (const dir of directories) {
|
|
folderQueue.push({
|
|
path: dir
|
|
});
|
|
}
|
|
}
|
|
|
|
// 批量从索引中删除文件
|
|
if (deletedFiles.length > 0) {
|
|
waitUntil(batchRemoveFilesFromIndex(context, deletedFiles));
|
|
}
|
|
|
|
return new Response(JSON.stringify({
|
|
success: true,
|
|
deleted: deletedFiles,
|
|
failed: failedFiles
|
|
}), {
|
|
headers: { 'Content-Type': 'application/json', ...corsHeaders }
|
|
});
|
|
|
|
} catch (e) {
|
|
return new Response(JSON.stringify({
|
|
success: false,
|
|
error: e.message
|
|
}), {
|
|
status: 400,
|
|
headers: { 'Content-Type': 'application/json', ...corsHeaders }
|
|
});
|
|
}
|
|
}
|
|
|
|
// 单个文件删除处理
|
|
try {
|
|
// 解码params.path
|
|
params.path = decodeURIComponent(params.path);
|
|
const fileId = params.path.split(',').join('/');
|
|
const cdnUrl = `https://${url.hostname}/file/${fileId}`;
|
|
|
|
const success = await deleteFile(env, fileId, cdnUrl, url);
|
|
if (!success) {
|
|
throw new Error('Delete file failed');
|
|
} else {
|
|
// 从索引中删除文件
|
|
waitUntil(removeFileFromIndex(context, fileId));
|
|
}
|
|
|
|
return new Response(JSON.stringify({
|
|
success: true,
|
|
fileId: fileId
|
|
}), {
|
|
headers: { 'Content-Type': 'application/json', ...corsHeaders }
|
|
});
|
|
} catch (e) {
|
|
return new Response(JSON.stringify({
|
|
success: false,
|
|
error: e.message
|
|
}), {
|
|
status: 400,
|
|
headers: { 'Content-Type': 'application/json', ...corsHeaders }
|
|
});
|
|
}
|
|
}
|
|
|
|
// 删除单个文件的核心函数
|
|
async function deleteFile(env, fileId, cdnUrl, url) {
|
|
try {
|
|
// 读取图片信息
|
|
const db = getDatabase(env);
|
|
const img = await db.getWithMetadata(fileId);
|
|
|
|
// 如果文件记录不存在,直接返回成功(幂等删除)
|
|
if (!img) {
|
|
console.warn(`File ${fileId} not found in database, skipping delete`);
|
|
return true;
|
|
}
|
|
|
|
// 如果是R2渠道的图片,需要删除R2中对应的图片
|
|
if (img.metadata?.Channel === 'CloudflareR2') {
|
|
const R2DataBase = env.img_r2;
|
|
await R2DataBase.delete(fileId);
|
|
}
|
|
|
|
// S3 渠道的图片,需要删除S3中对应的图片
|
|
if (img.metadata?.Channel === 'S3') {
|
|
await deleteS3File(env, img);
|
|
}
|
|
|
|
// Discord 渠道的图片,需要删除 Discord 中对应的消息
|
|
if (img.metadata?.Channel === 'Discord') {
|
|
await deleteDiscordFile(env, img);
|
|
}
|
|
|
|
// HuggingFace 渠道的图片,需要删除 HuggingFace 中对应的文件
|
|
if (img.metadata?.Channel === 'HuggingFace') {
|
|
await deleteHuggingFaceFile(env, img);
|
|
}
|
|
|
|
// WebDAV 渠道的图片,需要删除 WebDAV 中对应的文件
|
|
if (img.metadata?.Channel === 'WebDAV') {
|
|
await deleteWebDAVFile(env, img);
|
|
}
|
|
|
|
// 删除数据库中的记录
|
|
// 注意:容量统计现在由索引自动维护,删除文件后索引更新时会自动重新计算
|
|
await db.delete(fileId);
|
|
|
|
// 清除CDN缓存
|
|
await purgeCFCache(env, cdnUrl);
|
|
|
|
// 清除 api/randomFileList 等API缓存
|
|
const normalizedFolder = fileId.split('/').slice(0, -1).join('/');
|
|
await purgeRandomFileListCache(url.origin, normalizedFolder);
|
|
await purgePublicFileListCache(url.origin, normalizedFolder);
|
|
|
|
return true;
|
|
} catch (e) {
|
|
console.error('Delete file failed:', e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 删除 S3 渠道的图片
|
|
async function deleteS3File(env, img) {
|
|
const db = getDatabase(env);
|
|
const s3Credentials = await resolveS3Credentials(db, env, img.metadata);
|
|
const s3Client = new S3Client({
|
|
region: s3Credentials.region || "auto",
|
|
endpoint: s3Credentials.endpoint,
|
|
credentials: {
|
|
accessKeyId: s3Credentials.accessKeyId,
|
|
secretAccessKey: s3Credentials.secretAccessKey
|
|
},
|
|
forcePathStyle: s3Credentials.pathStyle || false // 是否启用路径风格
|
|
});
|
|
|
|
const bucketName = s3Credentials.bucketName;
|
|
const key = s3Credentials.key;
|
|
|
|
try {
|
|
await s3Client.send(new DeleteObjectCommand({
|
|
Bucket: bucketName,
|
|
Key: key,
|
|
}));
|
|
return true;
|
|
} catch (error) {
|
|
console.error("S3 Delete Failed:", error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 删除 Discord 渠道的图片(删除 Discord 消息)
|
|
async function deleteDiscordFile(env, img) {
|
|
const db = getDatabase(env);
|
|
const discordCredentials = await resolveDiscordCredentials(db, env, img.metadata);
|
|
const botToken = discordCredentials.botToken;
|
|
const channelId = discordCredentials.channelId;
|
|
const messageId = discordCredentials.messageId;
|
|
|
|
if (!botToken || !channelId || !messageId) {
|
|
console.warn('Discord file missing required metadata for deletion');
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
const discordAPI = new DiscordAPI(botToken);
|
|
const success = await discordAPI.deleteMessage(channelId, messageId);
|
|
if (!success) {
|
|
console.error('Discord Delete Failed: API returned false');
|
|
}
|
|
return success;
|
|
} catch (error) {
|
|
console.error("Discord Delete Failed:", error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
// 删除 HuggingFace 渠道的图片
|
|
async function deleteHuggingFaceFile(env, img) {
|
|
const db = getDatabase(env);
|
|
const hfCredentials = await resolveHuggingFaceCredentials(db, env, img.metadata);
|
|
const token = hfCredentials.token;
|
|
const repo = hfCredentials.repo;
|
|
const filePath = hfCredentials.filePath;
|
|
const isPrivate = hfCredentials.isPrivate || false;
|
|
|
|
if (!token || !repo || !filePath) {
|
|
console.warn('HuggingFace file missing required metadata for deletion');
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
const huggingfaceAPI = new HuggingFaceAPI(token, repo, isPrivate);
|
|
const success = await huggingfaceAPI.deleteFile(filePath, `Delete ${filePath}`);
|
|
if (!success) {
|
|
console.error('HuggingFace Delete Failed: API returned false');
|
|
}
|
|
return success;
|
|
} catch (error) {
|
|
console.error("HuggingFace Delete Failed:", error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
// 删除 WebDAV 渠道的图片
|
|
async function deleteWebDAVFile(env, img) {
|
|
const filePath = img.metadata?.WebDAVFilePath;
|
|
|
|
if (!filePath) {
|
|
console.warn('WebDAV file missing required metadata for deletion');
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
const webdavConfig = await resolveWebDAVConfig(env, img.metadata);
|
|
if (!webdavConfig) {
|
|
console.warn('WebDAV channel config not found for deletion');
|
|
return false;
|
|
}
|
|
|
|
const webdavAPI = new WebDAVAPI(webdavConfig);
|
|
return await webdavAPI.deleteFile(filePath);
|
|
} catch (error) {
|
|
console.error("WebDAV Delete Failed:", error);
|
|
return false;
|
|
}
|
|
}
|