NotParcel/messageHandlers/hub_settings.js

194 lines
7.6 KiB
JavaScript

const client = global.discord_client
const pool = global.db_pool;
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
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.hubSettingsHandlers[user.id];
delete global.dmHandlers[user.id];
user.send('Closing hub settings.');
}
const opts = { // Map of option number to step number
1: {
step: 21, exec: (hubId, uid) => {
return 'Please provide the new short description. Say `cancel` to exit.';
}
},
2: {
step: 22, exec: (hubId, uid) => {
return 'Please provide the new long description. Say `cancel` to exit.';
}
},
3: {
step: 1, exec: async (hubId, uid) => {
const [hub] = await pool.query('SELECT allowGiftPurchase FROM hubs WHERE id = ?', [hubId]);
if (!hub) return 'Error fetching hub data.';
const newValue = hub.allowGiftPurchase ? 0 : 1;
await pool.query('UPDATE hubs SET allowGiftPurchase = ? WHERE id = ?', [newValue, hubId]);
return `Allow Gift Purchase is now set to: ${newValue ? "Yes" : "No"}.\n\nType another option number, or \`cancel\` to exit.`;
}
},
4: {
step: 24, exec: (hubId, uid) => {
return 'Please provide the new Terms of Service. Say `cancel` to exit.';
}
},
5: {
step: 25, exec: async (hubId, uid) => {
const newKey = crypto.randomBytes(16).toString('hex');
await pool.query('UPDATE hubs SET secretKey = ? WHERE id = ?', [newKey, hubId]);
return `New secret key generated: ||${newKey}||\n\nType another option number, or \`cancel\` to exit.`;
}
},
6: {
step: 1, exec: (hubId, uid) => {
return 'Parcel Auto-Import setup is not yet implemented. Type another option number, or `cancel` to exit.';
}
},
7: {
step: 27, exec: async (hubId, uid) => {
// generate a random confirmation code
const confirmationCode = crypto.randomBytes(8).toString('hex');
global.hubSettingsHandlers[uid].confirmationCode = confirmationCode;
return `***__WARNING: THIS WILL DELETE THE HUB AND ALL PRODUCTS! THIS ACTION CANNOT BE UNDONE.__***\nTo confirm deletion, please type the following: \`confirm ${confirmationCode}\`. Type \`cancel\` to exit.`;
}
}
}
const execute = async (message) => {
switch (global.hubSettingsHandlers[message.author.id].step) {
case 1: // Main Menu
if (message.content.toLowerCase() === 'cancel') {
cancel(message.author);
return;
}
const option = parseInt(message.content.trim());
if (!opts[option]) {
message.channel.send('Invalid option. Please enter a valid option number or `cancel` to exit.');
return;
}
global.hubSettingsHandlers[message.author.id].step = opts[option].step;
const response = await opts[option].exec(global.hubSettingsHandlers[message.author.id].hub, message.author.id);
message.channel.send(response);
break;
case 21: // Edit Short Description
const shortDesc = message.content.trim();
if (shortDesc.toLowerCase() === 'cancel') {
cancel(message.author);
return;
}
if (shortDesc.length > 256) {
message.channel.send('Short description is too long. Please limit to 256 characters.');
return;
}
await pool.query('UPDATE hubs SET shortDescription = ? WHERE id = ?', [shortDesc, global.hubSettingsHandlers[message.author.id].hub]);
global.hubSettingsHandlers[message.author.id].step = 1;
message.channel.send('Short description updated.\n\nType an option number, or `cancel` to exit.');
break;
case 22: // Edit Long Description
const longDesc = message.content.trim();
if (longDesc.toLowerCase() === 'cancel') {
cancel(message.author);
return;
}
if (longDesc.length > 5000) {
message.channel.send('Long description is too long. Please limit to 5000 characters.');
return;
}
await pool.query('UPDATE hubs SET longDescription = ? WHERE id = ?', [longDesc, global.hubSettingsHandlers[message.author.id].hub]);
global.hubSettingsHandlers[message.author.id].step = 1;
message.channel.send('Long description updated.\n\nType an option number, or `cancel` to exit.');
break;
case 24: // Edit Terms of Service
const tos = message.content.trim();
if (tos.toLowerCase() === 'cancel') {
cancel(message.author);
return;
}
if (tos.length > 10000) {
message.channel.send('Terms of Service is too long. Please limit to 10000 characters.');
return;
}
await pool.query('UPDATE hubs SET tos = ? WHERE id = ?', [tos, global.hubSettingsHandlers[message.author.id].hub]);
global.hubSettingsHandlers[message.author.id].step = 1;
message.channel.send('Terms of Service updated.\n\nType an option number, or `cancel` to exit.');
break;
case 27: // Delete Hub
const confirmMatch = message.content.trim().match(/^confirm (\w{16})$/);
if (message.content.toLowerCase() === 'cancel') {
cancel(message.author);
return;
}
if (!confirmMatch || confirmMatch[1] !== global.hubSettingsHandlers[message.author.id].confirmationCode) {
message.channel.send('Invalid confirmation code. Please type the exact confirmation code sent to you, or `cancel` to exit.');
return;
}
message.channel.send('Deletion Confirmed. Starting deletion process...').then(async msg => {
// Proceed with deletion
const hubId = global.hubSettingsHandlers[message.author.id].hub;
// Delete fileAuth
msg.edit(msg.content + '\nDeleting file authorizations...');
await pool.query('DELETE FROM fileAuth WHERE product IN (SELECT id FROM products WHERE hubId = ?)', [hubId]);
// Delete files
msg.edit(msg.content + '\nDeleting product files...');
// const safeFileId = path.basename(product.file);
// const filePath = path.join(__dirname, '../productFiles', safeFileId); // Code we use in the CDN route
const files = await pool.query('SELECT file FROM products WHERE hubId = ?', [hubId]);
for (const fileRow of files) {
const safeFileId = path.basename(fileRow.file);
const filePath = path.join(__dirname, '../productFiles', safeFileId); // Code we use in the CDN route
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
msg.edit(msg.content + `\nDeleted file: ${safeFileId}`);
} else {
msg.edit(msg.content + `\nFile not found, skipping: ${safeFileId}`);
}
}
// Delete purchases
msg.edit(msg.content + '\nDeleting purchases...');
await pool.query('DELETE FROM purchases WHERE productId IN (SELECT id FROM products WHERE hubId = ?)', [hubId]);
// Delete products
msg.edit(msg.content + '\nDeleting products...');
await pool.query('DELETE FROM products WHERE hubId = ?', [hubId]);
// Delete hub
msg.edit(msg.content + '\nDeleting hub...');
await pool.query('DELETE FROM hubs WHERE id = ?', [hubId]);
msg.edit(msg.content + '\nDeletion process complete.');
cancel(message.author);
});
break;
default:
message.channel.send('Invalid step.');
log.error(`Invalid hub settings step for user ${message.author.id}: ${global.hubSettingsHandlers[message.author.id].step}`);
cancel(message.author);
break;
}
}
module.exports = execute