diff --git a/package-lock.json b/package-lock.json index dc0c447..92b6867 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,8 @@ "version": "1.0.4", "license": "ISC", "dependencies": { - "@prisma/adapter-pg": "^7.8.0", - "@prisma/client": "^7.8.0", + "@prisma/adapter-pg": "^7.9.0", + "@prisma/client": "^7.9.0", "axios": "^1.16.0", "chalk": "^4.1.2", "discord.js": "^14.27.0", @@ -622,24 +622,24 @@ "license": "MIT" }, "node_modules/@prisma/adapter-pg": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.8.0.tgz", - "integrity": "sha512-ygb3UkerK3v8MDpXVgCISdRNDozpxh6+JVJgiIGbSr5KBgz10LLf5ejUskPGoXlsIjxsOu6nuy1JVQr2EKGSlg==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.9.0.tgz", + "integrity": "sha512-kPYuFvNTlqnaFf2UpXBBG3ycTT3PL76uSZtLFBEwDytjMMUW8ZHrsb9cSNIarzdPW5EXWmBOeOq9/MVjMtbWkA==", "license": "Apache-2.0", "dependencies": { - "@prisma/driver-adapter-utils": "7.8.0", + "@prisma/driver-adapter-utils": "7.9.0", "@types/pg": "^8.16.0", "pg": "^8.16.3", "postgres-array": "3.0.4" } }, "node_modules/@prisma/client": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.8.0.tgz", - "integrity": "sha512-HFp3Dawv/3sU3JtlPha90IB+48lS7zHiH4LKZPjmcE8YH5P9DOXGPvo8dqOtO7MqLDd1p2hOWMcFlRT1DMblHw==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.9.0.tgz", + "integrity": "sha512-BTG/mB+WL/1sD2gWwdNc2uuVJjNNBgCDlPFdjco6jJArgbg4IAChtzVeW4debFa/NKBbsGedCjET316sjllWTQ==", "license": "Apache-2.0", "dependencies": { - "@prisma/client-runtime-utils": "7.8.0" + "@prisma/client-runtime-utils": "7.9.0" }, "engines": { "node": "^20.19 || ^22.12 || >=24.0" @@ -658,9 +658,9 @@ } }, "node_modules/@prisma/client-runtime-utils": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.8.0.tgz", - "integrity": "sha512-5NQZztQ0oY/ADFkmd9gPuweH5A1/CCY8YQPorLLO0Mu6a87mY5gsnDkzmFmIHs9NFaLnZojzgddFVN4RpKYrdw==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.9.0.tgz", + "integrity": "sha512-kMVmS4ZEy3xlkca+TfxOEm/ToVVlOS2x1Tc6/wIRf/HfczBqENtSPcKszy4ZpFNzjJ8SRKvlU5V0rrpoFw2KOg==", "license": "Apache-2.0" }, "node_modules/@prisma/config": { @@ -680,6 +680,7 @@ "version": "7.8.0", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.8.0.tgz", "integrity": "sha512-p+QZReysDUqXC+mk17q9a+Y/qzh4c2KYliDK30buYUyfrGeTGSyfmc0AIrJRhZJrLHhRiJa9Au/J72h3C+szvA==", + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/dev": { @@ -709,14 +710,20 @@ } }, "node_modules/@prisma/driver-adapter-utils": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.8.0.tgz", - "integrity": "sha512-/Q13o0ZT0rjc1Xk0Q9KhZYwuq2EW/vSbWUBKfgEKkaCuB/Sg6bqnjmTZqC5cD4d6y1vfFAEwBRzfzoSMIVJ55A==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.9.0.tgz", + "integrity": "sha512-fFXujitfMyjk3kOd1Tbs5FXBm6i2OWwEhaP5lHgkUM99jHpPEQwCWj+z/WKPFq6EDMThE1zGzSlVegtR0Pmu2w==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.8.0" + "@prisma/debug": "7.9.0" } }, + "node_modules/@prisma/driver-adapter-utils/node_modules/@prisma/debug": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.9.0.tgz", + "integrity": "sha512-i0KdVQuKUE6N9NloHs+sUNAk2c9svR3myBndQbA3BoeoArsSpwtNgTdHZL+wBtCLCcdS2OOC/PKhgTe36jkF5A==", + "license": "Apache-2.0" + }, "node_modules/@prisma/engines": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.8.0.tgz", diff --git a/package.json b/package.json index 1f91571..7c1efc2 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "type": "module", "scripts": { "build": "tsc", - "start": "node --env-file=.env dist/index.js", + "start": "node --env-file=.env .", "dev": "tsx --env-file=.env src/index.ts", "prisma:generate": "prisma generate" }, @@ -20,8 +20,8 @@ "typescript": "^7.0.2" }, "dependencies": { - "@prisma/adapter-pg": "^7.8.0", - "@prisma/client": "^7.8.0", + "@prisma/adapter-pg": "^7.9.0", + "@prisma/client": "^7.9.0", "axios": "^1.16.0", "chalk": "^4.1.2", "discord.js": "^14.27.0", diff --git a/src/base/functions/color-resolve.ts b/src/base/functions/color-resolve.ts new file mode 100644 index 0000000..cbf27c9 --- /dev/null +++ b/src/base/functions/color-resolve.ts @@ -0,0 +1,34 @@ +import { ColorResolvable } from "discord.js"; +import { client } from "../../index.js"; +import { SystemColors } from "../../libs/colors.js"; +import { logger } from "../../libs/logger.js"; + +const cachedColors = new Map(); + +export async function resolveColor(): Promise { + if (process.env.CLIENT_ID == '1453802179789066442') return SystemColors.main; // custom branding coming as a future update + + const guildid = process.env.GUILD_ID! || process.env.MASTER_GUILD!; + + if (!guildid) { + return SystemColors.main; + } + + if (cachedColors.has(guildid)) { + return cachedColors.get(guildid)!; + } + + try { + const guild = await client.guilds.fetch(guildid); + const me = await guild.members.fetchMe(); + + const color = me.displayColor; + const resolved: ColorResolvable = color !== 0 ? color : SystemColors.main; + + cachedColors.set(guildid, resolved); + return resolved; + } catch (err) { + logger.error(`Failed to resolve color for guild ${guildid}: ${err}`); + return SystemColors.main; + } +} \ No newline at end of file diff --git a/src/base/functions/embed.ts b/src/base/functions/embed.ts new file mode 100644 index 0000000..57d5e3d --- /dev/null +++ b/src/base/functions/embed.ts @@ -0,0 +1,15 @@ +import { EmbedBuilder, ColorResolvable, ContainerBuilder, resolveColor as resolveColorValue } from "discord.js"; +import { resolveColor } from "./color-resolve.js"; + +export async function Embed(color?: ColorResolvable): Promise { + const embedColor = color ?? await resolveColor(); + + return new EmbedBuilder().setColor(embedColor); +} + +export async function Container(color?: ColorResolvable): Promise { + const embedColor = color ?? await resolveColor(); + const accentColor = resolveColorValue(embedColor); + + return new ContainerBuilder().setAccentColor(accentColor); +} \ No newline at end of file diff --git a/src/commands/booster.ts b/src/commands/booster.ts index 13bd759..cfb5018 100644 --- a/src/commands/booster.ts +++ b/src/commands/booster.ts @@ -8,6 +8,7 @@ import { SeparatorSpacingSize, ButtonBuilder, ButtonStyle, + Colors, } from "discord.js"; import { getBooster, @@ -21,6 +22,7 @@ import { } from "../services/boosterService.js"; import { Command } from "../base/classes/command.js"; import { logger } from "../libs/logger.js"; +import { Container, Embed } from "../base/functions/embed.js"; export default new Command({ info: new SlashCommandBuilder() @@ -106,9 +108,8 @@ export default new Command({ if (!result.data) { if (isBoostingServer && member && member.premiumSince) { const discordBoost = `🟢 Boosting since `; - const embed = new EmbedBuilder() - .setColor(0xff73fa) - .setTitle(`Here is the Booster Information for ${user.id}!`) + const embed = await Embed(); + embed.setTitle(`Here is the Booster Information for ${user.id}!`) .setThumbnail(avatarUrl) .addFields( { @@ -126,8 +127,8 @@ export default new Command({ return; } - const container = new ContainerBuilder() - .setAccentColor(0xe642a4) + const container = await Container(); + container.setAccentColor(0xe642a4) .addTextDisplayComponents( new TextDisplayBuilder().setContent("**Uh oh!**"), new TextDisplayBuilder().setContent( @@ -157,9 +158,8 @@ export default new Command({ .setLabel("Our Support Server") .setURL("https://discord.gg/NUtyKs7hA6"); - const container = new ContainerBuilder() - .setAccentColor(0xe642a4) - .addTextDisplayComponents( + const container = await Container(Colors.Red) + container.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!.`, @@ -184,8 +184,8 @@ export default new Command({ const isActiveBooster = booster.active && booster.boostCounts > 0 && !!premiumSince; - const embed = new EmbedBuilder() - .setColor(booster.active ? 0xf47fff : 0x99aab5) + const embed = await Embed(); + embed.setColor(booster.active ? 0xf47fff : 0x99aab5) .setTitle(`Booster Info: ${user.username}`) .setThumbnail(avatarUrl) .addFields( @@ -253,9 +253,8 @@ export default new Command({ } const boostWord = amount === 1 ? "boost" : "boosts"; - const container = new ContainerBuilder() - .setAccentColor(0xe642a4) - .addTextDisplayComponents( + const container = await Container(); + container.addTextDisplayComponents( new TextDisplayBuilder().setContent(`**Boost successfully added!**`), new TextDisplayBuilder().setContent( `We've successfully added ${amount} ${boostWord} to ${user}'s profile.`, @@ -294,9 +293,8 @@ export default new Command({ getTotalBoosts(discordGuild.id), ]); - const embed = new EmbedBuilder() - .setColor(0xf47fff) - .setTitle("Server Boost Statistics") + const embed = await Embed(); + embed.setTitle("Server Boost Statistics") .addFields( { name: "Current Boosters", diff --git a/src/commands/help.ts b/src/commands/help.ts index a5e989e..262a93e 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -6,6 +6,7 @@ import { } from "discord.js"; import { Command } from "../base/classes/command.js"; import { SystemColors } from "../libs/colors.js"; +import { Container } from "../base/functions/embed.js"; const knowledgebase: Record = { booster: @@ -574,17 +575,16 @@ export default new Command({ const sub = interaction.options.getString("command", true); if (knowledgebase[sub]) { - const container = new ContainerBuilder() - .setAccentColor(SystemColors.main) - .addTextDisplayComponents(txt => txt.setContent( + const container = await Container() + container.addTextDisplayComponents(txt => txt.setContent( `## Command info: \`/${sub}\`\n`+ knowledgebase[sub] )); await interaction.reply({ components: [container], flags: [MessageFlags.IsComponentsV2]}) } else { - const container = new ContainerBuilder() - .setAccentColor(Colors.Red) + const container = await Container(Colors.Red); + container.setAccentColor(Colors.Red) .addTextDisplayComponents(txt => txt.setContent( `## Command not found\n`+ `We couldn't find any resources matching the command \`${sub}\`, please try later.` diff --git a/src/commands/info.ts b/src/commands/info.ts index 3ab827d..9c4ab21 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -8,6 +8,7 @@ import { import axios from "axios"; import { Command } from "../base/classes/command.js"; import packageInfo from "../../package.json" with { type: "json" }; +import { Embed } from "../base/functions/embed.js"; interface GithubRes { login: string, @@ -33,16 +34,15 @@ export default new Command({ const uptimeString = `${days}d ${hours}h ${minutes}m ${seconds}s`; - const embed = new EmbedBuilder() - .setColor(16712630) - .setThumbnail(interaction.client.user.displayAvatarURL({ size: 2048 })) + const embed = await Embed(); + embed.setThumbnail(interaction.client.user.displayAvatarURL({ size: 2048 })) .setTitle("Bot information") .setDescription( "Boostify is a Discord bot designed to help you manage your server boosts." ) .addFields( { - name: "Developers", + name: `Contributors [${info.data.length}]`, value: info.data .map((user) => `[@${user.login}](${user.html_url})`) .join("\n"), diff --git a/src/commands/role.ts b/src/commands/role.ts index 97d13fa..ce30ce1 100644 --- a/src/commands/role.ts +++ b/src/commands/role.ts @@ -12,13 +12,11 @@ import { deleteCustomRole, } from "../services/roleService.js"; import { Command } from "../base/classes/command.js"; +import { Container } from "../base/functions/embed.js"; -const ACCENT = 0xe642a4; - -function componentsV2(lines: string[]) { - const container = new ContainerBuilder() - .setAccentColor(ACCENT) - .addTextDisplayComponents(...lines.map((content) => new TextDisplayBuilder().setContent(content))); +async function componentsV2(lines: string[]) { + const container = await Container(); + container.addTextDisplayComponents(...lines.map((content) => new TextDisplayBuilder().setContent(content))); return { flags: MessageFlags.Ephemeral | MessageFlags.IsComponentsV2, components: [container], @@ -60,7 +58,7 @@ export default new Command({ async execute(interaction) { const guild = interaction.guild; if (!guild) { - await interaction.reply(componentsV2(["This command can only be used in a server."])); + await interaction.reply(await componentsV2(["This command can only be used in a server."])); return; } @@ -69,7 +67,7 @@ export default new Command({ if (!resolved?.success) { await interaction.reply( - componentsV2(["Could not load your guild data for this server."]) + await componentsV2(["Could not load your guild data for this server."]) ); return; } @@ -78,7 +76,7 @@ export default new Command({ if (!nitroBoosting) { await interaction.reply( - componentsV2([ + await componentsV2([ "**Uh oh!**", "You must be boosting this server with Nitro to use this command.", ]) @@ -101,7 +99,7 @@ export default new Command({ case "create": { if (boosterRecord.customRole) { await interaction.reply( - componentsV2([ + await componentsV2([ "You already have a custom role.", "Use `/role edit` to change it.", ]) @@ -115,7 +113,7 @@ export default new Command({ if (!/^#[0-9a-fA-F]{6}$/.test(color)) { await interaction.reply( - componentsV2([ + await componentsV2([ "Invalid color.", "Use a hex color like `#ff0000`.", ]) @@ -128,14 +126,14 @@ export default new Command({ const role = await createCustomRole(guild, member, name, color as ColorResolvable, gradient as ColorResolvable); await interaction.editReply( - componentsV2([`**Custom role created!**`, `${role} is ready to use.`]) + await componentsV2([`**Custom role created!**`, `${role} is ready to use.`]) ); break; } case "edit": { if (!boosterRecord.customRole?.discordRoleId) { await interaction.reply( - componentsV2(["You don't have a custom role yet.", "Use `/role create` first."]) + await componentsV2(["You don't have a custom role yet.", "Use `/role create` first."]) ); return; } @@ -145,7 +143,7 @@ export default new Command({ if (color && !/^#[0-9a-fA-F]{6}$/.test(color as string)) { await interaction.reply( - componentsV2([ + await componentsV2([ "Invalid color.", "Use a hex color like `#ff0000`.", ]) @@ -158,23 +156,23 @@ export default new Command({ const role = await updateCustomRole(guild, interaction.user.id, name, color); if (!role) { await interaction.editReply( - componentsV2(["Couldn't update that role.", "It may have been deleted manually."]) + await componentsV2(["Couldn't update that role.", "It may have been deleted manually."]) ); return; } - await interaction.editReply(componentsV2(["**Custom role updated.**"])); + await interaction.editReply(await componentsV2(["**Custom role updated.**"])); break; } case "delete": { if (!boosterRecord.customRole?.discordRoleId) { - await interaction.reply(componentsV2(["You don't have a custom role to delete."])); + await interaction.reply(await componentsV2(["You don't have a custom role to delete."])); return; } await interaction.deferReply({ flags: MessageFlags.Ephemeral }); await deleteCustomRole(guild, interaction.user.id); - await interaction.editReply(componentsV2(["**Custom role deleted.**"])); + await interaction.editReply(await componentsV2(["**Custom role deleted.**"])); return; } } diff --git a/src/index.ts b/src/index.ts index 25af189..635b9d9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -104,7 +104,7 @@ const eventFiles = fs await loadLoops(); try { - client.login(process.env.BOT_TOKEN); + await client.login(process.env.BOT_TOKEN); } catch (err) { console.log(err); } diff --git a/src/libs/loadCommands.ts b/src/libs/loadCommands.ts index c01229e..effbfb0 100644 --- a/src/libs/loadCommands.ts +++ b/src/libs/loadCommands.ts @@ -47,7 +47,7 @@ export async function loadCommands(): Promise { } if (command?.masterLock) { - if (!process.env.MASTER_GUILD) { + if (!process.env.MASTER_GUILD || !process.env.GUILD_ID) { logger.warn(`Skipping ${file} - missing ${chalk.bold('MASTER_GUILD')} on environmental variables.`); continue; }