update v2.5.7

This commit is contained in:
MarSeventh
2026-01-30 16:14:19 +08:00
parent 62357b50e3
commit a03766a9f9
33 changed files with 314 additions and 107 deletions

File diff suppressed because one or more lines are too long

BIN
css/157.3f84c0cf.css.gz Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -1,4 +1,4 @@
import { purgeCFCache } from "../../../utils/purgeCache";
import { purgeCFCache, purgeRandomFileListCache, purgePublicFileListCache } from "../../../utils/purgeCache";
import { addFileToIndex } from "../../../utils/indexManager.js";
import { getDatabase } from "../../../utils/databaseAdapter.js";
@@ -15,12 +15,12 @@ export async function onRequest(context) {
// 组装 CDN URL
const url = new URL(request.url);
if (params.path) {
params.path = String(params.path).split(',').join('/');
}
const cdnUrl = `https://${url.hostname}/file/${params.path}`;
// 解码params.path
params.path = decodeURIComponent(params.path);
@@ -36,9 +36,13 @@ export async function onRequest(context) {
// 清除CDN缓存
await purgeCFCache(env, cdnUrl);
// 清除 randomFileList 等API缓存
const normalizedFolder = params.path.split('/').slice(0, -1).join('/');
await purgeRandomFileListCache(url.origin, normalizedFolder);
await purgePublicFileListCache(url.origin, normalizedFolder);
// 更新索引
waitUntil(addFileToIndex(context, params.path, value.metadata));
return new Response(info);
}
}

View File

@@ -1,5 +1,5 @@
import { S3Client, DeleteObjectCommand } from "@aws-sdk/client-s3";
import { purgeCFCache } from "../../../utils/purgeCache";
import { purgeCFCache, purgeRandomFileListCache, purgePublicFileListCache } from "../../../utils/purgeCache";
import { removeFileFromIndex, batchRemoveFilesFromIndex } from "../../../utils/indexManager.js";
import { getDatabase } from '../../../utils/databaseAdapter.js';
import { DiscordAPI } from '../../../utils/discordAPI.js';
@@ -161,18 +161,10 @@ async function deleteFile(env, fileId, cdnUrl, url) {
// 清除CDN缓存
await purgeCFCache(env, cdnUrl);
// 清除randomFileList API缓存
try {
const cache = caches.default;
const nullResponse = new Response(null, {
headers: { 'Cache-Control': 'max-age=0' },
});
const normalizedFolder = fileId.split('/').slice(0, -1).join('/');
await cache.put(`${url.origin}/api/randomFileList?dir=${normalizedFolder}`, nullResponse);
} catch (error) {
console.error('Failed to clear cache:', error);
}
// 清除 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) {

View File

@@ -1,5 +1,5 @@
import { S3Client, CopyObjectCommand, DeleteObjectCommand } from "@aws-sdk/client-s3";
import { purgeCFCache } from "../../../utils/purgeCache";
import { purgeCFCache, purgeRandomFileListCache, purgePublicFileListCache } from "../../../utils/purgeCache";
import { moveFileInIndex, batchMoveFilesInIndex } from "../../../utils/indexManager.js";
import { getDatabase } from '../../../utils/databaseAdapter.js';
@@ -177,20 +177,11 @@ async function moveFile(env, fileId, newFileId, cdnUrl, url) {
// 清除CDN缓存
await purgeCFCache(env, cdnUrl);
// 清除randomFileList API缓存
try {
const cache = caches.default;
const nullResponse = new Response(null, {
headers: { 'Cache-Control': 'max-age=0' },
});
const normalizedFolder = fileId.split('/').slice(0, -1).join('/');
const normalizedDist = newFileId.split('/').slice(0, -1).join('/');
await cache.put(`${url.origin}/api/randomFileList?dir=${normalizedFolder}`, nullResponse);
await cache.put(`${url.origin}/api/randomFileList?dir=${normalizedDist}`, nullResponse);
} catch (error) {
console.error('Failed to clear cache:', error);
}
// 清除 api/randomFileList API缓存
const normalizedFolder = fileId.split('/').slice(0, -1).join('/');
const normalizedDist = newFileId.split('/').slice(0, -1).join('/');
await purgeRandomFileListCache(url.origin, normalizedFolder, normalizedDist);
await purgePublicFileListCache(url.origin, normalizedFolder, normalizedDist);
return true;
} catch (e) {

View File

@@ -1,4 +1,4 @@
import { purgeCFCache } from "../../../utils/purgeCache";
import { purgeCFCache, purgeRandomFileListCache, purgePublicFileListCache } from "../../../utils/purgeCache";
import { addFileToIndex } from "../../../utils/indexManager.js";
import { getDatabase } from "../../../utils/databaseAdapter.js";
@@ -15,12 +15,12 @@ export async function onRequest(context) {
// 组装 CDN URL
const url = new URL(request.url);
if (params.path) {
params.path = String(params.path).split(',').join('/');
}
const cdnUrl = `https://${url.hostname}/file/${params.path}`;
// 解码params.path
params.path = decodeURIComponent(params.path);
@@ -36,9 +36,13 @@ export async function onRequest(context) {
// 清除CDN缓存
await purgeCFCache(env, cdnUrl);
// 清除 randomFileList 等API缓存
const normalizedFolder = params.path.split('/').slice(0, -1).join('/');
await purgeRandomFileListCache(url.origin, normalizedFolder);
await purgePublicFileListCache(url.origin, normalizedFolder);
// 更新索引
waitUntil(addFileToIndex(context, params.path, value.metadata));
return new Response(info);
}
}

View File

@@ -47,6 +47,70 @@ function isAllowedDirectory(dir, allowedDirs) {
return false;
}
/**
* 获取公开浏览文件列表(带缓存)
* @param {Object} context - 上下文对象
* @param {URL} url - 请求URL
* @param {string} dir - 目录
* @param {boolean} recursive - 是否递归
* @returns {Promise<Object>} 文件列表和目录列表,包含 fromCache 字段
*/
async function getPublicFileList(context, url, dir, recursive) {
// 构建缓存键(目录格式去掉末尾的/,与清除缓存时的格式一致)
const cacheDir = dir.replace(/\/$/, '');
const cacheKey = `${url.origin}/api/publicFileList?dir=${cacheDir}&recursive=${recursive}`;
// 检查缓存中是否有记录
const cache = caches.default;
const cacheRes = await cache.match(cacheKey);
if (cacheRes) {
const data = JSON.parse(await cacheRes.text());
data.fromCache = true;
return data;
}
// 读取文件列表
const result = await readIndex(context, {
directory: dir,
start: 0,
count: -1,
includeSubdirFiles: recursive,
accessStatus: 'normal', // 只返回正常可访问的内容
});
if (!result.success) {
return { files: [], directories: [], totalCount: 0, fromCache: false };
}
// 转换文件格式(只保留必要信息)
const files = result.files.map(file => ({
id: file.id,
metadata: {
FileType: file.metadata?.FileType,
TimeStamp: file.metadata?.TimeStamp,
FileSize: file.metadata?.FileSize,
}
}));
const cacheData = {
files,
directories: result.directories,
totalCount: result.totalCount,
};
// 缓存结果缓存时间为24小时
await cache.put(cacheKey, new Response(JSON.stringify(cacheData), {
headers: {
"Content-Type": "application/json",
}
}), {
expirationTtl: 24 * 60 * 60
});
cacheData.fromCache = false;
return cacheData;
}
export async function onRequest(context) {
const { request, env } = context;
const url = new URL(request.url);
@@ -88,13 +152,13 @@ export async function onRequest(context) {
let dir = url.searchParams.get('dir') || '';
let search = url.searchParams.get('search') || '';
if (search) {
search = decodeURIComponent(search).trim();
search = decodeURIComponent(search).trim().toLowerCase();
}
// 获取高级搜索参数
const recursive = url.searchParams.get('recursive') === 'true';
const fileType = url.searchParams.get('type') || ''; // image, video, audio, other
// 检查目录权限
if (!isAllowedDirectory(dir, allowedDirs)) {
return new Response(JSON.stringify({ error: 'Directory not allowed' }), {
@@ -115,24 +179,11 @@ export async function onRequest(context) {
const start = parseInt(url.searchParams.get('start'), 10) || 0;
const count = parseInt(url.searchParams.get('count'), 10) || 50;
// 取文件列表(获取全部,因为需要先过滤 block/adult
const result = await readIndex(context, {
directory: dir,
search,
start: 0,
count: -1, // 获取全部
includeSubdirFiles: recursive,
});
if (!result.success) {
return new Response(JSON.stringify({ error: 'Failed to read file list' }), {
status: 500,
headers: { 'Content-Type': 'application/json', ...corsHeaders }
});
}
// 取文件列表(带缓存
const cachedData = await getPublicFileList(context, url, dir, recursive);
// 过滤子目录,只返回允许的目录
const filteredDirectories = result.directories.filter(subDir => {
const filteredDirectories = cachedData.directories.filter(subDir => {
return isAllowedDirectory(subDir, allowedDirs);
});
@@ -140,22 +191,20 @@ export async function onRequest(context) {
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg', 'avif'];
const videoExts = ['mp4', 'webm', 'ogg', 'mov', 'm4v', 'mkv', 'avi', '3gp', 'mpeg', 'mpg', 'flv', 'wmv', 'ts', 'rmvb'];
const audioExts = ['mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a', 'wma', 'ape', 'opus'];
const getFileExt = (name) => (name.split('.').pop() || '').toLowerCase();
const isImageFile = (name) => imageExts.includes(getFileExt(name));
const isVideoFile = (name) => videoExts.includes(getFileExt(name));
const isAudioFile = (name) => audioExts.includes(getFileExt(name));
// 过滤掉 block 和 adult 图片(公开浏览不应显示这些内容)
let filteredFiles = result.files.filter(file => {
const listType = file.metadata?.ListType;
const label = file.metadata?.Label;
// 排除被屏蔽的和成人内容
if (listType === 'Block' || label === 'adult') {
return false;
}
return true;
});
let filteredFiles = cachedData.files;
// 搜索过滤
if (search) {
filteredFiles = filteredFiles.filter(file => {
return file.id.toLowerCase().includes(search);
});
}
// 按文件类型过滤
if (fileType) {
@@ -176,23 +225,19 @@ export async function onRequest(context) {
// 过滤后再分页
filteredFiles = filteredFiles.slice(start, start + count);
// 转换文件格式(只返回必要信息,隐藏敏感元数据)
// 转换文件格式
const safeFiles = filteredFiles.map(file => ({
name: file.id,
metadata: {
FileType: file.metadata?.FileType,
TimeStamp: file.metadata?.TimeStamp,
FileSize: file.metadata?.FileSize,
// 不返回 Channel、IP、Label 等敏感信息
}
metadata: file.metadata
}));
return new Response(JSON.stringify({
files: safeFiles,
directories: filteredDirectories,
totalCount: fileType ? filteredTotalCount : result.totalCount,
totalCount: (search || fileType) ? filteredTotalCount : cachedData.totalCount,
returnedCount: safeFiles.length,
allowedDirs: allowedDirs, // 返回允许的目录列表供前端使用
fromCache: cachedData.fromCache,
}), {
headers: { 'Content-Type': 'application/json', ...corsHeaders }
});

View File

@@ -40,6 +40,9 @@ export async function onRequest(context) {
fileType = fileType.split(',');
}
// 读取图片方向参数landscape(横图), portrait(竖图), square(方图)
const orientation = requestUrl.searchParams.get('orientation') || '';
// 读取指定文件夹
const paramDir = requestUrl.searchParams.get('dir') || '';
const dir = paramDir.replace(/^\/+/, '').replace(/\/{2,}/g, '/').replace(/\/$/, '');
@@ -62,6 +65,27 @@ export async function onRequest(context) {
// 筛选出符合fileType要求的记录
allRecords = allRecords.filter(item => { return fileType.some(type => item.FileType?.includes(type)) });
// 根据图片方向筛选
if (orientation && allRecords.length > 0) {
const SQUARE_THRESHOLD = 0.1; // 宽高比差异小于10%视为方图
allRecords = allRecords.filter(item => {
// 如果没有尺寸信息,跳过该记录
if (!item.Width || !item.Height) return false;
const ratio = item.Width / item.Height;
switch (orientation) {
case 'landscape': // 横图:宽 > 高
return ratio > (1 + SQUARE_THRESHOLD);
case 'portrait': // 竖图:高 > 宽
return ratio < (1 - SQUARE_THRESHOLD);
case 'square': // 方图:宽 ≈ 高
return ratio >= (1 - SQUARE_THRESHOLD) && ratio <= (1 + SQUARE_THRESHOLD);
default:
return true;
}
});
}
if (allRecords.length == 0) {
return new Response(JSON.stringify({}), { status: 200 });
@@ -109,13 +133,15 @@ async function getRandomFileList(context, url, dir) {
return JSON.parse(await cacheRes.text());
}
let allRecords = await readIndex(context, { directory: dir, count: -1, includeSubdirFiles: true });
let allRecords = await readIndex(context, { directory: dir, count: -1, includeSubdirFiles: true, accessStatus: 'normal' });
// 仅保留记录的name和metadata中的FileType字段
// 仅保留记录的name和metadata中的必要字段
allRecords = allRecords.files?.map(item => {
return {
name: item.id,
FileType: item.metadata?.FileType
FileType: item.metadata?.FileType,
Width: item.metadata?.Width,
Height: item.metadata?.Height
}
});

View File

@@ -276,6 +276,7 @@ async function mergeR2ChunksInfo(context, uploadId, completedChunks, metadata) {
}
metadata.ChannelName = r2ChannelName;
metadata.FileSize = (totalSize / 1024 / 1024).toFixed(2);
metadata.FileSizeBytes = totalSize;
// 清理multipart info
await db.delete(multipartKey);
@@ -372,6 +373,7 @@ async function mergeS3ChunksInfo(context, uploadId, completedChunks, metadata) {
metadata.Channel = "S3";
metadata.ChannelName = s3Channel.name;
metadata.FileSize = (totalSize / 1024 / 1024).toFixed(2);
metadata.FileSizeBytes = totalSize;
const s3ServerDomain = endpoint.replace(/https?:\/\//, "");
if (pathStyle) {
@@ -467,6 +469,7 @@ async function mergeTelegramChunksInfo(context, uploadId, completedChunks, metad
metadata.IsChunked = true;
metadata.TotalChunks = completedChunks.length;
metadata.FileSize = (totalSize / 1024 / 1024).toFixed(2);
metadata.FileSizeBytes = totalSize;
// 将分片信息存储到value中
const chunksData = JSON.stringify(chunks);
@@ -546,6 +549,7 @@ async function mergeDiscordChunksInfo(context, uploadId, completedChunks, metada
metadata.IsChunked = true;
metadata.TotalChunks = completedChunks.length;
metadata.FileSize = (totalSize / 1024 / 1024).toFixed(2);
metadata.FileSizeBytes = totalSize;
// 将分片信息存储到value中
const chunksData = JSON.stringify(chunks);

View File

@@ -2,7 +2,7 @@ import { userAuthCheck, UnauthorizedResponse } from "../utils/userAuth";
import { fetchUploadConfig, fetchSecurityConfig } from "../utils/sysConfig";
import {
createResponse, getUploadIp, getIPAddress, isExtValid,
moderateContent, purgeCDNCache, isBlockedUploadIp, buildUniqueFileId, endUpload
moderateContent, purgeCDNCache, isBlockedUploadIp, buildUniqueFileId, endUpload, getImageDimensions
} from "./uploadTools";
import { initializeChunkedUpload, handleChunkUpload, uploadLargeFileToTelegram, handleCleanupRequest } from "./chunkUpload";
import { handleChunkMerge } from "./chunkMerge";
@@ -124,15 +124,32 @@ async function processFileUpload(context, formdata = null) {
// 获取文件信息
const time = new Date().getTime();
const fileType = formdata.get('file').type;
let fileName = formdata.get('file').name;
const fileSize = (formdata.get('file').size / 1024 / 1024).toFixed(2); // 文件大小单位MB
const file = formdata.get('file');
const fileType = file.type;
let fileName = file.name;
const fileSizeBytes = file.size; // 文件大小,单位字节
const fileSize = (fileSizeBytes / 1024 / 1024).toFixed(2); // 文件大小单位MB
// 检查fileType和fileName是否存在
if (fileType === null || fileType === undefined || fileName === null || fileName === undefined) {
return createResponse('Error: fileType or fileName is wrong, check the integrity of this file!', { status: 400 });
}
// 提取图片尺寸
let imageDimensions = null;
if (fileType.startsWith('image/')) {
try {
// JPEG 需要更多数据EXIF 可能很大),其他格式 512 字节足够
const lowerType = fileType.toLowerCase();
const isJpeg = lowerType === 'image/jpeg' || lowerType === 'image/jpg' || lowerType === 'image/pjpeg';
const readSize = isJpeg ? 65536 : 512;
const headerBuffer = await file.slice(0, readSize).arrayBuffer();
imageDimensions = getImageDimensions(headerBuffer, fileType);
} catch (error) {
console.error('Error reading image dimensions:', error);
}
}
// 如果上传文件夹路径为空,尝试从文件名中获取
if (uploadFolder === '' || uploadFolder === null || uploadFolder === undefined) {
uploadFolder = fileName.split('/').slice(0, -1).join('/');
@@ -148,6 +165,7 @@ async function processFileUpload(context, formdata = null) {
FileName: fileName,
FileType: fileType,
FileSize: fileSize,
FileSizeBytes: fileSizeBytes,
UploadIP: uploadIp,
UploadAddress: ipAddress,
ListType: "None",
@@ -157,6 +175,12 @@ async function processFileUpload(context, formdata = null) {
Tags: []
};
// 添加图片尺寸信息
if (imageDimensions) {
metadata.Width = imageDimensions.width;
metadata.Height = imageDimensions.height;
}
let fileExt = fileName.split('.').pop(); // 文件扩展名
if (!isExtValid(fileExt)) {
// 如果文件名中没有扩展名,尝试从文件类型中获取

View File

@@ -1,5 +1,5 @@
import { fetchSecurityConfig } from "../utils/sysConfig";
import { purgeCFCache } from "../utils/purgeCache";
import { purgeCFCache, purgeRandomFileListCache, purgePublicFileListCache } from "../utils/purgeCache";
import { addFileToIndex } from "../utils/indexManager.js";
import { getDatabase } from '../utils/databaseAdapter.js';
@@ -81,6 +81,98 @@ export function isExtValid(fileExt) {
].includes(fileExt);
}
/**
* 从图片文件头部提取尺寸信息
* 支持 JPEG, PNG, GIF, WebP, BMP 格式
* 优先通过文件头魔数检测格式,不依赖 MIME 类型
* @param {ArrayBuffer} buffer - 文件的 ArrayBuffer
* @param {string} fileType - 文件 MIME 类型(仅作参考)
* @returns {Object|null} { width, height } 或 null
*/
export function getImageDimensions(buffer, fileType) {
try {
const view = new DataView(buffer);
const uint8 = new Uint8Array(buffer);
// 通过文件头魔数检测格式(不依赖 MIME 类型)
// PNG 签名: 89 50 4E 47
if (uint8[0] === 0x89 && uint8[1] === 0x50 && uint8[2] === 0x4E && uint8[3] === 0x47) {
const width = view.getUint32(16, false);
const height = view.getUint32(20, false);
return { width, height };
}
// JPEG 签名: FF D8 FF
if (uint8[0] === 0xFF && uint8[1] === 0xD8 && uint8[2] === 0xFF) {
let offset = 2;
while (offset < buffer.byteLength - 9) {
if (uint8[offset] !== 0xFF) break;
const marker = uint8[offset + 1];
// SOF0, SOF1, SOF2 标记包含尺寸信息
if (marker >= 0xC0 && marker <= 0xC3 && marker !== 0xC4) {
const height = view.getUint16(offset + 5, false);
const width = view.getUint16(offset + 7, false);
return { width, height };
}
const length = view.getUint16(offset + 2, false);
offset += 2 + length;
}
return null;
}
// GIF 签名: 47 49 46 (GIF)
if (uint8[0] === 0x47 && uint8[1] === 0x49 && uint8[2] === 0x46) {
const width = view.getUint16(6, true); // little-endian
const height = view.getUint16(8, true);
return { width, height };
}
// WebP 签名: RIFF....WEBP
if (uint8[0] === 0x52 && uint8[1] === 0x49 && uint8[2] === 0x46 && uint8[3] === 0x46 &&
uint8[8] === 0x57 && uint8[9] === 0x45 && uint8[10] === 0x42 && uint8[11] === 0x50) {
// VP8 (lossy): VP8 + 空格
if (uint8[12] === 0x56 && uint8[13] === 0x50 && uint8[14] === 0x38 && uint8[15] === 0x20) {
if (buffer.byteLength >= 30) {
const width = (view.getUint16(26, true) & 0x3FFF);
const height = (view.getUint16(28, true) & 0x3FFF);
return { width, height };
}
}
// VP8L (lossless): VP8L
if (uint8[12] === 0x56 && uint8[13] === 0x50 && uint8[14] === 0x38 && uint8[15] === 0x4C) {
if (buffer.byteLength >= 25) {
const bits = view.getUint32(21, true);
const width = (bits & 0x3FFF) + 1;
const height = ((bits >> 14) & 0x3FFF) + 1;
return { width, height };
}
}
// VP8X (extended): VP8X
if (uint8[12] === 0x56 && uint8[13] === 0x50 && uint8[14] === 0x38 && uint8[15] === 0x58) {
if (buffer.byteLength >= 30) {
const width = (uint8[24] | (uint8[25] << 8) | (uint8[26] << 16)) + 1;
const height = (uint8[27] | (uint8[28] << 8) | (uint8[29] << 16)) + 1;
return { width, height };
}
}
return null;
}
// BMP 签名: 42 4D (BM)
if (uint8[0] === 0x42 && uint8[1] === 0x4D) {
const width = view.getInt32(18, true);
const height = Math.abs(view.getInt32(22, true)); // height 可能为负数
return { width, height };
}
return null;
} catch (error) {
console.error('Error extracting image dimensions:', error);
return null;
}
}
// 图像审查
export async function moderateContent(env, url) {
const securityConfig = await fetchSecurityConfig(env);
@@ -168,18 +260,9 @@ export async function purgeCDNCache(env, cdnUrl, url, normalizedFolder) {
console.error('Failed to clear CDN cache:', error);
}
// 清除api/randomFileList API缓存
try {
const cache = caches.default;
// await cache.delete(`${url.origin}/api/randomFileList`); delete有bug通过写入一个max-age=0的response来清除缓存
const nullResponse = new Response(null, {
headers: { 'Cache-Control': 'max-age=0' },
});
await cache.put(`${url.origin}/api/randomFileList?dir=${normalizedFolder}`, nullResponse);
} catch (error) {
console.error('Failed to clear cache:', error);
}
// 清除 api/randomFileList API缓存
await purgeRandomFileListCache(url.origin, normalizedFolder);
await purgePublicFileListCache(url.origin, normalizedFolder);
}
// 结束上传:清除缓存,维护索引

View File

@@ -19,4 +19,38 @@ export async function purgeCFCache(env, cdnUrl) {
body: `{"files":["${ cdnUrl }"]}`
};
await fetch(`https://api.cloudflare.com/client/v4/zones/${ cfZoneId }/purge_cache`, options);
}
export async function purgeRandomFileListCache(origin, ...dirs) {
try {
const cache = caches.default;
// cache.delete有bug通过写入一个max-age=0的response来清除缓存
const nullResponse = new Response(null, {
headers: { 'Cache-Control': 'max-age=0' },
});
for (const dir of dirs) {
await cache.put(`${origin}/api/randomFileList?dir=${dir}`, nullResponse);
}
} catch (error) {
console.error('Failed to clear randomFileList cache:', error);
}
}
export async function purgePublicFileListCache(origin, ...dirs) {
try {
const cache = caches.default;
// cache.delete有bug通过写入一个max-age=0的response来清除缓存
const nullResponse = new Response(null, {
headers: { 'Cache-Control': 'max-age=0' },
});
for (const dir of dirs) {
// 清除递归和非递归两种缓存
await cache.put(`${origin}/api/publicFileList?dir=${dir}&recursive=false`, nullResponse);
await cache.put(`${origin}/api/publicFileList?dir=${dir}&recursive=true`, nullResponse);
}
} catch (error) {
console.error('Failed to clear publicFileList cache:', error);
}
}

View File

@@ -1 +1 @@
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/logo.png"><link rel="apple-touch-icon" href="/logo.png"><link rel="mask-icon" href="/logo.png" color="#f4b400"><meta name="description" content="Sanyue ImgHub - A modern file hosting platform"><meta name="keywords" content="Sanyue, ImgHub, file hosting, image hosting, cloud storage"><meta name="author" content="SanyueQi"><title>Sanyue ImgHub</title><script defer="defer" src="/js/chunk-vendors.92518219.js"></script><script defer="defer" src="/js/app.19ef69ac.js"></script><link href="/css/chunk-vendors.4363ed49.css" rel="stylesheet"><link href="/css/app.3e1508bd.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but sanyue_imghub doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/logo.png"><link rel="apple-touch-icon" href="/logo.png"><link rel="mask-icon" href="/logo.png" color="#f4b400"><meta name="description" content="Sanyue ImgHub - A modern file hosting platform"><meta name="keywords" content="Sanyue, ImgHub, file hosting, image hosting, cloud storage"><meta name="author" content="SanyueQi"><title>Sanyue ImgHub</title><script defer="defer" src="/js/chunk-vendors.92518219.js"></script><script defer="defer" src="/js/app.1611e1ad.js"></script><link href="/css/chunk-vendors.4363ed49.css" rel="stylesheet"><link href="/css/app.3e1508bd.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but sanyue_imghub doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

Binary file not shown.

2
js/157.17e1accc.js Normal file

File diff suppressed because one or more lines are too long

BIN
js/157.17e1accc.js.gz Normal file

Binary file not shown.

1
js/157.17e1accc.js.map Normal file

File diff suppressed because one or more lines are too long

BIN
js/157.17e1accc.js.map.gz Normal file

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
js/690.ff8af593.js.map.gz Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
js/app.1611e1ad.js.map.gz Normal file

Binary file not shown.

Binary file not shown.