Add hub settings and setup maybe?

This commit is contained in:
Christopher Cookman 2025-09-14 16:24:18 -06:00
parent 197f497652
commit 192d1431c1
6 changed files with 241 additions and 1 deletions

9
.env.example Normal file
View file

@ -0,0 +1,9 @@
SESSION_SECRET=superlongandsecretrandomstring
DB_HOST=localhost
DB_PORT=3306
DB_USER=mystore
DB_PASS=mystore
DB_NAME=mystoredb
NODE_ENV=production
DISCORD_TOKEN=my discord bot token
BASE_URL=https://np.example.com

View file

@ -220,6 +220,16 @@ module.exports = {
{ {
name: "hub", name: "hub",
description: "Get the hub URL.", description: "Get the hub URL.",
},
{
name: "setup",
description: "Setup the bot for this server",
default_member_permissions: 0
},
{
name: "settings",
description: "View and manage hub settings",
default_member_permissions: 0
} }
], ],
admin: [] admin: []

View file

@ -9,7 +9,7 @@ const execute = async (interaction) => {
const [pairing] = await pool.query('SELECT * FROM users WHERE pairingCode = ?', [pairingCode]); const [pairing] = await pool.query('SELECT * FROM users WHERE pairingCode = ?', [pairingCode]);
if (!pairing) return interaction.reply({ content: "Invalid pairing code", ephemeral: true }); if (!pairing) return interaction.reply({ content: "Invalid pairing code", ephemeral: true });
await pool.query('UPDATE users SET discordId = ?, discordDisplayName = ?, pairingCode = NULL WHERE pairingCode = ?', [discordID, interaction.user.displayName, pairingCode]); await pool.query('UPDATE users SET discordId = ?, discordDisplayName = ?, pairingCode = NULL WHERE pairingCode = ?', [discordID, interaction.user.displayName, pairingCode]);
return interaction.reply({ content: "Successfully linked your account", ephemeral: true }); return interaction.reply({ content: "Successfully linked your account. Please relaunch the hub!", ephemeral: true });
} }
module.exports = { execute } module.exports = { execute }

47
commands/settings.js Normal file
View file

@ -0,0 +1,47 @@
const client = global.discord_client
const pool = global.db_pool;
const hubSettingsHandler = require('../messageHandlers/hub_settings.js');
if (!global.productCreationData) global.productCreationData = {};
const execute = async (interaction) => {
if (!interaction.guildId) return interaction.reply({ content: "This command can only be used in a server", ephemeral: true });
if (global.hubSettingsHandlers[interaction.user.id]) return interaction.reply({ content: "See DMs!", ephemeral: true });
try {
const guildId = interaction.guildId;
const hubResult = await pool.query(`SELECT * FROM hubs WHERE discordGuild = ?`, [guildId]);
if (hubResult.length === 0) {
delete global.hubSettingsHandlers[interaction.user.id];
return interaction.reply({ content: "This guild does not have a hub set up!", ephemeral: true });
}
// Proceed with settings menu
await interaction.reply({ ephemeral: true, content: "Getting things ready..." });
await interaction.user.send({ embeds: [{
title: `Hub Settings for ${hubResult[0].name}`,
description: `**Short Description:** ${hubResult[0].shortDescription}\n**Long Description:** ${hubResult[0].longDescription}\n**Allow Gift Purchase:** ${hubResult[0].allowGiftPurchase ? "Yes" : "No"}\n**Terms of Service:** ${hubResult[0].tos}\n\nHub Secret Key: ||${hubResult[0].secretKey}||`,
color: 0x00AE86,
fields: [
{ name: "Main Menu", value: "Type an option number, or `cancel` to close." },
{ name: "1. Edit Short Description", value: "Change the short description of the hub." },
{ name: "2. Edit Long Description", value: "Change the long description of the hub." },
{ name: "3. Toggle Allow Gift Purchase", value: "Enable or disable allowing users to buy gifts for others on this hub." },
{ name: "4. Edit Terms of Service", value: "Change the terms of service for this hub." },
{ name: "5. Regenerate Secret Key", value: "Generate a new secret key for this hub. (Old key will be invalidated)" },
{ name: "6. Parcel Auto-Import", value: "Set up automatic importing of Parcel purchases when linking." }
]
}] });
await interaction.user.send({ content: "Please provide a description for the product. Say `cancel` to exit." });
interaction.editReply({ephemeral: true, content: "Check your DMs!"});
global.hubSettingsHandlers[interaction.user.id] = {
step: 1,
hub: hubResult[0].id
};
global.dmHandlers[interaction.user.id] = hubSettingsHandler;
} catch (err) {
console.error(err);
delete global.productCreationData[interaction.user.id];
return interaction.editReply({ content: "An error occurred.", ephemeral: true });
}
};
module.exports = { execute }

46
commands/setup.js Normal file
View file

@ -0,0 +1,46 @@
const client = global.discord_client
const pool = global.db_pool;
// /setup command to initialize a hub in the server
/* Hub table schema
CREATE TABLE IF NOT EXISTS hubs (
id CHAR(36) PRIMARY KEY DEFAULT UUID(),
ownerId BIGINT, -- Owner of the hub (Discord ID)
discordGuild BIGINT, -- Discord Guild ID
name VARCHAR(128), -- Name of the hub
shortDescription VARCHAR(256), -- Short description of the hub
longDescription TEXT, -- Long description of the hub
allowGiftPurchase BOOLEAN DEFAULT TRUE, -- Allow users to buy gifts for others on this hub.
tos TEXT DEFAULT 'This Hub does not have any Terms of Service yet. If you are the Hub owner, you can update this under settings.', -- Terms of Service
bgmId BIGINT DEFAULT NULL, -- Background Music ID
secretKey VARCHAR(128) UNIQUE NOT NULL -- Secret key for the hub to authenticate
)
*/
const crypto = require('crypto');
const log = require('../utils/logger.js');
const execute = async (interaction) => {
if (!interaction.guildId) return interaction.reply({ content: "This command can only be used in a server", ephemeral: true });
try {
const guildId = interaction.guildId;
const [existingHub] = await pool.query('SELECT * FROM hubs WHERE discordGuild = ?', [guildId]);
if (existingHub) return interaction.reply({ content: "This server already has a hub set up.", ephemeral: true });
const ownerId = interaction.user.id;
const name = interaction.guild.name;
const shortDescription = "Our super cool product hub!";
const longDescription = "Welcome to our super cool product hub! We offer a variety of awesome products for you to explore and purchase. Enjoy your stay!";
const [result] = await pool.query('INSERT INTO hubs (ownerId, discordGuild, name, shortDescription, longDescription, secretKey) VALUES (?, ?, ?, ?, ?, ?)', [ownerId, guildId, name, shortDescription, longDescription, crypto.randomBytes(64).toString('hex')]);
if (result.affectedRows === 1) {
return interaction.reply({ content: `Hub successfully created with ID: \`${result.insertId}\`. Get details via the /settings command!`, ephemeral: true });
} else {
return interaction.reply({ content: "An error occurred while creating the hub. Please try again later.", ephemeral: true });
}
} catch (error) {
log.error(error);
return interaction.reply({ content: "An error occurred while creating the hub. Please try again later.", ephemeral: true });
}
}
module.exports = { execute }

View file

@ -0,0 +1,128 @@
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.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: () => {
return 'Please provide the new short description. Say `cancel` to exit.';
}},
2: {step: 22, exec: () => {
return 'Please provide the new long description. Say `cancel` to exit.';
}},
3: {step: 23, exec: async (hubId) => {
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: () => {
return 'Please provide the new Terms of Service. Say `cancel` to exit.';
}},
5: {step: 25, exec: async (hubId) => {
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: 26, exec: () => {
return 'Parcel Auto-Import setup is not yet implemented. Type another option number, or `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.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;
default:
message.channel.send('Invalid step.');
cancel(message.author);
break;
}
}
module.exports = execute