Skip to content

Commit 117b03c

Browse files
authored
Merge pull request #8 from teamboostify/revamp/codebase
Revamped the codebase (some bits)
2 parents 6755172 + cfe3348 commit 117b03c

12 files changed

Lines changed: 248 additions & 111 deletions

File tree

src/base/classes/command.ts

Lines changed: 76 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import {
22
ChatInputCommandInteraction,
3+
Colors,
4+
ContainerBuilder,
35
MessageFlags,
46
PermissionFlagsBits,
57
SlashCommandBuilder,
@@ -32,7 +34,7 @@ export class Command {
3234
requiredPermissions: (keyof typeof PermissionFlagsBits)[];
3335
cooldown: number;
3436
masterLock: boolean
35-
private _cooldowns: Map<string, number>;
37+
private readonly _cooldowns: Map<string, number>;
3638

3739
constructor({ info, execute, guildOnly = false, ownerOnly = false, requiredPermissions = [], cooldown = 0, masterLock = false }: CommandOptions) {
3840
this.data = info;
@@ -50,50 +52,12 @@ export class Command {
5052
}
5153

5254
async run(interaction: ChatInputCommandInteraction, ownerId?: string): Promise<void> {
53-
if (this.guildOnly && !interaction.guild) {
54-
await interaction.reply({ content: 'This command is limited to servers', flags: MessageFlags.Ephemeral });
55-
return;
56-
}
57-
58-
if (this.ownerOnly && interaction.user.id !== ownerId) {
59-
await interaction.reply({ content: 'Only the owner can run this command', flags: MessageFlags.Ephemeral });
55+
if (!await this.validateCommand(interaction, ownerId)) {
6056
return;
6157
}
6258

63-
if (this.masterLock && interaction.guild) {
64-
if (interaction.guild.id != process.env.MASTER_GUILD) {
65-
await interaction.reply('This command cannot be ran in this server.')
66-
}
67-
}
68-
69-
if (this.requiredPermissions.length && interaction.guild) {
70-
const missing = this.requiredPermissions.filter(
71-
p => !interaction.memberPermissions?.has(PermissionFlagsBits[p])
72-
);
73-
if (missing.length) {
74-
await interaction.reply({
75-
content: `You're lacking of the necessary permissions: \`${missing.join(', ')}\``,
76-
flags: MessageFlags.Ephemeral,
77-
});
78-
return;
79-
}
80-
}
81-
82-
if (this.cooldown > 0) {
83-
const now = Date.now();
84-
const expiry = this._cooldowns.get(interaction.user.id);
85-
if (expiry && now < expiry) {
86-
const unixExpiry = Math.floor(expiry / 1000);
87-
await interaction.reply({
88-
content: `You can use \`/${this.name}\` again, please wait <t:${unixExpiry}:R>.`,
89-
flags: MessageFlags.Ephemeral,
90-
});
91-
return;
92-
}
93-
this._cooldowns.set(interaction.user.id, now + this.cooldown * 1000);
94-
setTimeout(() => this._cooldowns.delete(interaction.user.id), this.cooldown * 1000);
95-
}
96-
59+
await this.applyCooldown(interaction);
60+
9761
try {
9862
await this.execute(interaction);
9963
} catch (err) {
@@ -106,4 +70,74 @@ export class Command {
10670
}
10771
}
10872
}
73+
74+
private async validateCommand(interaction: ChatInputCommandInteraction, ownerId?: string): Promise<boolean> {
75+
if (this.guildOnly && !interaction.guild) {
76+
await interaction.reply({ content: 'This command is limited to servers', flags: MessageFlags.Ephemeral });
77+
return false;
78+
}
79+
80+
if (this.ownerOnly && interaction.user.id !== ownerId) {
81+
const container = new ContainerBuilder().setAccentColor(Colors.Red)
82+
.addTextDisplayComponents((txt) => txt.setContent([
83+
"## Owner-locked command",
84+
"This command can only be used by the owner of this server."
85+
].join("\n")))
86+
await interaction.reply({ components: [container], flags: [MessageFlags.Ephemeral, MessageFlags.IsComponentsV2]})
87+
return false;
88+
}
89+
90+
if (this.masterLock && interaction.guild?.id !== process.env.MASTER_GUILD) {
91+
await interaction.reply('This command cannot be ran in this server.');
92+
return false;
93+
}
94+
95+
if (!await this.validatePermissions(interaction)) {
96+
return false;
97+
}
98+
99+
return true;
100+
}
101+
102+
private async validatePermissions(interaction: ChatInputCommandInteraction): Promise<boolean> {
103+
if (!this.requiredPermissions.length || !interaction.guild) {
104+
return true;
105+
}
106+
107+
const missing = this.requiredPermissions.filter(
108+
p => !interaction.memberPermissions?.has(PermissionFlagsBits[p])
109+
);
110+
111+
if (missing.length) {
112+
await interaction.reply({
113+
content: `You're lacking of the necessary permissions: \`${missing.join(', ')}\``,
114+
flags: MessageFlags.Ephemeral,
115+
});
116+
return false;
117+
}
118+
119+
return true;
120+
}
121+
122+
private async applyCooldown(interaction: ChatInputCommandInteraction): Promise<void> {
123+
if (this.cooldown <= 0) {
124+
return;
125+
}
126+
127+
const now = Date.now();
128+
const expiry = this._cooldowns.get(interaction.user.id);
129+
130+
if (expiry && now < expiry) {
131+
const unixExpiry = Math.floor(expiry / 1000);
132+
await interaction.reply({
133+
content: `You can use \`/${this.name}\` again, please wait <t:${unixExpiry}:R>.`,
134+
flags: MessageFlags.Ephemeral,
135+
});
136+
return;
137+
}
138+
139+
this._cooldowns.set(interaction.user.id, now + this.cooldown * 1000);
140+
setTimeout(() => this._cooldowns.delete(interaction.user.id), this.cooldown * 1000);
141+
await this.execute(interaction);
142+
}
109143
}
Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
import {
22
SlashCommandBuilder,
3-
ChatInputCommandInteraction,
43
EmbedBuilder,
5-
ActionRowBuilder,
6-
ButtonBuilder,
7-
ButtonStyle,
84
} from "discord.js";
95
import { Command } from "../base/classes/command.js";
106

src/commands/info.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
SlashCommandBuilder,
3-
ChatInputCommandInteraction,
43
EmbedBuilder,
54
ButtonBuilder,
65
ButtonStyle,

src/commands/role.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
SlashCommandBuilder,
3-
ChatInputCommandInteraction,
43
ColorResolvable,
54
ContainerBuilder,
65
TextDisplayBuilder,

src/commands/settings.ts

Whitespace-only changes.

src/commands/setup.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ import {
44
ChannelType,
55
LabelBuilder,
66
MessageFlags,
7+
ContainerBuilder,
8+
Colors,
79
} from "discord.js";
810
import { Command } from "../base/classes/command.js";
911
import { prisma } from "../libs/database.js";
12+
import { SystemColors } from "../libs/colors.js";
1013

1114
export default new Command({
1215
info: new SlashCommandBuilder()
@@ -22,7 +25,12 @@ export default new Command({
2225
});
2326

2427
if (serversetup) {
25-
await interaction.reply({ content: "This server has been setted up.", flags: [MessageFlags.Ephemeral]})
28+
const container = new ContainerBuilder().setAccentColor(SystemColors.main)
29+
.addTextDisplayComponents((txt) => txt.setContent([
30+
"## This server is already set up",
31+
"This server was already configured, if you want to change a setting please run the `/settings` command."
32+
].join("\n")))
33+
await interaction.reply({ components: [container], flags: [MessageFlags.Ephemeral, MessageFlags.IsComponentsV2]})
2634
return
2735
}
2836

src/events/clientReady.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ActivityType, Client, Events } from "discord.js";
1+
import { Client, Events } from "discord.js";
22
import { logger } from "../libs/logger.js";
33

44
export default {

src/events/guildMemberUpdate.ts

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
import "../libs/loadVariables.js"
2-
import { Client, EmbedBuilder, Events, GuildMember, TextChannel } from "discord.js";
1+
import "../libs/loadVariables.js";
2+
import {
3+
Client,
4+
EmbedBuilder,
5+
Events,
6+
GuildMember,
7+
TextChannel,
8+
} from "discord.js";
39
import {
410
registerBoost,
511
removeBoost,
@@ -15,12 +21,19 @@ import { prisma } from "../libs/database.js";
1521

1622
export default {
1723
name: Events.GuildMemberUpdate,
18-
async execute(_client: Client, oldMember: GuildMember, newMember: GuildMember) {
24+
async execute(
25+
_client: Client,
26+
oldMember: GuildMember,
27+
newMember: GuildMember,
28+
) {
1929
try {
2030
if (oldMember.partial) oldMember = await oldMember.fetch();
2131
if (newMember.partial) newMember = await newMember.fetch();
2232
} catch (error) {
23-
logger.error(`Failed to fetch partials for user ${oldMember.user.username}`);
33+
const username =
34+
oldMember?.user?.username ?? oldMember?.id ?? "Unknown user";
35+
logger.error(`Failed to fetch partials for user ${username}`);
36+
return;
2437
}
2538

2639
const wasBoostingBefore = oldMember.premiumSince !== null;
@@ -43,7 +56,9 @@ async function onBoostStart(member: GuildMember): Promise<void> {
4356

4457
const settings = await getGuildSettings(guild.id);
4558
if (!settings) {
46-
logger.error(`No guild settings found for guild ${guild.id} — run /setup first.`);
59+
logger.error(
60+
`No guild settings found for guild ${guild.id} — run /setup first.`,
61+
);
4762
return;
4863
}
4964

@@ -58,43 +73,45 @@ async function onBoostStart(member: GuildMember): Promise<void> {
5873
await clearPendingCustomRoleDeletion(record.id);
5974
await assignLevelRoles(member, record.boostCounts ?? 1);
6075

61-
const greetChannel = guild.channels.cache.get(settings.greetChannelId) as TextChannel | undefined;
76+
const greetChannel = guild.channels.cache.get(settings.greetChannelId) as
77+
| TextChannel
78+
| undefined;
6279
if (greetChannel) {
6380
const embed = new EmbedBuilder()
6481
.setColor(0xf47fff)
6582
.setTitle("New Server Boost! 🎉")
6683
.setDescription(`${member} has boosted the server!`)
6784
.addFields(
68-
{
69-
name: "Total Boosts",
70-
value: String(record.boostCounts ?? 1),
71-
inline: true
72-
},
73-
{ name: "Member",
74-
value: member.user.tag,
75-
inline: true
85+
{
86+
name: "Total Boosts",
87+
value: String(record.boostCounts ?? 1),
88+
inline: true,
7689
},
90+
{ name: "Member", value: member.user.tag, inline: true },
7791
)
7892
.setThumbnail(member.displayAvatarURL({ size: 512 }))
93+
.setFooter({ text: "Thank youuu!"})
7994
.setTimestamp();
8095
await greetChannel.send({ embeds: [embed] });
8196
}
8297

83-
const logChannel = guild.channels.cache.get(settings.logChannelId) as TextChannel | undefined;
98+
const logChannel = guild.channels.cache.get(settings.logChannelId) as
99+
| TextChannel
100+
| undefined;
84101
if (logChannel) {
85102
const logEmbed = new EmbedBuilder()
86103
.setColor(0x57f287)
87104
.setTitle("Boost Started")
88105
.addFields(
89-
{
90-
name: "User",
91-
value: `${member.user.tag} (${member.id})`,
92-
inline: false
106+
{
107+
name: "User",
108+
value: `${member.user.tag} (${member.id})`,
109+
inline: false,
93110
},
94-
{
95-
name: "Total Boost Count",
96-
value: String(record.boostCounts ?? 1),
97-
inline: true
111+
{
112+
name: "Total Boost Count",
113+
value: String(record.boostCounts ?? 1),
114+
inline: true,
98115
},
99116
)
100117
.setThumbnail(member.displayAvatarURL({ size: 512 }))
@@ -103,7 +120,9 @@ async function onBoostStart(member: GuildMember): Promise<void> {
103120
}
104121

105122
try {
106-
await member.send(`Thank you for boosting the server! You now have access to booster perks.`);
123+
await member.send(
124+
`Thank you for boosting the server! You now have access to booster perks.`,
125+
);
107126
} catch {}
108127
}
109128

@@ -112,7 +131,9 @@ async function onBoostEnd(member: GuildMember): Promise<void> {
112131

113132
const settings = await getGuildSettings(guild.id);
114133
if (!settings) {
115-
logger.error(`No guild settings found for guild ${guild.id} — run /setup first.`);
134+
logger.error(
135+
`No guild settings found for guild ${guild.id} — run /setup first.`,
136+
);
116137
return;
117138
}
118139

@@ -122,25 +143,27 @@ async function onBoostEnd(member: GuildMember): Promise<void> {
122143
await removeAllLevelRoles(member);
123144
await scheduleCustomRoleDeletionAfterGrace(member.id, guild.id);
124145

125-
const logChannel = guild.channels.cache.get(settings.logChannelId) as TextChannel | undefined;
146+
const logChannel = guild.channels.cache.get(settings.logChannelId) as
147+
| TextChannel
148+
| undefined;
126149
if (logChannel) {
127150
const logEmbed = new EmbedBuilder()
128151
.setColor(0xed4245)
129152
.setTitle("Boost Ended")
130153
.addFields(
131-
{
132-
name: "User",
133-
value: `${member.user.tag} (${member.id})`,
134-
inline: false
154+
{
155+
name: "User",
156+
value: `${member.user.tag} (${member.id})`,
157+
inline: false,
135158
},
136-
{
137-
name: "Historical Boost Count",
138-
value: String(result.boostCounts ?? 0),
139-
inline: true
159+
{
160+
name: "Historical Boost Count",
161+
value: String(result.boostCounts ?? 0),
162+
inline: true,
140163
},
141164
)
142165
.setThumbnail(member.displayAvatarURL({ size: 512 }))
143166
.setTimestamp();
144167
await logChannel.send({ embeds: [logEmbed] });
145168
}
146-
}
169+
}

src/events/roleCleanup.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Client, Events } from "discord.js";
2-
import { logger } from "../libs/logger.js";
32
import { processDueCustomRoleDeletions } from "../services/customRoleCleanup.js";
43

54
const CUSTOM_ROLE_CLEANUP_MS = 60 * 60 * 1000;

src/libs/colors.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export enum SystemColors {
2+
main = 0xFF48B6
3+
}

0 commit comments

Comments
 (0)