From 868f4aa8a20af3c7c2cdbd2ae38782ba6cf3a054 Mon Sep 17 00:00:00 2001 From: breadddevv Date: Fri, 26 Jun 2026 21:24:32 +0100 Subject: [PATCH 1/2] Added booster fixes & db cleanups --- .gitignore | 6 +- package.json | 2 +- src/commands/booster.ts | 154 ++++++++++++++++++++++++++++-------- src/index.ts | 2 + src/libs/loops.ts | 49 ++++++++++++ src/loops/boosterCleanup.ts | 14 ++++ 6 files changed, 193 insertions(+), 34 deletions(-) create mode 100644 src/libs/loops.ts create mode 100644 src/loops/boosterCleanup.ts diff --git a/.gitignore b/.gitignore index 970d855..961a040 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ /node_modules /dist .env -src/generated/ \ No newline at end of file +src/generated/ + +src/commands/*.make.ts +src/events/*.make.ts +# ^ For commands/events that are being made - not ready for commits \ No newline at end of file diff --git a/package.json b/package.json index 275ffd6..dd96fe3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "boostify", - "version": "1.0.1", + "version": "1.0.2", "description": "", "main": "dist/index.js", "type": "module", diff --git a/src/commands/booster.ts b/src/commands/booster.ts index 31e4949..b946070 100644 --- a/src/commands/booster.ts +++ b/src/commands/booster.ts @@ -5,6 +5,9 @@ import { ContainerBuilder, TextDisplayBuilder, MessageFlags, + SeparatorSpacingSize, + ButtonBuilder, + ButtonStyle, } from "discord.js"; import { getBooster, @@ -14,12 +17,14 @@ import { getActiveBoosters, getTotalBoosts, registerBoost, + removeBoost, } from "../services/boosterService.js"; import { Command } from "../base/classes/command.js"; +import { logger } from "../libs/logger.js"; export default new Command({ info: new SlashCommandBuilder() - .setName("booster") + .setName("booster") .setDescription("Booster management commands") .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild) .addSubcommand((sub) => @@ -27,41 +32,44 @@ export default new Command({ .setName("check") .setDescription("Check booster info for a user") .addUserOption((opt) => - opt.setName("user").setDescription("The user to check").setRequired(true) - ) + opt + .setName("user") + .setDescription("The user to check") + .setRequired(true), + ), ) .addSubcommand((sub) => sub .setName("add") .setDescription("Add boost count to a user") .addUserOption((opt) => - opt.setName("user").setDescription("The user").setRequired(true) + opt.setName("user").setDescription("The user").setRequired(true), ) .addIntegerOption((opt) => opt .setName("amount") .setDescription("Amount to add") .setRequired(true) - .setMinValue(1) - ) + .setMinValue(1), + ), ) .addSubcommand((sub) => sub .setName("remove") .setDescription("Remove boost count from a user") .addUserOption((opt) => - opt.setName("user").setDescription("The user").setRequired(true) + opt.setName("user").setDescription("The user").setRequired(true), ) .addIntegerOption((opt) => opt .setName("amount") .setDescription("Amount to remove") .setRequired(true) - .setMinValue(1) - ) + .setMinValue(1), + ), ) .addSubcommand((sub) => - sub.setName("stats").setDescription("View server boost statistics") + sub.setName("stats").setDescription("View server boost statistics"), ), async execute(interaction) { const sub = interaction.options.getSubcommand(); @@ -80,13 +88,18 @@ export default new Command({ if (sub === "check") { const user = interaction.options.getUser("user", true); const result = await getBooster(user.id, interaction); - const member = await discordGuild.members.fetch(user.id).catch(() => null); + const member = await discordGuild.members + .fetch(user.id) + .catch(() => null); const isBoostingServer = member?.premiumSince !== null; const avatarUrl = - member?.displayAvatarURL({ size: 256 }) ?? user.displayAvatarURL({ size: 256 }); + member?.displayAvatarURL({ size: 256 }) ?? + user.displayAvatarURL({ size: 256 }); if (!result?.success) { - await interaction.editReply({ content: "Could not load booster info for this server." }); + await interaction.editReply({ + content: "Could not load booster info for this server.", + }); return; } @@ -98,10 +111,14 @@ export default new Command({ .setTitle(`Here is the Booster Information for ${user.id}!`) .setThumbnail(avatarUrl) .addFields( - { name: "Status", value: "🟢 Active (Nitro Boost)", inline: true }, + { + name: "Status", + value: "🟢 Active (Nitro Boost)", + inline: true, + }, { name: "Discord boost", value: discordBoost, inline: true }, { name: "Custom Role", value: "None", inline: true }, - { name: "User Ping", value: `<@${user.id}>`} + { name: "User Ping", value: `<@${user.id}>` }, ) .setTimestamp(); @@ -113,7 +130,9 @@ export default new Command({ .setAccentColor(0xe642a4) .addTextDisplayComponents( new TextDisplayBuilder().setContent("**Uh oh!**"), - new TextDisplayBuilder().setContent(`It looks like ${user} is not a Booster.`) + new TextDisplayBuilder().setContent( + `It looks like ${user} is not a Booster.`, + ), ); await interaction.editReply({ @@ -125,19 +144,69 @@ export default new Command({ const booster = result.data; + if (booster.boostCounts == 0) { + try { + removeBoost(booster.userId, interaction.guild.id); + } catch (err) { + logger.error( + `An error occured while removing ${booster.userId}'s data: ${err}`, + ); + + const supportButton = new ButtonBuilder() + .setStyle(ButtonStyle.Link) + .setLabel("Our Support Server") + .setURL("https://discord.gg/NUtyKs7hA6"); + + const container = new ContainerBuilder() + .setAccentColor(0xe642a4) + .addTextDisplayComponents( + new TextDisplayBuilder().setContent("**Uh oh!**"), + new TextDisplayBuilder().setContent( + `It looks like we ran into an issue\n-# If this issue is persistent, please consult your console logs if you're using a self-hosted version of Boostify, and create an issue on our [Repository](https://github.com/teamboostify/boostify/issues), or use our support server!.`, + ), + ) + .addSeparatorComponents((sep) => + sep.setDivider(true).setSpacing(SeparatorSpacingSize.Small), + ) + .addActionRowComponents((actrow) => + actrow.addComponents(supportButton), + ); + + await interaction.editReply({ + flags: MessageFlags.Ephemeral | MessageFlags.IsComponentsV2, + components: [container], + }); + return; + } + } + const embed = new EmbedBuilder() .setColor(booster.active ? 0xf47fff : 0x99aab5) .setTitle(`Booster Info: ${user.username}`) .setThumbnail(avatarUrl) .addFields( - { name: "Status", value: booster.active ? "🟢 Active" : "🔴 Inactive", inline: true }, + { + name: "Status", + value: booster.active && booster.boostCounts > 0 ? "🟢 Active" : "🔴 Inactive", + inline: true, + }, { name: "Custom Role", - value: booster.customRole ? `<@&${booster.customRole.discordRoleId}>` : "None", + value: booster.customRole + ? `<@&${booster.customRole.discordRoleId}>` + : "None", + inline: true, + }, + { + name: "Boosting since", + value: ``, + inline: true, + }, + { + name: "Boosts Counts", + value: booster.boostCounts.toString(), inline: true, }, - { name: "Boosting since", value: ``, inline: true}, - { name: "Boosts Counts", value: booster.boostCounts.toString(), inline: true} ) .setTimestamp(); @@ -149,7 +218,9 @@ export default new Command({ const user = interaction.options.getUser("user", true); const amount = interaction.options.getInteger("amount", true); - const targetMember = await discordGuild.members.fetch(user.id).catch(() => null); + const targetMember = await discordGuild.members + .fetch(user.id) + .catch(() => null); if (!targetMember?.premiumSince) { await interaction.editReply({ content: `${user} is not currently boosting this server with Nitro, so boosts can't be added for them.`, @@ -157,11 +228,18 @@ export default new Command({ return; } - await registerBoost(user.id, discordGuild.id, discordGuild.name, discordGuild.iconURL()); + await registerBoost( + user.id, + discordGuild.id, + discordGuild.name, + discordGuild.iconURL(), + ); const updated = await addBoostCount(user.id, discordGuild.id, amount); if (!updated) { - await interaction.editReply({ content: "Failed to update boost count." }); + await interaction.editReply({ + content: "Failed to update boost count.", + }); return; } @@ -169,12 +247,10 @@ export default new Command({ const container = new ContainerBuilder() .setAccentColor(0xe642a4) .addTextDisplayComponents( + new TextDisplayBuilder().setContent(`**Boost successfully added!**`), new TextDisplayBuilder().setContent( - `**Boost successfully added!**` + `We've successfully added ${amount} ${boostWord} to ${user}'s profile.`, ), - new TextDisplayBuilder().setContent( - `We've successfully added ${amount} ${boostWord} to ${user}'s profile.` - ) ); await interaction.editReply({ @@ -190,7 +266,9 @@ export default new Command({ const updated = await removeBoostCount(user.id, discordGuild.id, amount); if (!updated) { - await interaction.editReply({ content: `No booster record found for ${user.tag}.` }); + await interaction.editReply({ + content: `No booster record found for ${user.tag}.`, + }); return; } @@ -211,9 +289,21 @@ export default new Command({ .setColor(0xf47fff) .setTitle("Server Boost Statistics") .addFields( - { name: "Current Boosters", value: String(activeBoosters.length), inline: true }, - { name: "Total Boosts (All Time)", value: String(totalBoosts), inline: true }, - { name: "Unique Boosters (All Time)", value: String(allBoosters.length), inline: true } + { + name: "Current Boosters", + value: String(activeBoosters.length), + inline: true, + }, + { + name: "Total Boosts (All Time)", + value: String(totalBoosts), + inline: true, + }, + { + name: "Unique Boosters (All Time)", + value: String(allBoosters.length), + inline: true, + }, ) .setTimestamp(); @@ -221,4 +311,4 @@ export default new Command({ return; } }, -}) \ No newline at end of file +}); diff --git a/src/index.ts b/src/index.ts index 27338e4..ef2f288 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import fs from "fs"; import { fileURLToPath, pathToFileURL } from "url"; import { logger } from "./libs/logger.js"; import chalk from "chalk"; +import { loadLoops } from "./libs/loops.js"; const __dirname = fileURLToPath(new URL(".", import.meta.url)); @@ -99,6 +100,7 @@ const eventFiles = fs } await loadCommands(); + await loadLoops(); try { client.login(process.env.BOT_TOKEN); diff --git a/src/libs/loops.ts b/src/libs/loops.ts new file mode 100644 index 0000000..294f293 --- /dev/null +++ b/src/libs/loops.ts @@ -0,0 +1,49 @@ +import * as fs from "fs"; +import * as path from "path"; +import { fileURLToPath, pathToFileURL } from "url"; +import { logger } from "./logger.js"; + +const __dirname = fileURLToPath(new URL(".", import.meta.url)); + +export async function loadLoops() { + const loops = path.join(__dirname, "..", "loops"); + if (!fs.existsSync(loops)) { + logger.warn( + "Loops folder wasn't found — this may cause the system to be slow, and not lightweight!", + ); + return; + } + + const loopFiles = fs + .readdirSync(loops) + .filter((file) => file.endsWith(".ts") || file.endsWith(".js")); + + if (loopFiles.length === 0) { + logger.warn("No command files found — nothing to register."); + return; + } + + for (const file of loopFiles) { + const filePath = path.join(loops, file); + const fileInfo = (await import(pathToFileURL(filePath).href)).default; + console.log(file); + + if (!fileInfo?.runEvery || !fileInfo?.execute) { + logger.warn(`Skipping ${file} - missing runEvery or execute`); + continue; + } + + const run = async () => { + try { + await fileInfo.execute(); + } catch (err) { + logger.error(`Loop "${file}" failed: ${err}`); + } + }; + + run(); + setInterval(run, fileInfo.runEvery * 1000); + + logger.success(`Loaded loop "${file}"`); + } +} diff --git a/src/loops/boosterCleanup.ts b/src/loops/boosterCleanup.ts new file mode 100644 index 0000000..693f88b --- /dev/null +++ b/src/loops/boosterCleanup.ts @@ -0,0 +1,14 @@ +import { prisma } from "../libs/database.js" + +export default { + runEvery: 60, // seconds + async execute() { + await prisma.booster.deleteMany({ + where: { + boostCounts: { + lt: 1 // delete all boosters' data in which boost counts is 0 + } + } + }) + } +} \ No newline at end of file From 8b2b74a0e87ad4b8d6a10277b83ad278e02e773a Mon Sep 17 00:00:00 2001 From: breadddevv Date: Fri, 26 Jun 2026 21:40:00 +0100 Subject: [PATCH 2/2] Fixes --- .gitignore | 4 ++-- src/commands/booster.ts | 19 ++++++++++++++----- src/libs/loops.ts | 20 ++++++++++++++++---- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 961a040..0c0db4d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,6 @@ .env src/generated/ -src/commands/*.make.ts -src/events/*.make.ts +src/commands/*.make.ts +src/events/*.make.ts # ^ For commands/events that are being made - not ready for commits \ No newline at end of file diff --git a/src/commands/booster.ts b/src/commands/booster.ts index b946070..13bd759 100644 --- a/src/commands/booster.ts +++ b/src/commands/booster.ts @@ -146,7 +146,7 @@ export default new Command({ if (booster.boostCounts == 0) { try { - removeBoost(booster.userId, interaction.guild.id); + await removeBoost(booster.userId, interaction.guild.id); } catch (err) { logger.error( `An error occured while removing ${booster.userId}'s data: ${err}`, @@ -180,6 +180,10 @@ export default new Command({ } } + const premiumSince = member?.premiumSince; + const isActiveBooster = + booster.active && booster.boostCounts > 0 && !!premiumSince; + const embed = new EmbedBuilder() .setColor(booster.active ? 0xf47fff : 0x99aab5) .setTitle(`Booster Info: ${user.username}`) @@ -187,7 +191,7 @@ export default new Command({ .addFields( { name: "Status", - value: booster.active && booster.boostCounts > 0 ? "🟢 Active" : "🔴 Inactive", + value: isActiveBooster ? "🟢 Active" : "🔴 Inactive", inline: true, }, { @@ -199,7 +203,9 @@ export default new Command({ }, { name: "Boosting since", - value: ``, + value: premiumSince + ? `` + : "Not currently boosting", inline: true, }, { @@ -228,14 +234,17 @@ export default new Command({ return; } - await registerBoost( + const registered = await registerBoost( user.id, discordGuild.id, discordGuild.name, discordGuild.iconURL(), ); - const updated = await addBoostCount(user.id, discordGuild.id, amount); + const updated = + amount > 1 + ? await addBoostCount(user.id, discordGuild.id, amount - 1) + : registered; if (!updated) { await interaction.editReply({ content: "Failed to update boost count.", diff --git a/src/libs/loops.ts b/src/libs/loops.ts index 294f293..7b1bf2b 100644 --- a/src/libs/loops.ts +++ b/src/libs/loops.ts @@ -25,8 +25,13 @@ export async function loadLoops() { for (const file of loopFiles) { const filePath = path.join(loops, file); - const fileInfo = (await import(pathToFileURL(filePath).href)).default; - console.log(file); + let fileInfo; + try { + fileInfo = (await import(pathToFileURL(filePath).href)).default; + } catch (err) { + logger.error(`Failed to load loop "${file}": ${err}`); + continue; + } if (!fileInfo?.runEvery || !fileInfo?.execute) { logger.warn(`Skipping ${file} - missing runEvery or execute`); @@ -34,15 +39,22 @@ export async function loadLoops() { } const run = async () => { + if (running) return; + running = true; try { await fileInfo.execute(); } catch (err) { logger.error(`Loop "${file}" failed: ${err}`); + } finally { + running = false; } }; - run(); - setInterval(run, fileInfo.runEvery * 1000); + let running = false; + void run(); + setInterval(() => { + void run(); + }, fileInfo.runEvery * 1000); logger.success(`Loaded loop "${file}"`); }