diff --git a/src/routers/upload.ts b/src/routers/upload.ts index 748fbe5..0e12cee 100644 --- a/src/routers/upload.ts +++ b/src/routers/upload.ts @@ -10,7 +10,7 @@ import { processUploaded } from '../storage'; import { path, log, verify, getTrueHttp, getTrueDomain, generateId, formatBytes } from '../utils'; import { data } from '../data'; import { users } from '../auth'; -const { maxUploadSize, resourceIdSize, gfyIdSize, resourceIdType, spaceReplace }: Config = fs.readJsonSync(path('config.json')); +const { maxUploadSize, resourceIdSize, gfyIdSize, resourceIdType, spaceReplace, adminWebhookEnabled, adminWebhookUrl, adminWebhookUsername, adminWebhookAvatar }: Config = fs.readJsonSync(path('config.json')); const { CODE_UNAUTHORIZED, CODE_PAYLOAD_TOO_LARGE }: MagicNumbers = fs.readJsonSync(path('MagicNumbers.json')); const ASS_LOGO = 'https://cdn.discordapp.com/icons/848274994375294986/8d339d4a2f3f54b2295e5e0ff62bd9e6.png?size=1024'; @@ -110,42 +110,58 @@ router.post('/', (req: Request, res: Response, next: Function) => { .then(() => { // Log the upload const logInfo = `${req.file!.originalname} (${req.file!.mimetype}, ${formatBytes(req.file.size)})`; - log.success('File uploaded', logInfo, `uploaded by ${users[req.token ?? ''] ? users[req.token ?? ''].username : ''}`); + const uploader = users[req.token ?? ''] ? users[req.token ?? ''].username : ''; + log.success('File uploaded', logInfo, `uploaded by ${uploader}`); // Build the URLs const resourceUrl = `${getTrueHttp()}${trueDomain}/${resourceId}`; const thumbnailUrl = `${getTrueHttp()}${trueDomain}/${resourceId}/thumbnail`; const deleteUrl = `${getTrueHttp()}${trueDomain}/${resourceId}/delete/${req.file.deleteId}`; + const buildSendWebhook = (url: string, username: string, avatar: string, admin = false) => { + if (url === '') return; + + // Build the webhook + const hook = new Webhook(url); + hook.setUsername(username); + hook.setAvatar(avatar); + + // Build the embed + const embed = new MessageBuilder() + .setTitle(logInfo) + //@ts-ignore + .setURL(resourceUrl) + .setDescription(`${admin ? `**User:** \`${uploader}\`\n` : ''}**Size:** \`${formatBytes(req.file.size)}\`\n**[Delete](${deleteUrl})**`) + .setThumbnail(thumbnailUrl) + .setColor(req.file.vibrant) + .setTimestamp(); + + // Send the embed to the webhook, then delete the client after to free resources + log.debug(`Sending${admin ? ' admin' : ''} embed to webhook`); + hook.send(embed) + .then(() => log.debug(`Webhook${admin ? ' admin' : ''} sent`)) + .catch((err) => log.error('Webhook error').err(err)); + } + // Send the response res.type('json').send({ resource: resourceUrl, thumbnail: thumbnailUrl, delete: deleteUrl }) .on('finish', () => { log.debug('Upload response sent'); // After we have sent the user the response, also send a Webhook to Discord (if headers are present) - if (req.headers['x-ass-webhook-url']) { - - // Build the webhook - const hook = new Webhook(req.headers['x-ass-webhook-url']?.toString()); - hook.setUsername(req.headers['x-ass-webhook-username']?.toString() || 'ass'); - hook.setAvatar(req.headers['x-ass-webhook-avatar']?.toString() || ASS_LOGO); - - // Build the embed - const embed = new MessageBuilder() - .setTitle(logInfo) - //@ts-ignore - .setURL(resourceUrl) - .setDescription(`**Size:** \`${formatBytes(req.file.size)}\`\n**[Delete](${deleteUrl})**`) - .setThumbnail(thumbnailUrl) - .setColor(req.file.vibrant) - .setTimestamp(); - - // Send the embed to the webhook, then delete the client after to free resources - log.debug('Sending embed to webhook'); - hook.send(embed) - .then(() => log.debug('Webhook sent')) - .catch((err) => log.error('Webhook error').err(err)); - } + if (req.headers['x-ass-webhook-url']) + buildSendWebhook( + req.headers['x-ass-webhook-url']?.toString(), + req.headers['x-ass-webhook-username']?.toString() || 'ass', + req.headers['x-ass-webhook-avatar']?.toString() || ASS_LOGO); + + // Send the webhook to the default webhook, if it exists + if (adminWebhookEnabled) + buildSendWebhook( + adminWebhookUrl, + adminWebhookUsername.trim().length === 0 ? 'ass admin logs' : adminWebhookUsername, + adminWebhookAvatar.trim().length === 0 ? ASS_LOGO : adminWebhookAvatar, + true); // Also update the users upload count if (!users[req.token ?? '']) { diff --git a/src/setup.js b/src/setup.js index 9818a7d..4a1a964 100644 --- a/src/setup.js +++ b/src/setup.js @@ -15,9 +15,17 @@ const config = { dataEngine: '@tycrek/papito', frontendName: 'ass-x', useSia: false, + adminWebhookEnabled: false, s3enabled: false, }; +// Default admin webhook +const adminWebhookConfig = { + adminWebhookUrl: '', + adminWebhookUsername: '', + adminWebhookAvatar: '', +} + // Default S3 config const s3config = { s3endpoint: 'sfo3.digitaloceanspaces.com', @@ -178,6 +186,12 @@ function doSetup() { default: config.useSia, required: false }, + adminWebhookEnabled: { + description: 'Enable admin Discord webhook (This will let you audit ALL uploads, from ALL users)', + type: 'boolean', + default: config.adminWebhookEnabled, + required: false + }, s3enabled: { description: 'Enable uploading to S3 storage endpoints', type: 'boolean', @@ -187,6 +201,29 @@ function doSetup() { } }; + const adminWebhookSchema = { + properties: { + adminWebhookUrl: { + description: 'Discord webhook URL for admin notifications', + type: 'string', + default: adminWebhookConfig.adminWebhookUrl, + required: true + }, + adminWebhookUsername: { + description: 'Username to send the webhook as', + type: 'string', + default: adminWebhookConfig.adminWebhookUsername, + required: false + }, + adminWebhookAvatar: { + description: 'Avatar to use for the webhook icon', + type: 'string', + default: adminWebhookConfig.adminWebhookAvatar, + required: false + } + } + }; + const s3schema = { properties: { s3endpoint: { @@ -231,9 +268,12 @@ function doSetup() { prompt.get(setupSchema) .then((r) => results = r) // skipcq: JS-0086 + // Check if using admin webhook + .then(() => results.adminWebhookEnabled ? prompt.get(adminWebhookSchema) : adminWebhookConfig) // skipcq: JS-0229 + .then((r) => Object.entries(r).forEach(([k, v]) => results[k] = v)) // skipcq: JS-0086 // Check if using S3 .then(() => results.s3enabled ? prompt.get(s3schema) : s3config) // skipcq: JS-0229 - .then((r) => Object.entries(r).forEach(([key, value]) => results[key] = value)) // skipcq: JS-0086 + .then((r) => Object.entries(r).forEach(([k, v]) => results[k] = v)) // skipcq: JS-0086 // Verify information is correct .then(() => log diff --git a/src/types/json.d.ts b/src/types/json.d.ts index 3f9579e..7d0fd5e 100644 --- a/src/types/json.d.ts +++ b/src/types/json.d.ts @@ -16,6 +16,10 @@ declare module 'ass-json' { frontendName: string indexFile: string useSia: boolean + adminWebhookEnabled: boolean + adminWebhookUrl: string + adminWebhookUsername: string + adminWebhookAvatar: string s3enabled: boolean s3endpoint: string s3bucket: string