Force Push, guh

This commit is contained in:
Christopher Cookman 2025-01-16 21:09:47 -07:00
parent 9d9f0e2e91
commit 700ba6b1c9
14 changed files with 710 additions and 14 deletions

View file

@ -103,6 +103,119 @@ module.exports = {
required: true
}
]
},
{
"name": "createproduct",
"description": "Create a product",
"default_member_permissions": 0,
"options": [
{
"name": "name",
"description": "The name of the product",
"type": 3,
"required": true
}
]
},
{
name: "deleteproduct",
description: "Delete a product",
default_member_permissions: 0,
options: [
{
name: "name",
description: "The name of the product",
type: Discord.ApplicationCommandOptionType.String,
required: true
}
]
},
{
name: "update",
description: "Update a product",
default_member_permissions: 0,
options: [
{
name: "name",
description: "The name of the product",
type: Discord.ApplicationCommandOptionType.String,
required: true
},
{
name: "field",
description: "The field to update",
type: Discord.ApplicationCommandOptionType.String,
required: true,
choices: [
{
name: "Name",
value: "name"
},
{
name: "Description",
value: "description"
},
{
name: "Dev Product ID",
value: "devProductId"
},
{
name: "Image ID",
value: "imageId"
},
{
name: "File",
value: "file"
},
{
name: "Stocking Info",
value: "stock"
},
{
name: "Category",
value: "category"
}
]
}
]
},
{
name: "forcelink",
description: "Force link a Roblox account to a Discord user",
default_member_permissions: 0,
options: [
{
name: "roblox-id",
description: "The Roblox ID of the user",
type: Discord.ApplicationCommandOptionType.Number,
required: true
},
{
name: "discord-id",
description: "The Discord ID of the user",
type: Discord.ApplicationCommandOptionType.User,
required: true
}
]
},
{
name: "forceunlink",
description: "Force unlink a Roblox account from a Discord user",
default_member_permissions: 0,
options: [
{
name: "roblox-id",
description: "The Roblox ID of the user",
type: Discord.ApplicationCommandOptionType.Number,
required: false
},
{
name: "discord-id",
description: "The Discord ID of the user",
type: Discord.ApplicationCommandOptionType.User,
required: false
}
]
}
],
admin: []

47
commands/createproduct.js Normal file
View file

@ -0,0 +1,47 @@
const client = global.discord_client
const pool = global.db_pool;
const createProdHandler = require('../messageHandlers/create_prod.js');
if (!global.productCreationData) global.productCreationData = {};
const execute = async (interaction) => {
console.log("Checking if user is already creating a product");
if (global.productCreationData[interaction.user.id]) return interaction.reply({ content: "You are already creating a product!", ephemeral: true });
global.productCreationData[interaction.user.id] = {
name: interaction.options.getString("name")
};
try {
const productName = global.productCreationData[interaction.user.id].name;
const productResult = await pool.query(`SELECT * FROM products WHERE UPPER(name) = UPPER(?)`, [productName]);
if (productResult.length > 0) {
delete global.productCreationData[interaction.user.id];
return interaction.reply({ content: "A product with this name already exists!", ephemeral: true });
}
console.log("Checking guild hub");
const guildId = interaction.guildId;
const hubResult = await pool.query(`SELECT * FROM hubs WHERE discordGuild = ?`, [guildId]);
if (hubResult.length === 0) {
console.log("No hub found");
delete global.productCreationData[interaction.user.id];
return interaction.reply({ content: "This guild does not have a hub set up!", ephemeral: true });
}
console.log("Hub found");
// Proceed with creation
await interaction.reply({ ephemeral: true, content: "Getting things ready..." });
await interaction.user.send({ content: `Creating product: \`${productName}\`` });
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.productCreationData[interaction.user.id] = {
name: productName,
step: 1,
hub: hubResult[0].id
};
global.dmHandlers[interaction.user.id] = createProdHandler;
} catch (err) {
console.error(err);
delete global.productCreationData[interaction.user.id];
return interaction.editReply({ content: "An error occurred during the product creation process.", ephemeral: true });
}
};
module.exports = { execute }

34
commands/forcelink.js Normal file
View file

@ -0,0 +1,34 @@
const client = global.discord_client
const pool = global.db_pool;
const execute = async (interaction) => {
const robloxId = interaction.options.getNumber("roblox-id");
const discordID = interaction.options.getUser("discord-id")?.id;
if (!discordID) return interaction.reply({ content: "You must provide a Discord User", ephemeral: true });
if (!robloxId) return interaction.reply({ content: "You must provide a Roblox ID", ephemeral: true });
try {
const connection = await pool.getConnection();
const [rows] = await connection.query("SELECT * FROM users WHERE robloxId = ?", [robloxId]);
if (rows.length === 0) {
await connection.query("INSERT INTO users (robloxId, discordId) VALUES (?, ?)", [robloxId, discordID]);
} else {
const user = rows[0];
if (!user.discordId) {
await connection.query("UPDATE users SET discordId = ? WHERE robloxId = ?", [discordID, robloxId]);
} else if (user.discordId !== discordID) {
await connection.query("UPDATE users SET discordId = NULL WHERE discordId = ?", [discordID]);
await connection.query("UPDATE users SET discordId = ? WHERE robloxId = ?", [discordID, robloxId]);
}
}
connection.release();
} catch (error) {
console.error(error);
return interaction.reply({ content: "An error occurred while linking your account", ephemeral: true });
}
return interaction.reply({ content: "Successfully linked your account", ephemeral: true });
}
module.exports = { execute }

View file

@ -16,20 +16,50 @@ const execute = async (interaction) => {
robloxID = row.robloxId;
}
// Get the hub for the guild
const guildID = interaction.guild.id;
const [hub] = await pool.query('SELECT * FROM hubs WHERE discordGuild = ?', [guildID]);
if (!hub) return interaction.reply({ content: "Hub not found for this guild", ephemeral: true });
// Check if the user exists
const [user] = await pool.query('SELECT * FROM users WHERE robloxId = ?', [robloxID]);
if (!user) return interaction.reply({ content: "User not found", ephemeral: true });
const productName = interaction.options.getString("product-name");
// try catch try and find the product based on partial product name, parse everything in uppercase to make things easier
const [product] = await pool.query('SELECT * FROM products WHERE UPPER(name) LIKE ?', [`%${productName.toUpperCase()}%`]);
const [product] = await pool.query('SELECT * FROM products WHERE UPPER(name) LIKE ? AND hubId = ?', [`%${productName.toUpperCase()}%`, hub.id]);
if (!product) return interaction.reply({ content: "Product not found", ephemeral: true });
// Check if the user already owns the product
const [purchase] = await pool.query('SELECT * FROM purchases WHERE robloxId = ? AND productId = ?', [robloxID, product.id]);
if (purchase) return interaction.reply({ content: "User already owns this product", ephemeral: true });
// Insert purchase into database
await pool.query('INSERT INTO purchases (robloxId, productId, hubId) VALUES (?, ?, ?)', [robloxID, product.id, product.hubId]);
try {
// Assuming you have a function to send a message to the user
dscUser = await client.users.fetch(user.discordId);
dscUser.send(`You have been givena copy of ${product.name}!\nUse \`/retrive ${product.name}\` in the Discord server to download it!`);
} catch (error) {
// Do nothing, user has privacy settings enabled
}
if (hub.logChannel != null) {
try {
chan = await client.channels.fetch(hub.logChannel);
chan.send({
embeds: [
{
title: `Product Given`,
color: 0x00ff00,
description: `**Roblox ID:** ${user.robloxId}\n**Discord User:** <@${user.discordId}>\n**Product:** ${product.name}\n**Type:** Give`
}
]
})
} catch (error) {
// Do nothing, channel was deleted
}
}
return interaction.reply({ content: `Gave \`${product.name}\` to ${robloxID}`, ephemeral: true });
};

View file

@ -11,9 +11,12 @@ const execute = async (interaction) => {
// Check if the user exists
if (!robloxID) return interaction.reply({ content: "User not found", ephemeral: true });
const [hub] = await pool.query('SELECT * FROM hubs WHERE discordGuild = ?', [interaction.guild.id]);
if (!hub) return interaction.reply({ content: "Hub not found for this guild", ephemeral: true });
const productName = interaction.options.getString("product-name");
// try catch try and find the product based on partial product name, parse everything in uppercase to make things easier
const [product] = await pool.query('SELECT * FROM products WHERE UPPER(name) LIKE ?', [`%${productName.toUpperCase()}%`]);
const [product] = await pool.query('SELECT * FROM products WHERE UPPER(name) LIKE ? AND hubId = ?', [`%${productName.toUpperCase()}%`, hub.id]);
if (!product) return interaction.reply({ content: "Product not found", ephemeral: true });
// Check if the user already owns the product
const [purchase] = await pool.query('SELECT * FROM purchases WHERE robloxId = ? AND productId = ?', [robloxID, product.id]);
@ -28,7 +31,7 @@ const execute = async (interaction) => {
type: 2,
label: 'Download',
style: 5,
url: `https://cdn.example.com/${product.file}/${authToken}`
url: `${process.env.BASE_URL}/cdn/${product.file}/${authToken}`
}
]
}
@ -47,7 +50,7 @@ const execute = async (interaction) => {
type: 2,
label: 'Download',
style: 5,
url: `https://cdn.example.com/${product.file}/${authToken}`
url: `${process.env.BASE_URL}/cdn/${product.file}/${authToken}`
}
]
}

View file

@ -20,9 +20,14 @@ const execute = async (interaction) => {
const [user] = await pool.query('SELECT * FROM users WHERE robloxId = ?', [robloxID]);
if (!user) return interaction.reply({ content: "User not found", ephemeral: true });
// Get the hub for the guild
const guildID = interaction.guild.id;
const [hub] = await pool.query('SELECT * FROM hubs WHERE discordGuild = ?', [guildID]);
if (!hub) return interaction.reply({ content: "Hub not found for this guild", ephemeral: true });
const productName = interaction.options.getString("product-name");
// try catch try and find the product based on partial product name, parse everything in uppercase to make things easier
const [product] = await pool.query('SELECT * FROM products WHERE UPPER(name) LIKE ?', [`%${productName.toUpperCase()}%`]);
const [product] = await pool.query('SELECT * FROM products WHERE UPPER(name) LIKE ? AND hubId = ?', [`%${productName.toUpperCase()}%`, hub.id]);
if (!product) return interaction.reply({ content: "Product not found", ephemeral: true });
// Check if the user already owns the product
const [purchase] = await pool.query('SELECT * FROM purchases WHERE robloxId = ? AND productId = ?', [robloxID, product.id]);
@ -30,6 +35,23 @@ const execute = async (interaction) => {
// Remove purchase from database
await pool.query('DELETE FROM purchases WHERE robloxId = ? AND productId = ?', [robloxID, product.id]);
if (hub.logChannel != null) {
try {
chan = await client.channels.fetch(hub.logChannel);
chan.send({
embeds: [
{
title: `Product Revoked`,
color: 0xff0000,
description: `**Roblox ID:** ${user.robloxId}\n**Discord User:** <@${user.discordId}>\n**Product:** ${product.name}`
}
]
})
} catch (error) {
// Do nothing, channel was deleted
}
}
return interaction.reply({ content: `Removed \`${product.name}\` from ${robloxID}`, ephemeral: true });
};

View file

@ -9,4 +9,6 @@ const execute = async (interaction) => {
return interaction.reply({ content: "Successfully unlinked your account. Please use the hub game to get a new pairing code!", ephemeral: true });
}
module.exports = { execute }
const noop = () => { };
module.exports = { execute: noop }

61
commands/update.js Normal file
View file

@ -0,0 +1,61 @@
const client = global.discord_client
const pool = global.db_pool;
const updateProductHandler = require('../messageHandlers/update_prod.js');
if (!global.productUpdateData) global.productUpdateData = {};
const execute = async (interaction) => {
if (!interaction.guild) return interaction.reply({ content: "This command must be used in a server!", ephemeral: true });
if (global.productUpdateData[interaction.user.id]) return interaction.reply({ content: "You are already updating a product!", ephemeral: true });
try {
// Get the hub for the guild
const guildID = interaction.guild.id;
const [hub] = await pool.query('SELECT * FROM hubs WHERE discordGuild = ?', [guildID]);
if (!hub) return interaction.reply({ content: "Hub not found for this guild", ephemeral: true });
const productName = interaction.options.getString("name");
const [product] = await pool.query('SELECT * FROM products WHERE UPPER(name) LIKE ? AND hubId = ?', [`%${productName.toUpperCase()}%`, hub.id]);
if (!product) return interaction.reply({ content: "Product not found", ephemeral: true });
// Proceed with creation
await interaction.reply({ ephemeral: true, content: "Getting things ready..." });
await interaction.user.send({ content: `Updating product: \`${product.name}\`` });
switch (interaction.options.getString("field")) {
case "name":
await interaction.user.send({ content: "Please provide a new name for the product. Say `cancel` to exit." });
break;
case "description":
await interaction.user.send({ content: "Please provide a new description for the product. Say `cancel` to exit." });
break;
case "devProductId":
await interaction.user.send({ content: "Please provide a new developer product ID for the product. Say `cancel` to exit." });
break;
case "imageId":
await interaction.user.send({ content: "Please provide a new image ID for the product. Say `cancel` to exit." });
break;
case "file":
await interaction.user.send({ content: "Please provide a new file for the product. Say `cancel` to exit." });
break;
case "stock":
await interaction.user.send({ content: "Please provide a new stock quantity for the product. Say `cancel` to exit. Set `-1` to disable." });
break;
case "category":
await interaction.user.send({ content: "Please provide a new category for the product. Say `cancel` to exit. Set to `~none` to remove." });
break;
default:
return interaction.editReply({ content: "Invalid field provided.", ephemeral: true });
}
interaction.editReply({ ephemeral: true, content: "Check your DMs!" });
global.productUpdateData[interaction.user.id] = {
id: product.id,
type: interaction.options.getString("field")
};
global.dmHandlers[interaction.user.id] = updateProductHandler;
} catch (err) {
console.error(err);
delete global.productCreationData[interaction.user.id];
return interaction.editReply({ content: "An error occurred during the product creation process.", ephemeral: true });
}
};
module.exports = { execute }

View file

@ -9,7 +9,7 @@ const app = express();
global.log = log;
const Discord = require("discord.js")
const client = new Discord.Client({intents: ["Guilds", "DirectMessages"]})
const client = new Discord.Client({intents: ["Guilds", "DirectMessages", "MessageContent", "GuildMembers"]})
const pool = MariaDB.createPool({
host: process.env.DB_HOST,
@ -86,6 +86,15 @@ client.on("interactionCreate", async (interaction) => {
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
}
});
global.dmHandlers = {}
client.on("messageCreate", async (message) => {
if (message.author.bot) return;
if (!message.channel.isDMBased()) return;
if (!global.dmHandlers[message.author.id]) {
return;
}
global.dmHandlers[message.author.id](message);
});
const port = process.env.SERVER_PORT || 3000;

View file

@ -0,0 +1,136 @@
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.productCreationData[user.id];
delete global.dmHandlers[user.id];
user.send('Product creation cancelled.');
}
const execute = async (message) => {
if (!interaction.guild) return interaction.reply({ content: "This command must be used in a server!", ephemeral: true });
switch (global.productCreationData[message.author.id].step) {
case 1: // Description
const description = message.content.trim();
if (description.toLowerCase() === 'cancel') {
cancel(message.author);
return;
}
global.productCreationData[message.author.id].description = description;
global.productCreationData[message.author.id].step = 2;
message.channel.send('Description set. Please provide the dev product ID. Say `cancel` to cancel creation.');
break;
case 2: // Dev product ID
const devProductId = message.content.trim();
if (devProductId.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;
}
global.productCreationData[message.author.id].devProductId = devProductId;
global.productCreationData[message.author.id].step = 3;
message.channel.send('Dev product ID set. Please provide the decal ID. Say `skip` to not set one, or `cancel` to cancel creation.');
break;
case 3: // Image ID
const imageId = message.content.trim();
if (imageId.toLowerCase() === 'cancel') {
cancel(message.author);
return;
}
if (imageId.toLowerCase() === "skip") {
global.productCreationData[message.author.id].imageId = null;
global.productCreationData[message.author.id].step = 4;
message.channel.send('Image ID skipped. Please upload the product file. Say `cancel` to cancel creation.');
return;
}
if (!/^\d{1,15}$/.test(newImageId)) {
message.channel.send('Invalid Image ID. It must be a number between 1 and 15 digits.');
return;
}
global.productCreationData[message.author.id].imageId = imageId;
global.productCreationData[message.author.id].step = 4;
message.channel.send('Image ID set. Please upload the product file.');
break;
case 4: // 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')}`);
const prodId = crypto.randomUUID();
download(attachment.url, file.path, (err) => {
if (err) {
message.channel.send('Failed to download the file.');
cancel(message.author);
return;
}
global.productCreationData[message.author.id].filePath = file.path.split('/').pop();
let fileType = attachment.name.split('.').pop();
global.productCreationData[message.author.id].step = 5;
message.channel.send('File uploaded successfully. Product creation complete.');
pool.query(`INSERT INTO products (id, name, description, devProductId, decalId, file, fileType, hubId) VALUES (?, ?, ?, ?, ?, ?, ?, ?);`, [prodId, global.productCreationData[message.author.id].name, global.productCreationData[message.author.id].description, global.productCreationData[message.author.id].devProductId, global.productCreationData[message.author.id].imageId, global.productCreationData[message.author.id].filePath, fileType, global.productCreationData[message.author.id].hub], (err) => {
if (err) {
console.error(err);
message.channel.send('An error occurred while creating the product.');
cancel(message.author);
return;
}
message.author.send('Product created successfully.');
delete global.productCreationData[message.author.id];
delete global.dmHandlers[message.author.id];
});
});
} else {
message.channel.send('No file attached. Please upload the product file.');
}
break;
default:
message.channel.send('Invalid step.');
cancel(message.author);
break;
}
}
module.exports = execute

View file

@ -0,0 +1,143 @@
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;
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

View file

@ -0,0 +1,3 @@
ALTER TABLE hubs
ADD COLUMN logChannel VARCHAR(255);
-- Uhh

View file

@ -25,6 +25,37 @@ router.get('/getSession', async (req, res) => {
try {
const products = await pool.query('SELECT * FROM products WHERE hubId = ?', [hub.id]);
const purchases = await pool.query('SELECT * FROM purchases WHERE hubId = ?', [hub.id]);
// Generate bestSeller object from purchases. Find what product id has the most sales, then make the object
const purchaseCounts = purchases.reduce((acc, purchase) => {
console.log(acc)
console.log(purchase)
acc[purchase.productId] = (acc[purchase.productId] || 0) + 1;
return acc;
}, {});
const bestSellerProductId = Object.keys(purchaseCounts).reduce((a, b) => purchaseCounts[a] > purchaseCounts[b] ? a : b, null);
const bestSellerProduct = products.find(product => product.id == bestSellerProductId);
const bestSeller = bestSellerProduct ? {
productID: bestSellerProduct.id,
name: bestSellerProduct.name,
description: bestSellerProduct.description,
devproduct_id: (bestSellerProduct.stock == 0) ? 0 : bestSellerProduct.devProductID,
decalID: bestSellerProduct.decalId || "0",
stock: bestSellerProduct.stock == -1 ? false : bestSellerProduct.stock,
onsale: bestSellerProduct.stock == -1 || bestSellerProduct.stock > 0 ? true : false,
category: bestSellerProduct.stock == -1 || bestSellerProduct.stock > 0 ? bestSellerProduct.category : "Out of Stock",
rating: {
currentScore: 0,
maxScore: 0,
amountOfReviews: 0
},
tags: [],
playerData: {
robloxId: pid,
ownsProduct: purchases.some(purchase => purchase.productId == bestSellerProduct.id && purchase.robloxId == pid)
}
} : {};
// generate array of products
const respData = JSON.stringify({
status: 200,
@ -51,15 +82,16 @@ router.get('/getSession', async (req, res) => {
},
},
productsData: {
bestsellerProduct: bestSeller,
allProducts: products.map(product => ({
productID: product.id,
name: product.name,
description: product.description,
devproduct_id: product.devProductID,
devproduct_id: (product.stock == 0) ? 0 : product.devProductID,
decalID: product.decalId || "0",
stock: product.stock > 0 ? product.stock : false,
onsale: true,
category: product.category,
stock: (product.stock == -1 || purchases.some(purchase => purchase.productId == product.id && purchase.robloxId == pid)) ? false : product.stock,
onsale: product.stock == -1 || product.stock > 0 ? true : false,
category: product.stock == -1 || product.stock > 0 ? product.category : "Out of Stock",
rating: {
currentScore: 0,
maxScore: 0,
@ -78,6 +110,7 @@ router.get('/getSession', async (req, res) => {
requestedAt: new Date().toISOString(),
}
}, (_, v) => typeof v === 'bigint' ? v.toString() : v)
return res.status(200).send(respData)
} catch (error) {
log.error(error);

View file

@ -1,6 +1,7 @@
const express = require('express');
const router = express.Router();
const pool = global.db_pool;
const client = global.discord_client;
// Main payment processor
@ -13,7 +14,7 @@ router.post("/external/hub/order/complete", async (req, res) => {
const { robloxID, productID } = req.body;
if (!robloxID || !productID) return res.status(400).json({ status: "400", message: 'Missing Roblox ID or Product ID' });
const [user] = await pool.query('SELECT * FROM users WHERE robloxId = ?', [robloxID]);
const [product] = await pool.query('SELECT * FROM products WHERE id = ?', [productID]);
const [product] = await pool.query('SELECT * FROM products WHERE id = ? AND hubId = ?', [productID, hub.id]);
// Check if user and product exists
if (!user || !product) return res.status(404).json({ status: "404", message: 'User or Product not found' });
@ -24,6 +25,36 @@ router.post("/external/hub/order/complete", async (req, res) => {
// Insert purchase into database
await pool.query('INSERT INTO purchases (robloxId, productId, hubId) VALUES (?, ?, ?)', [robloxID, product.id, hub.id]);
if (product.stock !== -1 && product.stock !== 0) {
await pool.query('UPDATE products SET stock = stock - 1 WHERE id = ?', [product.id]);
}
res.status(200).json({ status: "200", message: 'Purchased product' });
// Handle logging
try {
// Assuming you have a function to send a message to the user
dscUser = await client.users.fetch(user.discordId);
dscUser.send(`You have successfully purchased ${product.name}!\nUse \`/retrive ${product.name}\` in the Discord server to download it!`);
} catch (error) {
// Do nothing, user has privacy settings enabled
}
if (hub.logChannel != null) {
try {
chan = await client.channels.fetch(hub.logChannel);
chan.send({
embeds: [
{
title: `New Purchase`,
color: 0x00ff00,
description: `**Roblox ID:** ${user.robloxId}\n**Discord User:** <@${user.discordId}>\n**Product:** ${product.name}\n**Type:** Normal`
}
]
})
} catch (error) {
// Do nothing, channel was deleted
}
}
});
// Gift validator
@ -48,7 +79,6 @@ router.post("/external/hub/gift/validate", async (req, res) => {
// Check if purchase already exists
if (purchase) return res.status(409).json({ status: "409", message: 'User already owns product', data: {userExists: true, ownsProduct: true} });
// All good!
return res.status(200).json({ status: "200", message: 'User does not own product', data: {userExists: true, ownsProduct: false} });
});
@ -74,6 +104,36 @@ router.post("/external/hub/gift/complete", async (req, res) => {
// Insert purchase into database
await pool.query('INSERT INTO purchases (robloxId, productId, hubId) VALUES (?, ?, ?)', [recipientID, product.id, hub.id]);
if (product.stock !== -1 && product.stock !== 0) {
await pool.query('UPDATE products SET stock = stock - 1 WHERE id = ?', [product.id]);
}
res.status(200).json({ status: "200", message: 'Gifted product to user' });
// Handle logging
try {
// Assuming you have a function to send a message to the user
dscUser = await client.users.fetch(user.discordId);
dscUser.send(`You have successfully purchased ${product.name}!\nUse \`/retrive ${product.name}\` in the Discord server to download it!`);
} catch (error) {
// Do nothing, user has privacy settings enabled
}
if (hub.logChannel != null) {
try {
chan = await client.channels.fetch(hub.logChannel);
chan.send({
embeds: [
{
title: `New Purchase`,
color: 0x00ff00,
description: `**Roblox ID:** ${user.robloxId}\n**Discord User:** <@${user.discordId}>\n**Product:** ${product.name}\n**Type:** Gift`
}
]
})
} catch (error) {
// Do nothing, channel was deleted
}
}
});
module.exports = router;