mirror of
https://github.com/System-End/cdn.git
synced 2026-04-19 20:55:10 +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/
|
/.idea/
|
||||||
/.env
|
/.env
|
||||||
/bun.lockb
|
/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 logger = require('./config/logger');
|
||||||
const storage = require('./storage');
|
const storage = require('./storage');
|
||||||
const {generateFileUrl} = require('./utils');
|
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 CONCURRENT_UPLOADS = 3; // Max concurrent uploads (messages)
|
||||||
|
|
||||||
const processedMessages = new Map();
|
const processedMessages = new Map();
|
||||||
|
|
@ -43,16 +50,23 @@ function generateUniqueFileName(fileName) {
|
||||||
async function processFiles(fileMessage, client) {
|
async function processFiles(fileMessage, client) {
|
||||||
const uploadedFiles = [];
|
const uploadedFiles = [];
|
||||||
const failedFiles = [];
|
const failedFiles = [];
|
||||||
|
const sizeFailedFiles = [];
|
||||||
|
const fileTypeResponses = new Set();
|
||||||
|
|
||||||
logger.info(`Processing ${fileMessage.files?.length || 0} files`);
|
logger.info(`Processing ${fileMessage.files?.length || 0} files`);
|
||||||
|
|
||||||
for (const file of fileMessage.files || []) {
|
for (const file of fileMessage.files || []) {
|
||||||
try {
|
try {
|
||||||
if (file.size > MAX_FILE_SIZE) {
|
if (file.size > MAX_FILE_SIZE) {
|
||||||
failedFiles.push(file.name);
|
sizeFailedFiles.push(file.name);
|
||||||
continue;
|
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, {
|
const response = await fetch(file.url_private, {
|
||||||
headers: {Authorization: `Bearer ${process.env.SLACK_BOT_TOKEN}`}
|
headers: {Authorization: `Bearer ${process.env.SLACK_BOT_TOKEN}`}
|
||||||
});
|
});
|
||||||
|
|
@ -71,6 +85,7 @@ async function processFiles(fileMessage, client) {
|
||||||
|
|
||||||
uploadedFiles.push({
|
uploadedFiles.push({
|
||||||
name: uniqueFileName,
|
name: uniqueFileName,
|
||||||
|
originalName: file.name,
|
||||||
url: generateFileUrl(userDir, uniqueFileName),
|
url: generateFileUrl(userDir, uniqueFileName),
|
||||||
contentType: file.mimetype
|
contentType: file.mimetype
|
||||||
});
|
});
|
||||||
|
|
@ -81,8 +96,12 @@ async function processFiles(fileMessage, client) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Completed: ${uploadedFiles.length} ok, ${failedFiles.length} failed`);
|
return {
|
||||||
return {uploadedFiles, failedFiles};
|
uploadedFiles,
|
||||||
|
failedFiles,
|
||||||
|
sizeFailedFiles,
|
||||||
|
isSizeError: sizeFailedFiles.length > 0
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slack interaction
|
// 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 {
|
try {
|
||||||
await client.reactions.remove({
|
await client.reactions.remove({
|
||||||
name: 'beachball',
|
name: 'beachball',
|
||||||
timestamp: fileMessage.ts,
|
timestamp: fileMessage.ts,
|
||||||
channel: event.channel_id
|
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({
|
await client.reactions.add({
|
||||||
name: success ? 'white_check_mark' : 'x',
|
name: reactionName,
|
||||||
timestamp: fileMessage.ts,
|
timestamp: fileMessage.ts,
|
||||||
channel: event.channel_id
|
channel: event.channel_id
|
||||||
});
|
});
|
||||||
|
|
@ -133,7 +163,7 @@ async function findFileMessage(event, client) {
|
||||||
throw new Error('No share info found for this channel');
|
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 messageTs = channelShare[0].ts;
|
||||||
|
|
||||||
const messageInfo = await client.conversations.history({
|
const messageInfo = await client.conversations.history({
|
||||||
|
|
@ -154,21 +184,51 @@ async function findFileMessage(event, client) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendResultsMessage(client, channelId, fileMessage, uploadedFiles, failedFiles) {
|
async function sendResultsMessage(client, channelId, fileMessage, uploadedFiles, failedFiles, sizeFailedFiles) {
|
||||||
let message = `Hey <@${fileMessage.user}>, `;
|
try {
|
||||||
if (uploadedFiles.length > 0) {
|
let message;
|
||||||
message += `here ${uploadedFiles.length === 1 ? 'is your link' : 'are your links'}:\n`;
|
if (uploadedFiles.length === 0 && (failedFiles.length > 0 || sizeFailedFiles.length > 0)) {
|
||||||
message += uploadedFiles.map(f => `• ${f.name}: ${f.url}`).join('\n');
|
// All files failed - use appropriate error type
|
||||||
}
|
message = formatErrorMessage(
|
||||||
if (failedFiles.length > 0) {
|
[...failedFiles, ...sizeFailedFiles],
|
||||||
message += `\n\nFailed to process: ${failedFiles.join(', ')}`;
|
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({
|
const lines = message.split('\n');
|
||||||
channel: channelId,
|
const attachments = [];
|
||||||
thread_ts: fileMessage.ts,
|
let textBuffer = '';
|
||||||
text: message
|
|
||||||
});
|
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) {
|
async function handleError(client, channelId, fileMessage, reactionAdded) {
|
||||||
|
|
@ -210,9 +270,27 @@ async function handleFileUpload(event, client) {
|
||||||
await addProcessingReaction(client, event, fileMessage);
|
await addProcessingReaction(client, event, fileMessage);
|
||||||
reactionAdded = true;
|
reactionAdded = true;
|
||||||
|
|
||||||
const {uploadedFiles, failedFiles} = await processFiles(fileMessage, client);
|
const {uploadedFiles, failedFiles, sizeFailedFiles} = await processFiles(fileMessage, client);
|
||||||
await sendResultsMessage(client, event.channel_id, fileMessage, uploadedFiles, failedFiles);
|
|
||||||
await updateReactions(client, event, fileMessage, failedFiles.length === 0);
|
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) {
|
} catch (error) {
|
||||||
logger.error(`Upload failed: ${error.message}`);
|
logger.error(`Upload failed: ${error.message}`);
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ function sanitizeFileName(fileName) {
|
||||||
return sanitizedFileName;
|
return sanitizedFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a unique, non-guessable file name
|
// Generate a unique file name
|
||||||
function generateUniqueFileName(fileName) {
|
function generateUniqueFileName(fileName) {
|
||||||
const sanitizedFileName = sanitizeFileName(fileName);
|
const sanitizedFileName = sanitizeFileName(fileName);
|
||||||
const uniqueFileName = `${Date.now()}-${crypto.randomBytes(16).toString('hex')}-${sanitizedFileName}`;
|
const uniqueFileName = `${Date.now()}-${crypto.randomBytes(16).toString('hex')}-${sanitizedFileName}`;
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,11 @@ const handleUpload = async (file) => {
|
||||||
try {
|
try {
|
||||||
const buffer = fs.readFileSync(file.path);
|
const buffer = fs.readFileSync(file.path);
|
||||||
const fileName = path.basename(file.originalname);
|
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 contentType = file.mimetype || 'application/octet-stream';
|
||||||
const uniqueFileName = `${Date.now()}-${fileName}`;
|
const uniqueFileName = `${Date.now()}-${fileName}`;
|
||||||
|
|
||||||
// Upload to S3 storage with content type
|
// Upload to S3
|
||||||
logger.debug(`Uploading: ${uniqueFileName}`);
|
logger.debug(`Uploading: ${uniqueFileName}`);
|
||||||
const uploaded = await uploadToStorage('s/v3', uniqueFileName, buffer, contentType);
|
const uploaded = await uploadToStorage('s/v3', uniqueFileName, buffer, contentType);
|
||||||
if (!uploaded) throw new Error('Storage upload failed');
|
if (!uploaded) throw new Error('Storage upload failed');
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue