gfsdgsd
This commit is contained in:
+147
-4
@@ -175,7 +175,13 @@ export const handleUpload = async (req, res, self) => {
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
let file = (typeof parts.file === 'object' && parts.file !== null && parts.file.data) ? parts.file : null;
|
||||
let rawFiles = [];
|
||||
if (Array.isArray(parts.files)) rawFiles = parts.files;
|
||||
else if (Array.isArray(parts.file)) rawFiles = parts.file;
|
||||
else if (parts.files && typeof parts.files === 'object' && parts.files.data) rawFiles = [parts.files];
|
||||
else if (parts.file && typeof parts.file === 'object' && parts.file.data) rawFiles = [parts.file];
|
||||
|
||||
let file = rawFiles[0] || null;
|
||||
let inputUrl = (typeof parts.url === 'string' && parts.url.trim()) ? parts.url.trim() : null;
|
||||
|
||||
if (!inputUrl && typeof parts.file === 'string' && /^https?:\/\//i.test(parts.file.trim())) {
|
||||
@@ -184,6 +190,7 @@ export const handleUpload = async (req, res, self) => {
|
||||
|
||||
if (inputUrl) {
|
||||
file = null;
|
||||
rawFiles = [];
|
||||
try {
|
||||
const parsed = new URL(inputUrl);
|
||||
if (parsed.searchParams.has('igsh')) {
|
||||
@@ -200,7 +207,14 @@ export const handleUpload = async (req, res, self) => {
|
||||
const title = rawTitle.length > 0 ? rawTitle.substring(0, 500) : null;
|
||||
|
||||
const is_oc = (parts.is_oc === true || parts.is_oc === 'true' || parts.is_oc === '1');
|
||||
const is_shitpost = (parts.is_shitpost === true || parts.is_shitpost === 'true' || parts.is_shitpost === '1') || cfg.websrv.shitpost_mode === true;
|
||||
const isAlbumRequested = (parts.is_album === true || parts.is_album === 'true' || parts.is_album === '1') || (rawFiles.length > 1 && !parts.is_shitpost);
|
||||
const is_album = isAlbumRequested && rawFiles.length > 1;
|
||||
const is_shitpost = !is_album && ((parts.is_shitpost === true || parts.is_shitpost === 'true' || parts.is_shitpost === '1') || cfg.websrv.shitpost_mode === true);
|
||||
|
||||
const maxAlbumItems = cfg.websrv?.max_album_items || cfg.websrv?.max_album_images || 100;
|
||||
if (is_album && rawFiles.length > maxAlbumItems) {
|
||||
return sendJson(res, { success: false, msg: `Album exceeds maximum limit of ${maxAlbumItems} items` }, 400);
|
||||
}
|
||||
|
||||
if (!file && !inputUrl) {
|
||||
return sendJson(res, { success: false, msg: 'No file or URL provided' }, 400);
|
||||
@@ -302,6 +316,15 @@ export const handleUpload = async (req, res, self) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (is_album) {
|
||||
for (let i = 0; i < rawFiles.length; i++) {
|
||||
const f = rawFiles[i];
|
||||
if (!f || !f.data || f.data.length === 0) {
|
||||
return sendJson(res, { success: false, msg: `Album item #${i + 1} is empty` }, 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let manualApproval = getManualApproval();
|
||||
const trustedThreshold = getTrustedUploads();
|
||||
if (trustedThreshold > 0 && !req.session.admin && !req.session.is_moderator) {
|
||||
@@ -776,12 +799,130 @@ export const handleUpload = async (req, res, self) => {
|
||||
visibility: targetVisibility,
|
||||
slug: itemSlug,
|
||||
expires_at: targetExpiresAt,
|
||||
uploader_ip: auditIp
|
||||
}, 'src', 'dest', 'mime', 'size', 'checksum', 'phash', 'username', 'userchannel', 'usernetwork', 'stamp', 'active', 'is_oc', 'original_filename', 'title', 'width', 'height', 'visibility', 'slug', 'expires_at', 'uploader_ip')}
|
||||
uploader_ip: auditIp,
|
||||
is_album: !!is_album,
|
||||
album_count: is_album ? rawFiles.length : 0
|
||||
}, 'src', 'dest', 'mime', 'size', 'checksum', 'phash', 'username', 'userchannel', 'usernetwork', 'stamp', 'active', 'is_oc', 'original_filename', 'title', 'width', 'height', 'visibility', 'slug', 'expires_at', 'uploader_ip', 'is_album', 'album_count')}
|
||||
`;
|
||||
|
||||
const itemid = await queue.getItemID(filename);
|
||||
|
||||
if (is_album) {
|
||||
// 1. Insert primary/cover image (file 0) as order_index 0
|
||||
const coverSubSlug = lib.generateSlug(11);
|
||||
await db`
|
||||
INSERT INTO album_items ${db({
|
||||
item_id: itemid,
|
||||
dest: filename,
|
||||
mime: actualMime,
|
||||
size: size,
|
||||
checksum: insertChecksum,
|
||||
phash: phash,
|
||||
width: itemWidth,
|
||||
height: itemHeight,
|
||||
order_index: 0,
|
||||
slug: coverSubSlug
|
||||
}, 'item_id', 'dest', 'mime', 'size', 'checksum', 'phash', 'width', 'height', 'order_index', 'slug')}
|
||||
`;
|
||||
|
||||
// 2. Process and insert remaining album images (1 .. rawFiles.length - 1)
|
||||
for (let i = 1; i < rawFiles.length; i++) {
|
||||
const subFile = rawFiles[i];
|
||||
if (!subFile || !subFile.data) continue;
|
||||
const subUuid = await queue.genuuid();
|
||||
const subTmpPath = path.join(cfg.paths.tmp, `${subUuid}.tmp`);
|
||||
await fs.writeFile(subTmpPath, subFile.data);
|
||||
|
||||
let subMime = (await queue.spawn('file', ['--mime-type', '-b', subTmpPath])).stdout.trim();
|
||||
const allowedMimesList = Object.keys(cfg.mimes || {});
|
||||
if (!allowedMimesList.includes(subMime) && subMime !== 'application/x-shockwave-flash' && subMime !== 'application/vnd.adobe.flash.movie') {
|
||||
const extFromMime = cfg.mimes?.[subFile.contentType] ? subFile.contentType : null;
|
||||
if (extFromMime) subMime = extFromMime;
|
||||
else {
|
||||
await fs.unlink(subTmpPath).catch(() => {});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const [subChecksum, subPhash, subDims] = await Promise.all([
|
||||
queue.spawn('sha256sum', [subTmpPath]).then(r => r.stdout.trim().split(' ')[0]),
|
||||
queue.generatePHash(subTmpPath).catch(() => null),
|
||||
(async () => {
|
||||
try {
|
||||
if (subMime.startsWith('image/')) {
|
||||
const { stdout: magickOut } = await queue.spawn('magick', [
|
||||
'identify', '-format', '%wx%h\n', subTmpPath + '[0]'
|
||||
], { quiet: true, ignoreExitCode: true });
|
||||
const line = magickOut.trim().split('\n')[0];
|
||||
const match = line.match(/^(\d+)x(\d+)$/);
|
||||
if (match) return { width: parseInt(match[1], 10), height: parseInt(match[2], 10) };
|
||||
} else if (subMime.startsWith('video/')) {
|
||||
const { stdout: probeOut } = await queue.spawn('ffprobe', [
|
||||
'-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=width,height', '-of', 'csv=s=x:p=0', subTmpPath
|
||||
], { quiet: true, ignoreExitCode: true });
|
||||
const line = probeOut.trim().split('\n')[0];
|
||||
const match = line.match(/^(\d+)x(\d+)$/);
|
||||
if (match) return { width: parseInt(match[1], 10), height: parseInt(match[2], 10) };
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
})()
|
||||
]);
|
||||
|
||||
const subExt = cfg.mimes[subMime] || 'webp';
|
||||
const subFilename = `${subUuid}.${subExt}`;
|
||||
const subDestPath = manualApproval ? path.join(cfg.paths.pending, 'b', subFilename) : path.join(cfg.paths.b, subFilename);
|
||||
|
||||
await fs.copyFile(subTmpPath, subDestPath);
|
||||
await fs.unlink(subTmpPath).catch(() => {});
|
||||
|
||||
const subWidth = subDims?.width ?? null;
|
||||
const subHeight = subDims?.height ?? null;
|
||||
const subSize = subFile.data.length;
|
||||
const subItemSlug = lib.generateSlug(11);
|
||||
|
||||
await db`
|
||||
INSERT INTO album_items ${db({
|
||||
item_id: itemid,
|
||||
dest: subFilename,
|
||||
mime: subMime,
|
||||
size: subSize,
|
||||
checksum: subChecksum,
|
||||
phash: subPhash,
|
||||
width: subWidth,
|
||||
height: subHeight,
|
||||
order_index: i,
|
||||
slug: subItemSlug
|
||||
}, 'item_id', 'dest', 'mime', 'size', 'checksum', 'phash', 'width', 'height', 'order_index', 'slug')}
|
||||
`;
|
||||
|
||||
// Generate thumbnail for album item filmstrip
|
||||
try {
|
||||
const tDir = manualApproval ? path.join(cfg.paths.pending, 't') : cfg.paths.t;
|
||||
const thumbDest = path.join(tDir, `${subUuid}.webp`);
|
||||
if (subMime.startsWith('image/')) {
|
||||
await queue.spawn('magick', [subDestPath + '[0]', '-resize', '256x256^', '-gravity', 'center', '-crop', '256x256+0+0', '+repage', thumbDest]);
|
||||
} else if (subMime.startsWith('video/')) {
|
||||
await queue.spawn('ffmpegthumbnailer', ['-i', subDestPath, '-s', '256', '-o', thumbDest]).catch(async () => {
|
||||
await queue.spawn('ffmpeg', ['-y', '-ss', '00:00:01', '-i', subDestPath, '-vframes', '1', '-vf', 'scale=256:256:force_original_aspect_ratio=increase,crop=256:256', thumbDest]);
|
||||
});
|
||||
} else if (subMime.startsWith('audio/')) {
|
||||
try {
|
||||
const cDir = manualApproval ? path.join(cfg.paths.pending, 'ca') : cfg.paths.ca;
|
||||
const caDest = path.join(cDir, `${subUuid}.webp`);
|
||||
await queue.spawn('ffmpeg', ['-y', '-i', subDestPath, '-an', '-vcodec', 'webp', '-frames:v', '1', caDest], { quiet: true });
|
||||
const caStat = await fs.stat(caDest).catch(() => null);
|
||||
if (caStat && caStat.size > 0) {
|
||||
await queue.spawn('magick', [caDest + '[0]', '-resize', '256x256^', '-gravity', 'center', '-crop', '256x256+0+0', '+repage', thumbDest]);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
} catch (tErr) {
|
||||
console.warn(`[UPLOAD] Failed to generate thumbnail for album item ${subFilename}:`, tErr.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (req.session?.is_anon) {
|
||||
await logAnonActivity(req, {
|
||||
action: 'upload',
|
||||
@@ -1109,6 +1250,8 @@ export const handleUpload = async (req, res, self) => {
|
||||
mime: actualMime,
|
||||
tag_id: effectiveRating ? (effectiveRating === 'sfw' ? 1 : (effectiveRating === 'nsfw' ? 2 : (cfg.nsfl_tag_id || 3))) : 0,
|
||||
is_oc: !!is_oc,
|
||||
is_album: !!is_album,
|
||||
album_count: is_album ? rawFiles.length : 0,
|
||||
display_name: req.session.display_name || null,
|
||||
username: req.session.user
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user