mirror of
https://github.com/System-End/cdn.git
synced 2026-04-19 18:35:12 +00:00
Fully switch to bun / remove slack / update package list
This commit is contained in:
parent
312d34d103
commit
3d86957e9f
11 changed files with 4 additions and 493 deletions
|
|
@ -1,9 +1,3 @@
|
||||||
# Slack
|
|
||||||
SLACK_BOT_TOKEN=xoxb- # From OAuth & Permissions
|
|
||||||
SLACK_SIGNING_SECRET= # From Basic Information
|
|
||||||
SLACK_APP_TOKEN=xapp- # From Basic Information (for Socket Mode)
|
|
||||||
SLACK_CHANNEL_ID=channel-id # Channel where bot operates
|
|
||||||
|
|
||||||
# S3 Config CF in this example
|
# S3 Config CF in this example
|
||||||
AWS_ACCESS_KEY_ID=1234567890abcdef
|
AWS_ACCESS_KEY_ID=1234567890abcdef
|
||||||
AWS_SECRET_ACCESS_KEY=abcdef1234567890
|
AWS_SECRET_ACCESS_KEY=abcdef1234567890
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -2,6 +2,5 @@
|
||||||
/splitfornpm/
|
/splitfornpm/
|
||||||
/.idea/
|
/.idea/
|
||||||
/.env
|
/.env
|
||||||
/bun.lockb
|
|
||||||
/package-lock.json
|
/package-lock.json
|
||||||
/.history
|
/.history
|
||||||
BIN
bun.lockb
Executable file
BIN
bun.lockb
Executable file
Binary file not shown.
29
index.js
29
index.js
|
|
@ -1,25 +1,11 @@
|
||||||
const dotenv = require('dotenv');
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
const logger = require('./src/config/logger');
|
const logger = require('./src/config/logger');
|
||||||
|
|
||||||
logger.info('Starting CDN application 🚀');
|
logger.info('Starting CDN application 🚀');
|
||||||
|
|
||||||
// const {App} = require('@slack/bolt');
|
|
||||||
const fileUpload = require('./src/fileUpload');
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
const apiRoutes = require('./src/api/index.js');
|
const apiRoutes = require('./src/api/index.js');
|
||||||
|
|
||||||
const BOT_START_TIME = Date.now() / 1000;
|
|
||||||
|
|
||||||
// const app = new App({
|
|
||||||
// token: process.env.SLACK_BOT_TOKEN,
|
|
||||||
// signingSecret: process.env.SLACK_SIGNING_SECRET,
|
|
||||||
// socketMode: false,
|
|
||||||
// appToken: process.env.SLACK_APP_TOKEN
|
|
||||||
// });
|
|
||||||
|
|
||||||
// API server
|
// API server
|
||||||
const expressApp = express();
|
const expressApp = express();
|
||||||
expressApp.use(cors());
|
expressApp.use(cors());
|
||||||
|
|
@ -51,27 +37,12 @@ expressApp.use((req, res, next) => {
|
||||||
res.status(404).json({ error: 'Not found' });
|
res.status(404).json({ error: 'Not found' });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Event listener for file_shared events
|
|
||||||
// app.event('file_shared', async ({event, client}) => {
|
|
||||||
// if (parseFloat(event.event_ts) < BOT_START_TIME) return;
|
|
||||||
// if (event.channel_id !== process.env.SLACK_CHANNEL_ID) return;
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// await fileUpload.handleFileUpload(event, client);
|
|
||||||
// } catch (error) {
|
|
||||||
// logger.error(`Upload failed: ${error.message}`);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// Startup LOGs
|
// Startup LOGs
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
await fileUpload.initialize();
|
|
||||||
// await app.start();
|
|
||||||
const port = parseInt(process.env.PORT || '4553', 10);
|
const port = parseInt(process.env.PORT || '4553', 10);
|
||||||
expressApp.listen(port, () => {
|
expressApp.listen(port, () => {
|
||||||
logger.info('CDN started successfully 🔥', {
|
logger.info('CDN started successfully 🔥', {
|
||||||
slackMode: 'Socket Mode',
|
|
||||||
apiPort: port,
|
apiPort: port,
|
||||||
startTime: new Date().toISOString()
|
startTime: new Date().toISOString()
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
const winston = require('winston');
|
|
||||||
|
|
||||||
const logger = winston.createLogger({
|
|
||||||
level: 'info',
|
|
||||||
format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
|
|
||||||
transports: [new winston.transports.Console()],
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = logger;
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
{
|
{
|
||||||
"name": "cdn-v2-hackclub",
|
"name": "cdn-v2-hackclub",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Slack app and API to upload files to S3-compatible storage with unique URLs",
|
"description": "API to upload files to S3-compatible storage with unique URLs",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node index.js"
|
"start": "bun index.js",
|
||||||
|
"dev": "bun --watch index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.478.0",
|
"@aws-sdk/client-s3": "^3.478.0",
|
||||||
"@slack/bolt": "^4.2.0",
|
|
||||||
"@slack/web-api": "^7.8.0",
|
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^10.0.0",
|
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"node-fetch": "^2.6.1",
|
"node-fetch": "^2.6.1",
|
||||||
"p-limit": "^6.2.0",
|
"p-limit": "^6.2.0",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const {uploadToStorage} = require('../storage');
|
const {uploadToStorage} = require('../storage');
|
||||||
const {generateUrl, getCdnUrl} = require('./utils');
|
const {generateUrl} = require('./utils');
|
||||||
const logger = require('../config/logger');
|
const logger = require('../config/logger');
|
||||||
|
|
||||||
// Sanitize file name for storage
|
// Sanitize file name for storage
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
const logger = require('../config/logger');
|
|
||||||
|
|
||||||
const getCdnUrl = () => process.env.AWS_CDN_URL;
|
const getCdnUrl = () => process.env.AWS_CDN_URL;
|
||||||
|
|
||||||
const generateUrl = (version, fileName) => {
|
const generateUrl = (version, fileName) => {
|
||||||
|
|
|
||||||
|
|
@ -1,137 +0,0 @@
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
@ -1,302 +0,0 @@
|
||||||
const fetch = require('node-fetch');
|
|
||||||
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 = 50 * 1024 * 1024; // 50MB in bytes
|
|
||||||
const CONCURRENT_UPLOADS = 3; // Max concurrent uploads (messages)
|
|
||||||
|
|
||||||
const processedMessages = new Map();
|
|
||||||
let uploadLimit;
|
|
||||||
|
|
||||||
async function initialize() {
|
|
||||||
const pLimit = (await import('p-limit')).default;
|
|
||||||
uploadLimit = pLimit(CONCURRENT_UPLOADS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Basic stuff
|
|
||||||
function isMessageTooOld(eventTs) {
|
|
||||||
const eventTime = parseFloat(eventTs) * 1000;
|
|
||||||
return (Date.now() - eventTime) > 24 * 60 * 60 * 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isMessageProcessed(messageTs) {
|
|
||||||
return processedMessages.has(messageTs);
|
|
||||||
}
|
|
||||||
|
|
||||||
function markMessageAsProcessing(messageTs) {
|
|
||||||
processedMessages.set(messageTs, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// File processing
|
|
||||||
function sanitizeFileName(fileName) {
|
|
||||||
let sanitized = fileName.replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
||||||
return sanitized || `upload_${Date.now()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateUniqueFileName(fileName) {
|
|
||||||
return `${Date.now()}-${crypto.randomBytes(16).toString('hex')}-${sanitizeFileName(fileName)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// upload functionality
|
|
||||||
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) {
|
|
||||||
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}`}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) throw new Error('Download failed');
|
|
||||||
|
|
||||||
const buffer = await response.buffer();
|
|
||||||
const uniqueFileName = generateUniqueFileName(file.name);
|
|
||||||
const userDir = `s/${fileMessage.user}`;
|
|
||||||
|
|
||||||
const success = await uploadLimit(() =>
|
|
||||||
storage.uploadToStorage(userDir, uniqueFileName, buffer, file.mimetype)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!success) throw new Error('Upload failed');
|
|
||||||
|
|
||||||
uploadedFiles.push({
|
|
||||||
name: uniqueFileName,
|
|
||||||
originalName: file.name,
|
|
||||||
url: generateFileUrl(userDir, uniqueFileName),
|
|
||||||
contentType: file.mimetype
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`Failed: ${file.name} - ${error.message}`);
|
|
||||||
failedFiles.push(file.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
uploadedFiles,
|
|
||||||
failedFiles,
|
|
||||||
sizeFailedFiles,
|
|
||||||
isSizeError: sizeFailedFiles.length > 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slack interaction
|
|
||||||
async function addProcessingReaction(client, event, fileMessage) {
|
|
||||||
try {
|
|
||||||
await client.reactions.add({
|
|
||||||
name: 'beachball',
|
|
||||||
timestamp: fileMessage.ts,
|
|
||||||
channel: event.channel_id
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Failed to add reaction:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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: reactionName,
|
|
||||||
timestamp: fileMessage.ts,
|
|
||||||
channel: event.channel_id
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Failed to update reactions:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function findFileMessage(event, client) {
|
|
||||||
try {
|
|
||||||
const fileInfo = await client.files.info({
|
|
||||||
file: event.file_id,
|
|
||||||
include_shares: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!fileInfo.ok || !fileInfo.file) {
|
|
||||||
throw new Error('Could not get file info');
|
|
||||||
}
|
|
||||||
|
|
||||||
const channelShare = fileInfo.file.shares?.public?.[event.channel_id] ||
|
|
||||||
fileInfo.file.shares?.private?.[event.channel_id];
|
|
||||||
|
|
||||||
if (!channelShare || !channelShare.length) {
|
|
||||||
throw new Error('No share info found for this channel');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the EXACT message using the ts from share info (channelShare)
|
|
||||||
const messageTs = channelShare[0].ts;
|
|
||||||
|
|
||||||
const messageInfo = await client.conversations.history({
|
|
||||||
channel: event.channel_id,
|
|
||||||
latest: messageTs,
|
|
||||||
limit: 1,
|
|
||||||
inclusive: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!messageInfo.ok || !messageInfo.messages.length) {
|
|
||||||
throw new Error('Could not find original message');
|
|
||||||
}
|
|
||||||
|
|
||||||
return messageInfo.messages[0];
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error finding file message:', error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
if (fileMessage && reactionAdded) {
|
|
||||||
try {
|
|
||||||
await client.reactions.remove({
|
|
||||||
name: 'beachball',
|
|
||||||
timestamp: fileMessage.ts,
|
|
||||||
channel: channelId
|
|
||||||
});
|
|
||||||
} catch (cleanupError) {
|
|
||||||
if (cleanupError.data.error !== 'no_reaction') {
|
|
||||||
logger.error('Cleanup error:', cleanupError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await client.reactions.add({
|
|
||||||
name: 'x',
|
|
||||||
timestamp: fileMessage.ts,
|
|
||||||
channel: channelId
|
|
||||||
});
|
|
||||||
} catch (cleanupError) {
|
|
||||||
logger.error('Cleanup error:', cleanupError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleFileUpload(event, client) {
|
|
||||||
let fileMessage = null;
|
|
||||||
let reactionAdded = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (isMessageTooOld(event.event_ts)) return;
|
|
||||||
|
|
||||||
fileMessage = await findFileMessage(event, client);
|
|
||||||
if (!fileMessage || isMessageProcessed(fileMessage.ts)) return;
|
|
||||||
|
|
||||||
markMessageAsProcessing(fileMessage.ts);
|
|
||||||
await addProcessingReaction(client, event, fileMessage);
|
|
||||||
reactionAdded = true;
|
|
||||||
|
|
||||||
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}`);
|
|
||||||
await handleError(client, event.channel_id, fileMessage, reactionAdded);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { handleFileUpload, initialize };
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
|
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
|
||||||
const path = require('path');
|
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const logger = require('./config/logger');
|
const logger = require('./config/logger');
|
||||||
const {generateFileUrl} = require('./utils');
|
const {generateFileUrl} = require('./utils');
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue