mirror of
https://github.com/System-End/cdn.git
synced 2026-04-19 16:18:17 +00:00
Added the Fun lines and error images! Also added partitial error support!
This commit is contained in:
parent
16c690583d
commit
0655a0d8a7
5 changed files with 244 additions and 28 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -3,4 +3,5 @@
|
|||
/.idea/
|
||||
/.env
|
||||
/bun.lockb
|
||||
/package-lock.json
|
||||
/package-lock.json
|
||||
/.history
|
||||
137
src/config/messages.js
Normal file
137
src/config/messages.js
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
const messages = {
|
||||
success: {
|
||||
singleFile: "Hey <@{userId}>, here's your link:",
|
||||
multipleFiles: "Hey <@{userId}>, here are your links:",
|
||||
alternateSuccess: [
|
||||
"thanks!",
|
||||
"thanks, i'm gonna sell these to adfly!",
|
||||
"tysm!",
|
||||
"file away!"
|
||||
]
|
||||
},
|
||||
fileTypes: {
|
||||
gif: [
|
||||
"_gif_ that file to me and i'll upload it",
|
||||
"_gif_ me all all your files!"
|
||||
],
|
||||
heic: [
|
||||
"What the heic???"
|
||||
],
|
||||
mov: [
|
||||
"I'll _mov_ that to a permanent link for you"
|
||||
],
|
||||
html: [
|
||||
"Oh, launching a new website?",
|
||||
"uwu, what's this site?",
|
||||
"WooOOAAah hey! Are you serving a site?",
|
||||
"h-t-m-ello :wave:"
|
||||
],
|
||||
rar: [
|
||||
".rawr xD",
|
||||
"i also go \"rar\" sometimes!"
|
||||
]
|
||||
},
|
||||
errors: {
|
||||
tooBig: {
|
||||
messages: [
|
||||
"File too big!",
|
||||
"That's a chonky file!",
|
||||
"_orpheus struggles to lift the massive file_",
|
||||
"Sorry, that file's too thicc for me to handle!"
|
||||
],
|
||||
images: [
|
||||
"https://cloud-3tq9t10za-hack-club-bot.vercel.app/2too_big_4.png",
|
||||
"https://cloud-3tq9t10za-hack-club-bot.vercel.app/3too_big_2.png",
|
||||
"https://cloud-3tq9t10za-hack-club-bot.vercel.app/4too_big_1.png",
|
||||
"https://cloud-3tq9t10za-hack-club-bot.vercel.app/6too_big_5.png",
|
||||
"https://cloud-3tq9t10za-hack-club-bot.vercel.app/7too_big_3.png"
|
||||
]
|
||||
},
|
||||
generic: {
|
||||
messages: [
|
||||
"_orpheus sneezes and drops the files on the ground before blowing her nose on a blank jpeg._",
|
||||
"_orpheus trips and your files slip out of her hands and into an inconveniently placed sewer grate._",
|
||||
"_orpheus accidentally slips the files into a folder in her briefcase labeled \"homework\". she starts sweating profusely._"
|
||||
],
|
||||
images: [
|
||||
"https://cloud-3tq9t10za-hack-club-bot.vercel.app/0generic_3.png",
|
||||
"https://cloud-3tq9t10za-hack-club-bot.vercel.app/1generic_2.png",
|
||||
"https://cloud-3tq9t10za-hack-club-bot.vercel.app/5generic_1.png"
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getRandomItem(array) {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
|
||||
function getFileTypeMessage(fileExtension) {
|
||||
const ext = fileExtension.toLowerCase();
|
||||
return messages.fileTypes[ext] ? getRandomItem(messages.fileTypes[ext]) : null;
|
||||
}
|
||||
|
||||
function formatErrorMessage(failedFiles, isSizeError = false) {
|
||||
const errorType = isSizeError ? messages.errors.tooBig : messages.errors.generic;
|
||||
const errorMessage = getRandomItem(errorType.messages);
|
||||
const errorImage = getRandomItem(errorType.images);
|
||||
|
||||
return [
|
||||
errorMessage,
|
||||
`Failed files: ${failedFiles.join(', ')}`,
|
||||
'',
|
||||
`<${errorImage}|image>`
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function formatSuccessMessage(userId, files, failedFiles = [], sizeFailedFiles = []) {
|
||||
const messageLines = [];
|
||||
|
||||
const baseMessage = files.length === 1 ?
|
||||
messages.success.singleFile :
|
||||
messages.success.multipleFiles;
|
||||
messageLines.push(baseMessage.replace('{userId}', userId), '');
|
||||
|
||||
const fileGroups = new Map();
|
||||
files.forEach(file => {
|
||||
const ext = file.originalName.split('.').pop();
|
||||
const typeMessage = getFileTypeMessage(ext);
|
||||
const key = typeMessage || 'noType';
|
||||
|
||||
if (!fileGroups.has(key)) {
|
||||
fileGroups.set(key, []);
|
||||
}
|
||||
fileGroups.get(key).push(file);
|
||||
});
|
||||
|
||||
fileGroups.forEach((groupFiles, typeMessage) => {
|
||||
if (typeMessage !== 'noType') {
|
||||
messageLines.push('', typeMessage);
|
||||
}
|
||||
|
||||
groupFiles.forEach(file => {
|
||||
messageLines.push(`• ${file.originalName}: ${file.url}`);
|
||||
});
|
||||
});
|
||||
|
||||
if (sizeFailedFiles.length > 0) {
|
||||
messageLines.push(formatErrorMessage(sizeFailedFiles, true));
|
||||
}
|
||||
if (failedFiles.length > 0) {
|
||||
messageLines.push(formatErrorMessage(failedFiles, false));
|
||||
}
|
||||
|
||||
if (files.length > 0) {
|
||||
messageLines.push('', `_${getRandomItem(messages.success.alternateSuccess)}_`);
|
||||
}
|
||||
|
||||
return messageLines.join('\n');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
messages,
|
||||
getFileTypeMessage,
|
||||
formatSuccessMessage,
|
||||
formatErrorMessage,
|
||||
getRandomItem
|
||||
};
|
||||
|
|
@ -3,8 +3,15 @@ const crypto = require('crypto');
|
|||
const logger = require('./config/logger');
|
||||
const storage = require('./storage');
|
||||
const {generateFileUrl} = require('./utils');
|
||||
const path = require('path');
|
||||
const {
|
||||
messages,
|
||||
formatSuccessMessage,
|
||||
formatErrorMessage,
|
||||
getFileTypeMessage
|
||||
} = require('./config/messages');
|
||||
|
||||
const MAX_FILE_SIZE = 2 * 1024 * 1024 * 1024; // 2GB in bytes
|
||||
const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB in bytes
|
||||
const CONCURRENT_UPLOADS = 3; // Max concurrent uploads (messages)
|
||||
|
||||
const processedMessages = new Map();
|
||||
|
|
@ -43,16 +50,23 @@ function generateUniqueFileName(fileName) {
|
|||
async function processFiles(fileMessage, client) {
|
||||
const uploadedFiles = [];
|
||||
const failedFiles = [];
|
||||
const sizeFailedFiles = [];
|
||||
const fileTypeResponses = new Set();
|
||||
|
||||
logger.info(`Processing ${fileMessage.files?.length || 0} files`);
|
||||
|
||||
for (const file of fileMessage.files || []) {
|
||||
try {
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
failedFiles.push(file.name);
|
||||
sizeFailedFiles.push(file.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get file extension message if applicable
|
||||
const ext = path.extname(file.name).slice(1);
|
||||
const typeMessage = getFileTypeMessage(ext);
|
||||
if (typeMessage) fileTypeResponses.add(typeMessage);
|
||||
|
||||
const response = await fetch(file.url_private, {
|
||||
headers: {Authorization: `Bearer ${process.env.SLACK_BOT_TOKEN}`}
|
||||
});
|
||||
|
|
@ -71,6 +85,7 @@ async function processFiles(fileMessage, client) {
|
|||
|
||||
uploadedFiles.push({
|
||||
name: uniqueFileName,
|
||||
originalName: file.name,
|
||||
url: generateFileUrl(userDir, uniqueFileName),
|
||||
contentType: file.mimetype
|
||||
});
|
||||
|
|
@ -81,8 +96,12 @@ async function processFiles(fileMessage, client) {
|
|||
}
|
||||
}
|
||||
|
||||
logger.info(`Completed: ${uploadedFiles.length} ok, ${failedFiles.length} failed`);
|
||||
return {uploadedFiles, failedFiles};
|
||||
return {
|
||||
uploadedFiles,
|
||||
failedFiles,
|
||||
sizeFailedFiles,
|
||||
isSizeError: sizeFailedFiles.length > 0
|
||||
};
|
||||
}
|
||||
|
||||
// Slack interaction
|
||||
|
|
@ -98,15 +117,26 @@ async function addProcessingReaction(client, event, fileMessage) {
|
|||
}
|
||||
}
|
||||
|
||||
async function updateReactions(client, event, fileMessage, success) {
|
||||
async function updateReactions(client, event, fileMessage, totalFiles, failedCount) {
|
||||
try {
|
||||
await client.reactions.remove({
|
||||
name: 'beachball',
|
||||
timestamp: fileMessage.ts,
|
||||
channel: event.channel_id
|
||||
});
|
||||
|
||||
// Choose reaction based on how many files failed or well succeded
|
||||
let reactionName;
|
||||
if (failedCount === totalFiles) {
|
||||
reactionName = 'x'; // All files failed
|
||||
} else if (failedCount > 0) {
|
||||
reactionName = 'warning'; // Some files failed
|
||||
} else {
|
||||
reactionName = 'white_check_mark'; // All files succeeded
|
||||
}
|
||||
|
||||
await client.reactions.add({
|
||||
name: success ? 'white_check_mark' : 'x',
|
||||
name: reactionName,
|
||||
timestamp: fileMessage.ts,
|
||||
channel: event.channel_id
|
||||
});
|
||||
|
|
@ -133,7 +163,7 @@ async function findFileMessage(event, client) {
|
|||
throw new Error('No share info found for this channel');
|
||||
}
|
||||
|
||||
// Get the exact message using the ts from share info
|
||||
// Get the EXACT message using the ts from share info (channelShare)
|
||||
const messageTs = channelShare[0].ts;
|
||||
|
||||
const messageInfo = await client.conversations.history({
|
||||
|
|
@ -154,21 +184,51 @@ async function findFileMessage(event, client) {
|
|||
}
|
||||
}
|
||||
|
||||
async function sendResultsMessage(client, channelId, fileMessage, uploadedFiles, failedFiles) {
|
||||
let message = `Hey <@${fileMessage.user}>, `;
|
||||
if (uploadedFiles.length > 0) {
|
||||
message += `here ${uploadedFiles.length === 1 ? 'is your link' : 'are your links'}:\n`;
|
||||
message += uploadedFiles.map(f => `• ${f.name}: ${f.url}`).join('\n');
|
||||
}
|
||||
if (failedFiles.length > 0) {
|
||||
message += `\n\nFailed to process: ${failedFiles.join(', ')}`;
|
||||
}
|
||||
async function sendResultsMessage(client, channelId, fileMessage, uploadedFiles, failedFiles, sizeFailedFiles) {
|
||||
try {
|
||||
let message;
|
||||
if (uploadedFiles.length === 0 && (failedFiles.length > 0 || sizeFailedFiles.length > 0)) {
|
||||
// All files failed - use appropriate error type
|
||||
message = formatErrorMessage(
|
||||
[...failedFiles, ...sizeFailedFiles],
|
||||
sizeFailedFiles.length > 0 && failedFiles.length === 0 // Only use size error if all failures are size-related (i hope this is how it makes most sense)
|
||||
);
|
||||
} else {
|
||||
// Mixed success/failure or all success
|
||||
message = formatSuccessMessage(
|
||||
fileMessage.user,
|
||||
uploadedFiles,
|
||||
failedFiles,
|
||||
sizeFailedFiles
|
||||
);
|
||||
}
|
||||
|
||||
await client.chat.postMessage({
|
||||
channel: channelId,
|
||||
thread_ts: fileMessage.ts,
|
||||
text: message
|
||||
});
|
||||
const lines = message.split('\n');
|
||||
const attachments = [];
|
||||
let textBuffer = '';
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.match(/^<.*\|image>$/)) {
|
||||
const imageUrl = line.replace(/^<|>$/g, '').replace('|image', '');
|
||||
attachments.push({
|
||||
image_url: imageUrl,
|
||||
fallback: 'Error image'
|
||||
});
|
||||
} else {
|
||||
textBuffer += line + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
await client.chat.postMessage({
|
||||
channel: channelId,
|
||||
thread_ts: fileMessage.ts,
|
||||
text: textBuffer.trim(),
|
||||
attachments: attachments.length > 0 ? attachments : undefined
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to send results message:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleError(client, channelId, fileMessage, reactionAdded) {
|
||||
|
|
@ -210,9 +270,27 @@ async function handleFileUpload(event, client) {
|
|||
await addProcessingReaction(client, event, fileMessage);
|
||||
reactionAdded = true;
|
||||
|
||||
const {uploadedFiles, failedFiles} = await processFiles(fileMessage, client);
|
||||
await sendResultsMessage(client, event.channel_id, fileMessage, uploadedFiles, failedFiles);
|
||||
await updateReactions(client, event, fileMessage, failedFiles.length === 0);
|
||||
const {uploadedFiles, failedFiles, sizeFailedFiles} = await processFiles(fileMessage, client);
|
||||
|
||||
const totalFiles = uploadedFiles.length + failedFiles.length + sizeFailedFiles.length;
|
||||
const failedCount = failedFiles.length + sizeFailedFiles.length;
|
||||
|
||||
await sendResultsMessage(
|
||||
client,
|
||||
event.channel_id,
|
||||
fileMessage,
|
||||
uploadedFiles,
|
||||
failedFiles,
|
||||
sizeFailedFiles
|
||||
);
|
||||
|
||||
await updateReactions(
|
||||
client,
|
||||
event,
|
||||
fileMessage,
|
||||
totalFiles,
|
||||
failedCount
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Upload failed: ${error.message}`);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ function sanitizeFileName(fileName) {
|
|||
return sanitizedFileName;
|
||||
}
|
||||
|
||||
// Generate a unique, non-guessable file name
|
||||
// Generate a unique file name
|
||||
function generateUniqueFileName(fileName) {
|
||||
const sanitizedFileName = sanitizeFileName(fileName);
|
||||
const uniqueFileName = `${Date.now()}-${crypto.randomBytes(16).toString('hex')}-${sanitizedFileName}`;
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ const handleUpload = async (file) => {
|
|||
try {
|
||||
const buffer = fs.readFileSync(file.path);
|
||||
const fileName = path.basename(file.originalname);
|
||||
// Add content type detection for S3
|
||||
// content type detection for S3
|
||||
const contentType = file.mimetype || 'application/octet-stream';
|
||||
const uniqueFileName = `${Date.now()}-${fileName}`;
|
||||
|
||||
// Upload to S3 storage with content type
|
||||
// Upload to S3
|
||||
logger.debug(`Uploading: ${uniqueFileName}`);
|
||||
const uploaded = await uploadToStorage('s/v3', uniqueFileName, buffer, contentType);
|
||||
if (!uploaded) throw new Error('Storage upload failed');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue