const client = global.discord_client const pool = global.db_pool; const crypto = require('crypto'); const fs = require('fs'); const { pipeline } = require('stream'); const { promisify } = require('util'); const streamPipeline = promisify(pipeline); /** * Downloads a file from a URL to a specified destination. * @param {string} url - The URL to download the file from. * @param {string} dest - The destination file path to save the file. * @param {function} cb - Callback function called on completion or error. */ async function download(url, dest, cb) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch ${url}: ${response.statusText}`); } await streamPipeline(response.body, fs.createWriteStream(dest)); cb(null); // Signal success } catch (error) { cb(error); // Pass error to callback } } const cancel = (user) => { delete global.productUpdateData[user.id]; delete global.dmHandlers[user.id]; user.send('Product update cancelled.'); } const execute = async (message) => { switch (global.productUpdateData[message.author.id].type) { case "name": // Name const newName = message.content.trim(); if (newName.toLowerCase() === 'cancel') { cancel(message.author); return; } await pool.query('UPDATE products SET name = ? WHERE id = ?', [newName, global.productUpdateData[message.author.id].id]); message.channel.send('Product name updated!'); break; case "description": // Description const newDesc = message.content.trim(); if (newDesc.toLowerCase() === 'cancel') { cancel(message.author); return; } await pool.query('UPDATE products SET description = ? WHERE id = ?', [newDesc, global.productUpdateData[message.author.id].id]); message.channel.send('Product description updated!'); break; case "devProductId": // Dev Product ID const newDevId = message.content.trim(); if (newDevId.toLowerCase() === 'cancel') { cancel(message.author); return; } if (!/^\d{1,15}$/.test(newDevId)) { message.channel.send('Invalid Dev Product ID. It must be a number between 1 and 15 digits.'); return; } await pool.query('UPDATE products SET devProductID = ? WHERE id = ?', [newDevId, global.productUpdateData[message.author.id].id]); message.channel.send('Dev Product ID updated!'); break; case "imageId": // Image ID const newImageId = message.content.trim(); if (newImageId.toLowerCase() === 'cancel') { cancel(message.author); return; } if (!/^\d{1,15}$/.test(newImageId)) { message.channel.send('Invalid Image ID. It must be a number between 1 and 15 digits.'); return; } await pool.query('UPDATE products SET imageId = ? WHERE id = ?', [newImageId, global.productUpdateData[message.author.id].id]); message.channel.send('Image ID updated!'); break; case "file": // File if (message.content.trim().toLowerCase() === 'cancel') { cancel(message.author); return; } if (message.attachments.size > 0) { const attachment = message.attachments.first(); const validExtensions = ['zip', 'rbxm', 'rbxmx']; const fileExtension = attachment.name.split('.').pop().toLowerCase(); if (!validExtensions.includes(fileExtension)) { message.channel.send('Invalid file type. Only zip, rbxm, and rbxmx files are allowed.'); cancel(message.author); return; } const file = fs.createWriteStream(`./productFiles/${crypto.randomBytes(8).toString('hex')}`); download(attachment.url, file.path, async (err) => { if (err) { message.channel.send('Failed to download the file.'); cancel(message.author); return; } const fileType = attachment.name.split('.').pop(); await pool.query('UPDATE products SET file = ?, fileType = ? WHERE id = ?', [file.path.split('/').pop(), fileType, global.productUpdateData[message.author.id].id]); message.channel.send('File updated successfully!'); }); } else { message.channel.send('No file attached. Please upload the product file.'); } break; case "stock": // Stocking info const newStock = parseInt(message.content.trim()); if (isNaN(newStock)) { message.channel.send('Invalid stock quantity. It must be a number.'); return; } await pool.query('UPDATE products SET stock = ? WHERE id = ?', [newStock, global.productUpdateData[message.author.id].id]); message.channel.send('Stock quantity updated!'); break; case "category": // Category const newCategory = message.content.trim(); if (newCategory.toLowerCase() === 'cancel') { cancel(message.author); return; } if (newCategory.toLowerCase() === '~none') { newCategory = null; } await pool.query('UPDATE products SET category = ? WHERE id = ?', [newCategory, global.productUpdateData[message.author.id].id]); message.channel.send('Category updated!'); break; case "delete": // Delete const product = global.productUpdateData[message.author.id].id; console.log(product); if (message.content.toLowerCase() === 'cancel') { cancel(message.author); return; } if (message.content.toLowerCase() !== 'confirm') { message.channel.send('You must type `confirm` to delete the product, or `cancel` to exit.'); return; } // Proceed with deletion message.channel.send('Deletion Confirmed. Starting deletion process...').then(async msg => { let curMsg = msg.content; // Delete fileAuth curMsg = curMsg + '\nDeleting file authorizations...' await msg.edit(curMsg); await pool.query('DELETE FROM fileAuth WHERE product = ?', [product]); // Delete file curMsg = curMsg + '\nDeleting product file...' await msg.edit(curMsg); const [product] = await pool.query('SELECT file FROM products WHERE id = ?', [global.productUpdateData[message.author.id].id]); if (product) { const safeFileId = path.basename(product.file); const filePath = path.join(__dirname, '../productFiles', safeFileId); // Code we use in the CDN route if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); curMsg = curMsg + `\nDeleted file: ${safeFileId}`; await msg.edit(curMsg); } else { curMsg = curMsg + `\nFile not found (skipping): ${safeFileId}`; await msg.edit(curMsg); } } // Delete purchases curMsg = curMsg + '\nDeleting purchases...'; await msg.edit(curMsg); await pool.query('DELETE FROM purchases WHERE productId = ?', [global.productUpdateData[message.author.id].id]); // Delete product curMsg = curMsg + '\nDeleting product...'; await msg.edit(curMsg); await pool.query('DELETE FROM products WHERE id = ?', [global.productUpdateData[message.author.id].id]); curMsg = curMsg + '\nProduct deletion complete.'; await msg.edit(curMsg); }); break; default: message.channel.send('Invalid type. Somehow?'); cancel(message.author); break; } delete global.productUpdateData[message.author.id]; delete global.dmHandlers[message.author.id]; } module.exports = execute