chore: improve safety of commands

This commit is contained in:
Ahmad 2025-04-17 01:05:10 -04:00
parent 83bbf7b098
commit 7c2a99daf5
No known key found for this signature in database
GPG key ID: 8FD8A93530D182BF
28 changed files with 329 additions and 235 deletions

View file

@ -2,6 +2,7 @@
"token": "DISCORD_BOT_TOKEN",
"clientId": "DISCORD_BOT_ID",
"guildId": "DISCORD_SERVER_ID",
"serverInvite": "DISCORD_SERVER_INVITE_LINK",
"database": {
"dbConnectionString": "POSTGRESQL_CONNECTION_STRING",
"maxRetryAttempts": "MAX_RETRY_ATTEMPTS",

View file

@ -148,8 +148,9 @@ const command = {
),
async execute(interaction: ChatInputCommandInteraction) {
await interaction.deferReply();
if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.deferReply();
const subcommand = interaction.options.getSubcommand();
switch (subcommand) {

View file

@ -33,8 +33,9 @@ const command: SubcommandCommand = {
),
execute: async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.deferReply();
const subcommand = interaction.options.getSubcommand();
if (subcommand === 'status') {
@ -82,33 +83,30 @@ const command: SubcommandCommand = {
});
}
await interaction.reply({ embeds: [embed] });
await interaction.editReply({ embeds: [embed] });
} else if (subcommand === 'setcount') {
if (
!interaction.memberPermissions?.has(
PermissionsBitField.Flags.Administrator,
)
) {
await interaction.reply({
await interaction.editReply({
content: 'You need administrator permissions to use this command.',
flags: ['Ephemeral'],
});
return;
}
const count = interaction.options.getInteger('count');
if (count === null) {
await interaction.reply({
await interaction.editReply({
content: 'Invalid count specified.',
flags: ['Ephemeral'],
});
return;
}
await setCount(count);
await interaction.reply({
await interaction.editReply({
content: `Count has been set to **${count}**. The next number should be **${count + 1}**.`,
flags: ['Ephemeral'],
});
}
},

View file

@ -74,12 +74,11 @@ const command: SubcommandCommand = {
),
execute: async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.deferReply({
flags: ['Ephemeral'],
});
await interaction.editReply('Processing...');
const config = loadConfig();
const subcommand = interaction.options.getSubcommand();
@ -100,7 +99,7 @@ const command: SubcommandCommand = {
});
if (!isAdmin) {
const approvalChannel = interaction.guild?.channels.cache.get(
const approvalChannel = interaction.guild.channels.cache.get(
config.channels.factApproval,
);

View file

@ -18,6 +18,7 @@ import {
builder,
} from '@/util/giveaways/giveawayManager.js';
import { createPaginationButtons } from '@/util/helpers.js';
import { loadConfig } from '@/util/configLoader';
const command: SubcommandCommand = {
data: new SlashCommandBuilder()
@ -53,16 +54,30 @@ const command: SubcommandCommand = {
),
execute: async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (!interaction.isChatInputCommand() || !interaction.guild) return;
const config = loadConfig();
const communityManagerRoleId = config.roles.staffRoles.find(
(role) => role.name === 'Community Manager',
)?.roleId;
if (!communityManagerRoleId) {
await interaction.reply({
content:
'Community Manager role not found in the configuration. Please contact a server admin.',
flags: ['Ephemeral'],
});
return;
}
if (
!interaction.memberPermissions?.has(
PermissionsBitField.Flags.ModerateMembers,
)
!interaction.guild.members.cache
.find((member) => member.id === interaction.user.id)
?.roles.cache.has(communityManagerRoleId)
) {
await interaction.reply({
content: 'You do not have permission to manage giveaways.',
ephemeral: true,
flags: ['Ephemeral'],
});
return;
}
@ -152,7 +167,7 @@ async function handleListGiveaways(interaction: ChatInputCommandInteraction) {
if (i.user.id !== interaction.user.id) {
await i.reply({
content: 'You cannot use these buttons.',
ephemeral: true,
flags: ['Ephemeral'],
});
return;
}

View file

@ -22,13 +22,12 @@ const command: OptionsCommand = {
.setRequired(false),
),
execute: async (interaction) => {
if (!interaction.guild) return;
if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.deferReply();
try {
const usersPerPage =
(interaction.options.get('limit')?.value as number) || 10;
const usersPerPage = interaction.options.getInteger('limit') || 10;
const allUsers = await getLevelLeaderboard(100);

View file

@ -15,18 +15,16 @@ const command: OptionsCommand = {
.setRequired(false),
),
execute: async (interaction) => {
const member = await interaction.guild?.members.fetch(
(interaction.options.get('user')?.value as string) || interaction.user.id,
);
if (!member) {
await interaction.reply('User not found in this server.');
return;
}
if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.deferReply();
try {
const member = await interaction.guild.members.fetch(
(interaction.options.get('user')?.value as string) ||
interaction.user.id,
);
const userData = await getUserLevel(member.id);
const rankCard = await generateRankCard(member, userData);

View file

@ -3,6 +3,7 @@ import { PermissionsBitField, SlashCommandBuilder } from 'discord.js';
import { updateMember, updateMemberModerationHistory } from '@/db/db.js';
import { parseDuration, scheduleUnban } from '@/util/helpers.js';
import { OptionsCommand } from '@/types/CommandTypes.js';
import { loadConfig } from '@/util/configLoader.js';
import logAction from '@/util/logging/logAction.js';
const command: OptionsCommand = {
@ -30,40 +31,62 @@ const command: OptionsCommand = {
.setRequired(false),
),
execute: async (interaction) => {
const moderator = await interaction.guild?.members.fetch(
interaction.user.id,
);
const member = await interaction.guild?.members.fetch(
interaction.options.get('member')!.value as string,
);
const reason = interaction.options.get('reason')?.value as string;
const banDuration = interaction.options.get('duration')?.value as
| string
| undefined;
if (!interaction.isChatInputCommand() || !interaction.guild) return;
if (
!interaction.memberPermissions?.has(
PermissionsBitField.Flags.BanMembers,
) ||
moderator!.roles.highest.position <= member!.roles.highest.position ||
!member?.bannable
) {
await interaction.reply({
content:
'You do not have permission to ban members or this member cannot be banned.',
flags: ['Ephemeral'],
});
return;
}
await interaction.deferReply({ flags: ['Ephemeral'] });
try {
await member.user.send(
banDuration
? `You have been banned from ${interaction.guild!.name} for ${banDuration}. Reason: ${reason}. You can join back at ${new Date(
Date.now() + parseDuration(banDuration),
).toUTCString()} using the link below:\nhttps://discord.gg/KRTGjxx7gY`
: `You been indefinitely banned from ${interaction.guild!.name}. Reason: ${reason}.`,
const moderator = await interaction.guild.members.fetch(
interaction.user.id,
);
const member = await interaction.guild.members.fetch(
interaction.options.get('member')!.value as string,
);
const reason = interaction.options.get('reason')?.value as string;
const banDuration = interaction.options.get('duration')?.value as
| string
| undefined;
if (
!interaction.memberPermissions?.has(
PermissionsBitField.Flags.BanMembers,
)
) {
await interaction.reply({
content: 'You do not have permission to ban members.',
flags: ['Ephemeral'],
});
return;
}
if (moderator.roles.highest.position <= member.roles.highest.position) {
await interaction.reply({
content:
'You cannot ban a member with equal or higher role than yours.',
flags: ['Ephemeral'],
});
return;
}
if (!member.bannable) {
await interaction.reply({
content: 'I do not have permission to ban this member.',
flags: ['Ephemeral'],
});
return;
}
try {
await member.user.send(
banDuration
? `You have been banned from ${interaction.guild!.name} for ${banDuration}. Reason: ${reason}. You can join back at ${new Date(
Date.now() + parseDuration(banDuration),
).toUTCString()} using the link below:\n${interaction.guild.vanityURLCode ?? loadConfig().serverInvite}`
: `You been indefinitely banned from ${interaction.guild!.name}. Reason: ${reason}.`,
);
} catch (error) {
console.error('Failed to send DM:', error);
}
await member.ban({ reason });
if (banDuration) {
@ -97,20 +120,19 @@ const command: OptionsCommand = {
guild: interaction.guild!,
action: 'ban',
target: member,
moderator: moderator!,
moderator,
reason,
});
await interaction.reply({
await interaction.editReply({
content: banDuration
? `<@${member.id}> has been banned for ${banDuration}. Reason: ${reason}`
: `<@${member.id}> has been indefinitely banned. Reason: ${reason}`,
});
} catch (error) {
console.error('Ban command error:', error);
await interaction.reply({
await interaction.editReply({
content: 'Unable to ban member.',
flags: ['Ephemeral'],
});
}
},

View file

@ -2,6 +2,7 @@ import { PermissionsBitField, SlashCommandBuilder } from 'discord.js';
import { updateMemberModerationHistory } from '@/db/db.js';
import { OptionsCommand } from '@/types/CommandTypes.js';
import { loadConfig } from '@/util/configLoader.js';
import logAction from '@/util/logging/logAction.js';
const command: OptionsCommand = {
@ -21,33 +22,48 @@ const command: OptionsCommand = {
.setRequired(true),
),
execute: async (interaction) => {
const moderator = await interaction.guild?.members.fetch(
interaction.user.id,
);
const member = await interaction.guild?.members.fetch(
interaction.options.get('member')!.value as string,
);
const reason = interaction.options.get('reason')?.value as string;
if (!interaction.isChatInputCommand() || !interaction.guild) return;
if (
!interaction.memberPermissions?.has(
PermissionsBitField.Flags.KickMembers,
) ||
moderator!.roles.highest.position <= member!.roles.highest.position ||
!member?.kickable
) {
await interaction.reply({
content:
'You do not have permission to kick members or this member cannot be kicked.',
flags: ['Ephemeral'],
});
return;
}
await interaction.deferReply({ flags: ['Ephemeral'] });
try {
const moderator = await interaction.guild.members.fetch(
interaction.user.id,
);
const member = await interaction.guild.members.fetch(
interaction.options.get('member')!.value as string,
);
const reason = interaction.options.get('reason')?.value as string;
if (
!interaction.memberPermissions?.has(
PermissionsBitField.Flags.KickMembers,
)
) {
await interaction.editReply({
content: 'You do not have permission to kick members.',
});
return;
}
if (moderator!.roles.highest.position <= member.roles.highest.position) {
await interaction.editReply({
content:
'You cannot kick a member with equal or higher role than yours.',
});
return;
}
if (!member.kickable) {
await interaction.editReply({
content: 'I do not have permission to kick this member.',
});
return;
}
try {
await member.user.send(
`You have been kicked from ${interaction.guild!.name}. Reason: ${reason}. You can join back at: \nhttps://discord.gg/KRTGjxx7gY`,
`You have been kicked from ${interaction.guild!.name}. Reason: ${reason}. You can join back at: \n${interaction.guild.vanityURLCode ?? loadConfig().serverInvite}`,
);
} catch (error) {
console.error('Failed to send DM to kicked user:', error);
@ -68,18 +84,17 @@ const command: OptionsCommand = {
guild: interaction.guild!,
action: 'kick',
target: member,
moderator: moderator!,
moderator,
reason,
});
await interaction.reply({
await interaction.editReply({
content: `<@${member.id}> has been kicked. Reason: ${reason}`,
});
} catch (error) {
console.error('Kick command error:', error);
await interaction.reply({
await interaction.editReply({
content: 'Unable to kick member.',
flags: ['Ephemeral'],
});
}
},

View file

@ -30,38 +30,52 @@ const command: OptionsCommand = {
.setRequired(true),
),
execute: async (interaction) => {
const moderator = await interaction.guild?.members.fetch(
interaction.user.id,
);
const member = await interaction.guild?.members.fetch(
interaction.options.get('member')!.value as string,
);
const reason = interaction.options.get('reason')?.value as string;
const muteDuration = interaction.options.get('duration')?.value as string;
if (!interaction.isChatInputCommand() || !interaction.guild) return;
if (
!interaction.memberPermissions?.has(
PermissionsBitField.Flags.ModerateMembers,
) ||
moderator!.roles.highest.position <= member!.roles.highest.position ||
!member?.moderatable
) {
await interaction.reply({
content:
'You do not have permission to timeout members or this member cannot be timed out.',
flags: ['Ephemeral'],
});
return;
}
await interaction.deferReply({ flags: ['Ephemeral'] });
try {
const moderator = await interaction.guild.members.fetch(
interaction.user.id,
);
const member = await interaction.guild.members.fetch(
interaction.options.get('member')!.value as string,
);
const reason = interaction.options.get('reason')?.value as string;
const muteDuration = interaction.options.get('duration')?.value as string;
if (
!interaction.memberPermissions?.has(
PermissionsBitField.Flags.KickMembers,
)
) {
await interaction.editReply({
content: 'You do not have permission to mute members.',
});
return;
}
if (moderator.roles.highest.position <= member.roles.highest.position) {
await interaction.editReply({
content:
'You cannot mute a member with equal or higher role than yours.',
});
return;
}
if (!member.moderatable) {
await interaction.editReply({
content: 'I do not have permission to mute this member.',
});
return;
}
const durationMs = parseDuration(muteDuration);
const maxTimeout = 28 * 24 * 60 * 60 * 1000;
if (durationMs > maxTimeout) {
await interaction.reply({
await interaction.editReply({
content: 'Timeout duration cannot exceed 28 days.',
flags: ['Ephemeral'],
});
return;
}
@ -94,19 +108,18 @@ const command: OptionsCommand = {
guild: interaction.guild!,
action: 'mute',
target: member,
moderator: moderator!,
moderator,
reason,
duration: muteDuration,
});
await interaction.reply({
content: `<@${member.id}> has been timed out for ${muteDuration}. Reason: ${reason}`,
await interaction.editReply({
content: `<@${member.id}> has been muted for ${muteDuration}. Reason: ${reason}`,
});
} catch (error) {
console.error('Mute command error:', error);
await interaction.reply({
await interaction.editReply({
content: 'Unable to timeout member.',
flags: ['Ephemeral'],
});
}
},

View file

@ -20,52 +20,53 @@ const command: OptionsCommand = {
.setRequired(true),
),
execute: async (interaction) => {
const userId = interaction.options.get('userid')!.value as string;
const reason = interaction.options.get('reason')?.value as string;
if (!interaction.isChatInputCommand() || !interaction.guild) return;
if (
!interaction.memberPermissions?.has(PermissionsBitField.Flags.BanMembers)
) {
await interaction.reply({
content: 'You do not have permission to unban users.',
flags: ['Ephemeral'],
});
return;
}
interaction.deferReply({ flags: ['Ephemeral'] });
try {
const userId = interaction.options.get('userid')?.value as string;
const reason = interaction.options.get('reason')?.value as string;
if (
!interaction.memberPermissions?.has(
PermissionsBitField.Flags.BanMembers,
)
) {
await interaction.editReply({
content: 'You do not have permission to unban users.',
});
return;
}
try {
const ban = await interaction.guild?.bans.fetch(userId);
const ban = await interaction.guild.bans.fetch(userId);
if (!ban) {
await interaction.reply({
await interaction.editReply({
content: 'This user is not banned.',
flags: ['Ephemeral'],
});
return;
}
} catch {
await interaction.reply({
await interaction.editReply({
content: 'Error getting ban. Is this user banned?',
flags: ['Ephemeral'],
});
return;
}
await executeUnban(
interaction.client,
interaction.guildId!,
interaction.guild.id,
userId,
reason,
);
await interaction.reply({
await interaction.editReply({
content: `<@${userId}> has been unbanned. Reason: ${reason}`,
});
} catch (error) {
console.error(error);
await interaction.reply({
await interaction.editReply({
content: 'Unable to unban user.',
flags: ['Ephemeral'],
});
}
},

View file

@ -20,43 +20,44 @@ const command: OptionsCommand = {
.setRequired(true),
),
execute: async (interaction) => {
const moderator = await interaction.guild?.members.fetch(
interaction.user.id,
);
const member = await interaction.guild?.members.fetch(
interaction.options.get('member')!.value as string,
);
const reason = interaction.options.get('reason')?.value as string;
if (!interaction.isChatInputCommand() || !interaction.guild) return;
if (
!interaction.memberPermissions?.has(
PermissionsBitField.Flags.ModerateMembers,
)
) {
await interaction.reply({
content: 'You do not have permission to unmute members.',
flags: ['Ephemeral'],
});
return;
}
await interaction.deferReply({ flags: ['Ephemeral'] });
try {
const moderator = await interaction.guild.members.fetch(
interaction.user.id,
);
const member = await interaction.guild.members.fetch(
interaction.options.get('member')!.value as string,
);
const reason = interaction.options.get('reason')?.value as string;
if (
!interaction.memberPermissions?.has(
PermissionsBitField.Flags.ModerateMembers,
)
) {
await interaction.editReply({
content: 'You do not have permission to unmute members.',
});
return;
}
await executeUnmute(
interaction.client,
interaction.guild!.id,
member!.id,
interaction.guild.id,
member.id,
reason,
moderator,
);
await interaction.reply({
content: `<@${member!.id}>'s timeout has been removed. Reason: ${reason}`,
await interaction.editReply({
content: `<@${member.id}>'s timeout has been removed. Reason: ${reason}`,
});
} catch (error) {
console.error('Unmute command error:', error);
await interaction.reply({
await interaction.editReply({
content: 'Unable to unmute member.',
flags: ['Ephemeral'],
});
}
},

View file

@ -21,29 +21,38 @@ const command: OptionsCommand = {
.setRequired(true),
),
execute: async (interaction) => {
const moderator = await interaction.guild?.members.fetch(
interaction.user.id,
);
const member = await interaction.guild?.members.fetch(
interaction.options.get('member')!.value as unknown as string,
);
const reason = interaction.options.get('reason')
?.value as unknown as string;
if (!interaction.isChatInputCommand() || !interaction.guild) return;
if (
!interaction.memberPermissions?.has(
PermissionsBitField.Flags.ModerateMembers,
) ||
moderator!.roles.highest.position <= member!.roles.highest.position
) {
await interaction.reply({
content: 'You do not have permission to warn this member.',
flags: ['Ephemeral'],
});
return;
}
await interaction.deferReply({ flags: ['Ephemeral'] });
try {
const moderator = await interaction.guild.members.fetch(
interaction.user.id,
);
const member = await interaction.guild.members.fetch(
interaction.options.get('member')!.value as unknown as string,
);
const reason = interaction.options.getString('reason')!;
if (
!interaction.memberPermissions?.has(
PermissionsBitField.Flags.ModerateMembers,
)
) {
await interaction.editReply({
content: 'You do not have permission to warn members.',
});
return;
}
if (moderator.roles.highest.position <= member.roles.highest.position) {
await interaction.editReply({
content:
'You cannot warn a member with equal or higher role than yours.',
});
return;
}
await updateMemberModerationHistory({
discordId: member!.user.id,
moderatorDiscordId: interaction.user.id,
@ -61,14 +70,13 @@ const command: OptionsCommand = {
moderator: moderator!,
reason: reason,
});
await interaction.reply(
await interaction.editReply(
`<@${member!.user.id}> has been warned. Reason: ${reason}`,
);
} catch (error) {
console.error(error);
await interaction.reply({
await interaction.editReply({
content: 'There was an error trying to warn the member.',
flags: ['Ephemeral'],
});
}
},

View file

@ -8,25 +8,27 @@ const command: Command = {
.setDescription('Simulates a new member joining'),
execute: async (interaction) => {
if (!interaction.isChatInputCommand() || !interaction.guild) return;
const guild = interaction.guild;
await interaction.deferReply({ flags: ['Ephemeral'] });
if (
!interaction.memberPermissions!.has(
PermissionsBitField.Flags.Administrator,
)
) {
await interaction.reply({
await interaction.editReply({
content: 'You do not have permission to use this command.',
flags: ['Ephemeral'],
});
return;
}
const fakeMember = await guild!.members.fetch(interaction.user.id);
guild!.client.emit('guildMemberAdd', fakeMember);
const fakeMember = await guild.members.fetch(interaction.user.id);
guild.client.emit('guildMemberAdd', fakeMember);
await interaction.reply({
await interaction.editReply({
content: 'Triggered the join event!',
flags: ['Ephemeral'],
});
},
};

View file

@ -9,25 +9,26 @@ const command: Command = {
.setDescription('Simulates a member leaving'),
execute: async (interaction) => {
if (!interaction.isChatInputCommand() || !interaction.guild) return;
const guild = interaction.guild;
await interaction.deferReply({ flags: ['Ephemeral'] });
if (
!interaction.memberPermissions!.has(
PermissionsBitField.Flags.Administrator,
)
) {
await interaction.reply({
await interaction.editReply({
content: 'You do not have permission to use this command.',
flags: ['Ephemeral'],
});
}
const fakeMember = await guild!.members.fetch(interaction.user.id);
guild!.client.emit('guildMemberRemove', fakeMember);
const fakeMember = await guild.members.fetch(interaction.user.id);
guild.client.emit('guildMemberRemove', fakeMember);
await interaction.reply({
await interaction.editReply({
content: 'Triggered the leave event!',
flags: ['Ephemeral'],
});
await updateMember({

View file

@ -14,12 +14,15 @@ const command: Command = {
.setDescription('(Admin Only) Display the current configuration')
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
execute: async (interaction) => {
if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.deferReply({ flags: ['Ephemeral'] });
if (
!interaction.memberPermissions?.has(PermissionFlagsBits.Administrator)
) {
await interaction.reply({
await interaction.editReply({
content: 'You do not have permission to use this command.',
flags: ['Ephemeral'],
});
return;
}
@ -180,10 +183,9 @@ const command: Command = {
? [createPaginationButtons(pages.length, currentPage)]
: [];
const reply = await interaction.reply({
const reply = await interaction.editReply({
embeds: [pages[currentPage]],
components,
flags: ['Ephemeral'],
});
if (pages.length <= 1) return;

View file

@ -26,9 +26,11 @@ const command: OptionsCommand = {
),
execute: async (interaction) => {
try {
await interaction.deferReply();
if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.deferReply();
try {
const client = interaction.client as ExtendedClient;
const commandName = interaction.options.getString('command');
@ -178,7 +180,7 @@ async function handleSpecificCommand(
const cmd = client.commands.get(commandName);
if (!cmd) {
return interaction.reply({
return interaction.editReply({
content: `Command \`${commandName}\` not found.`,
ephemeral: true,
});
@ -227,7 +229,7 @@ async function handleSpecificCommand(
inline: false,
});
return interaction.reply({ embeds: [embed] });
return interaction.editReply({ embeds: [embed] });
}
/**

View file

@ -16,6 +16,10 @@ const command: Command = {
.setName('members')
.setDescription('Lists all non-bot members of the server'),
execute: async (interaction) => {
if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.deferReply();
let members = await getAllMembers();
members = members.sort((a, b) =>
(a.discordUsername ?? '').localeCompare(b.discordUsername ?? ''),
@ -63,7 +67,7 @@ const command: Command = {
const components =
pages.length > 1 ? [getButtonActionRow(), getSelectMenuRow()] : [];
await interaction.reply({
await interaction.editReply({
embeds: [pages[currentPage]],
components,
});

View file

@ -8,7 +8,7 @@ const command: Command = {
.setDescription('Check the latency from you to the bot'),
execute: async (interaction) => {
await interaction.reply(
`Pong! Latency: ${Date.now() - interaction.createdTimestamp}ms`,
`🏓 Pong! Latency: ${Date.now() - interaction.createdTimestamp}ms`,
);
},
};

View file

@ -8,21 +8,22 @@ const command: Command = {
.setName('recalculatelevels')
.setDescription('(Admin Only) Recalculate all user levels'),
execute: async (interaction) => {
if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.deferReply({ flags: ['Ephemeral'] });
await interaction.editReply('Recalculating levels...');
if (
!interaction.memberPermissions?.has(
PermissionsBitField.Flags.Administrator,
)
) {
await interaction.reply({
await interaction.editReply({
content: 'You do not have permission to use this command.',
flags: ['Ephemeral'],
});
return;
}
await interaction.deferReply();
await interaction.editReply('Recalculating levels...');
try {
await recalculateUserLevels();
await interaction.editReply('Levels recalculated successfully!');

View file

@ -36,7 +36,9 @@ const command: SubcommandCommand = {
),
execute: async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.deferReply({ flags: ['Ephemeral'] });
const config = loadConfig();
const managerRoleId = config.roles.staffRoles.find(
@ -52,18 +54,15 @@ const command: SubcommandCommand = {
PermissionsBitField.Flags.Administrator,
)
) {
await interaction.reply({
await interaction.editReply({
content:
'You do not have permission to use this command. This command is restricted to users with the Manager role.',
flags: ['Ephemeral'],
});
return;
}
const subcommand = interaction.options.getSubcommand();
await interaction.deferReply({ flags: ['Ephemeral'] });
try {
if (subcommand === 'database') {
await handleDatabaseReconnect(interaction);

View file

@ -18,6 +18,10 @@ const command: Command = {
.setName('restart')
.setDescription('(Manager Only) Restart the bot'),
execute: async (interaction) => {
if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.deferReply({ flags: ['Ephemeral'] });
const config = loadConfig();
const managerRoleId = config.roles.staffRoles.find(
(role) => role.name === 'Manager',
@ -32,17 +36,15 @@ const command: Command = {
PermissionsBitField.Flags.Administrator,
)
) {
await interaction.reply({
await interaction.editReply({
content:
'You do not have permission to restart the bot. This command is restricted to users with the Manager role.',
flags: ['Ephemeral'],
});
return;
}
await interaction.reply({
await interaction.editReply({
content: 'Restarting the bot... This may take a few moments.',
flags: ['Ephemeral'],
});
const dbConnected = await ensureDatabaseConnection();

View file

@ -7,8 +7,10 @@ const command: Command = {
.setName('server')
.setDescription('Provides information about the server.'),
execute: async (interaction) => {
if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.reply(
`The server **${interaction!.guild!.name}** has **${interaction!.guild!.memberCount}** members and was created on **${interaction!.guild!.createdAt}**. It is **${new Date().getFullYear() - interaction!.guild!.createdAt.getFullYear()!}** years old.`,
`The server **${interaction.guild.name}** has **${interaction.guild.memberCount}** members and was created on **${interaction.guild.createdAt}**. It is **${new Date().getFullYear() - interaction.guild.createdAt.getFullYear()}** years old.`,
);
},
};

View file

@ -19,13 +19,17 @@ const command: OptionsCommand = {
.setRequired(true),
),
execute: async (interaction) => {
if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.deferReply();
const userOption = interaction.options.get(
'user',
) as unknown as GuildMember;
const user = userOption.user;
if (!userOption || !user) {
await interaction.reply('User not found');
await interaction.editReply('User not found');
return;
}
if (
@ -33,7 +37,7 @@ const command: OptionsCommand = {
PermissionsBitField.Flags.ModerateMembers,
)
) {
await interaction.reply(
await interaction.editReply(
'You do not have permission to view member information.',
);
return;
@ -140,7 +144,7 @@ const command: OptionsCommand = {
iconURL: interaction.user.displayAvatarURL(),
});
await interaction.reply({ embeds: [embed] });
await interaction.editReply({ embeds: [embed] });
},
};

View file

@ -71,12 +71,16 @@ const command: SubcommandCommand = {
),
),
execute: async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (!interaction.isChatInputCommand() || !interaction.guild) return;
const commandUser = interaction.guild?.members.cache.get(
const commandUser = interaction.guild.members.cache.get(
interaction.user.id,
);
await interaction.deferReply({
flags: ['Ephemeral'],
});
const config = loadConfig();
const managerRoleId = config.roles.staffRoles.find(
(role) => role.name === 'Manager',
@ -87,18 +91,12 @@ const command: SubcommandCommand = {
!managerRoleId ||
commandUser.roles.highest.comparePositionTo(managerRoleId) < 0
) {
await interaction.reply({
await interaction.editReply({
content: 'You do not have permission to use this command',
flags: ['Ephemeral'],
});
return;
}
await interaction.deferReply({
flags: ['Ephemeral'],
});
await interaction.editReply('Processing...');
const subcommand = interaction.options.getSubcommand();
const user = interaction.options.getUser('user', true);
const amount = interaction.options.getInteger('amount', false);

View file

@ -12,9 +12,10 @@ async function startBot() {
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildModeration,
GatewayIntentBits.GuildInvites,
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.GuildModeration,
],
},
config,

View file

@ -33,6 +33,10 @@ export class ExtendedClient extends Client {
console.log('Skipping command deployment (SKIP_COMMAND_DEPLOY=true)');
const commandFiles = await this.loadCommandsWithoutDeploying();
if (!commandFiles?.length) {
throw new Error('No commands found');
}
await registerEvents(this);
console.log(
`Loaded ${commandFiles.length} commands and registered events (without deployment)`,

View file

@ -5,6 +5,7 @@ export interface Config {
token: string;
clientId: string;
guildId: string;
serverInvite: string;
database: {
dbConnectionString: string;
maxRetryAttempts: number;