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", "token": "DISCORD_BOT_TOKEN",
"clientId": "DISCORD_BOT_ID", "clientId": "DISCORD_BOT_ID",
"guildId": "DISCORD_SERVER_ID", "guildId": "DISCORD_SERVER_ID",
"serverInvite": "DISCORD_SERVER_INVITE_LINK",
"database": { "database": {
"dbConnectionString": "POSTGRESQL_CONNECTION_STRING", "dbConnectionString": "POSTGRESQL_CONNECTION_STRING",
"maxRetryAttempts": "MAX_RETRY_ATTEMPTS", "maxRetryAttempts": "MAX_RETRY_ATTEMPTS",

View file

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

View file

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

View file

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

View file

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

View file

@ -15,18 +15,16 @@ const command: OptionsCommand = {
.setRequired(false), .setRequired(false),
), ),
execute: async (interaction) => { execute: async (interaction) => {
const member = await interaction.guild?.members.fetch( if (!interaction.isChatInputCommand() || !interaction.guild) return;
(interaction.options.get('user')?.value as string) || interaction.user.id,
);
if (!member) {
await interaction.reply('User not found in this server.');
return;
}
await interaction.deferReply(); await interaction.deferReply();
try { 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 userData = await getUserLevel(member.id);
const rankCard = await generateRankCard(member, userData); 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 { updateMember, updateMemberModerationHistory } from '@/db/db.js';
import { parseDuration, scheduleUnban } from '@/util/helpers.js'; import { parseDuration, scheduleUnban } from '@/util/helpers.js';
import { OptionsCommand } from '@/types/CommandTypes.js'; import { OptionsCommand } from '@/types/CommandTypes.js';
import { loadConfig } from '@/util/configLoader.js';
import logAction from '@/util/logging/logAction.js'; import logAction from '@/util/logging/logAction.js';
const command: OptionsCommand = { const command: OptionsCommand = {
@ -30,10 +31,15 @@ const command: OptionsCommand = {
.setRequired(false), .setRequired(false),
), ),
execute: async (interaction) => { execute: async (interaction) => {
const moderator = await interaction.guild?.members.fetch( if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.deferReply({ flags: ['Ephemeral'] });
try {
const moderator = await interaction.guild.members.fetch(
interaction.user.id, interaction.user.id,
); );
const member = await interaction.guild?.members.fetch( const member = await interaction.guild.members.fetch(
interaction.options.get('member')!.value as string, interaction.options.get('member')!.value as string,
); );
const reason = interaction.options.get('reason')?.value as string; const reason = interaction.options.get('reason')?.value as string;
@ -44,13 +50,27 @@ const command: OptionsCommand = {
if ( if (
!interaction.memberPermissions?.has( !interaction.memberPermissions?.has(
PermissionsBitField.Flags.BanMembers, 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.',
flags: ['Ephemeral'],
});
return;
}
if (moderator.roles.highest.position <= member.roles.highest.position) {
await interaction.reply({ await interaction.reply({
content: content:
'You do not have permission to ban members or this member cannot be banned.', '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'], flags: ['Ephemeral'],
}); });
return; return;
@ -61,9 +81,12 @@ const command: OptionsCommand = {
banDuration banDuration
? `You have been banned from ${interaction.guild!.name} for ${banDuration}. Reason: ${reason}. You can join back at ${new Date( ? `You have been banned from ${interaction.guild!.name} for ${banDuration}. Reason: ${reason}. You can join back at ${new Date(
Date.now() + parseDuration(banDuration), Date.now() + parseDuration(banDuration),
).toUTCString()} using the link below:\nhttps://discord.gg/KRTGjxx7gY` ).toUTCString()} using the link below:\n${interaction.guild.vanityURLCode ?? loadConfig().serverInvite}`
: `You been indefinitely banned from ${interaction.guild!.name}. Reason: ${reason}.`, : `You been indefinitely banned from ${interaction.guild!.name}. Reason: ${reason}.`,
); );
} catch (error) {
console.error('Failed to send DM:', error);
}
await member.ban({ reason }); await member.ban({ reason });
if (banDuration) { if (banDuration) {
@ -97,20 +120,19 @@ const command: OptionsCommand = {
guild: interaction.guild!, guild: interaction.guild!,
action: 'ban', action: 'ban',
target: member, target: member,
moderator: moderator!, moderator,
reason, reason,
}); });
await interaction.reply({ await interaction.editReply({
content: banDuration content: banDuration
? `<@${member.id}> has been banned for ${banDuration}. Reason: ${reason}` ? `<@${member.id}> has been banned for ${banDuration}. Reason: ${reason}`
: `<@${member.id}> has been indefinitely banned. Reason: ${reason}`, : `<@${member.id}> has been indefinitely banned. Reason: ${reason}`,
}); });
} catch (error) { } catch (error) {
console.error('Ban command error:', error); console.error('Ban command error:', error);
await interaction.reply({ await interaction.editReply({
content: 'Unable to ban member.', 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 { updateMemberModerationHistory } from '@/db/db.js';
import { OptionsCommand } from '@/types/CommandTypes.js'; import { OptionsCommand } from '@/types/CommandTypes.js';
import { loadConfig } from '@/util/configLoader.js';
import logAction from '@/util/logging/logAction.js'; import logAction from '@/util/logging/logAction.js';
const command: OptionsCommand = { const command: OptionsCommand = {
@ -21,10 +22,15 @@ const command: OptionsCommand = {
.setRequired(true), .setRequired(true),
), ),
execute: async (interaction) => { execute: async (interaction) => {
const moderator = await interaction.guild?.members.fetch( if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.deferReply({ flags: ['Ephemeral'] });
try {
const moderator = await interaction.guild.members.fetch(
interaction.user.id, interaction.user.id,
); );
const member = await interaction.guild?.members.fetch( const member = await interaction.guild.members.fetch(
interaction.options.get('member')!.value as string, interaction.options.get('member')!.value as string,
); );
const reason = interaction.options.get('reason')?.value as string; const reason = interaction.options.get('reason')?.value as string;
@ -32,22 +38,32 @@ const command: OptionsCommand = {
if ( if (
!interaction.memberPermissions?.has( !interaction.memberPermissions?.has(
PermissionsBitField.Flags.KickMembers, PermissionsBitField.Flags.KickMembers,
) || )
moderator!.roles.highest.position <= member!.roles.highest.position ||
!member?.kickable
) { ) {
await interaction.reply({ 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: content:
'You do not have permission to kick members or this member cannot be kicked.', 'You cannot kick a member with equal or higher role than yours.',
flags: ['Ephemeral'], });
return;
}
if (!member.kickable) {
await interaction.editReply({
content: 'I do not have permission to kick this member.',
}); });
return; return;
} }
try {
try { try {
await member.user.send( 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) { } catch (error) {
console.error('Failed to send DM to kicked user:', error); console.error('Failed to send DM to kicked user:', error);
@ -68,18 +84,17 @@ const command: OptionsCommand = {
guild: interaction.guild!, guild: interaction.guild!,
action: 'kick', action: 'kick',
target: member, target: member,
moderator: moderator!, moderator,
reason, reason,
}); });
await interaction.reply({ await interaction.editReply({
content: `<@${member.id}> has been kicked. Reason: ${reason}`, content: `<@${member.id}> has been kicked. Reason: ${reason}`,
}); });
} catch (error) { } catch (error) {
console.error('Kick command error:', error); console.error('Kick command error:', error);
await interaction.reply({ await interaction.editReply({
content: 'Unable to kick member.', content: 'Unable to kick member.',
flags: ['Ephemeral'],
}); });
} }
}, },

View file

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

View file

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

View file

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

View file

@ -21,29 +21,38 @@ const command: OptionsCommand = {
.setRequired(true), .setRequired(true),
), ),
execute: async (interaction) => { execute: async (interaction) => {
const moderator = await interaction.guild?.members.fetch( if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.deferReply({ flags: ['Ephemeral'] });
try {
const moderator = await interaction.guild.members.fetch(
interaction.user.id, interaction.user.id,
); );
const member = await interaction.guild?.members.fetch( const member = await interaction.guild.members.fetch(
interaction.options.get('member')!.value as unknown as string, interaction.options.get('member')!.value as unknown as string,
); );
const reason = interaction.options.get('reason') const reason = interaction.options.getString('reason')!;
?.value as unknown as string;
if ( if (
!interaction.memberPermissions?.has( !interaction.memberPermissions?.has(
PermissionsBitField.Flags.ModerateMembers, PermissionsBitField.Flags.ModerateMembers,
) || )
moderator!.roles.highest.position <= member!.roles.highest.position
) { ) {
await interaction.reply({ await interaction.editReply({
content: 'You do not have permission to warn this member.', content: 'You do not have permission to warn members.',
flags: ['Ephemeral'], });
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; return;
} }
try {
await updateMemberModerationHistory({ await updateMemberModerationHistory({
discordId: member!.user.id, discordId: member!.user.id,
moderatorDiscordId: interaction.user.id, moderatorDiscordId: interaction.user.id,
@ -61,14 +70,13 @@ const command: OptionsCommand = {
moderator: moderator!, moderator: moderator!,
reason: reason, reason: reason,
}); });
await interaction.reply( await interaction.editReply(
`<@${member!.user.id}> has been warned. Reason: ${reason}`, `<@${member!.user.id}> has been warned. Reason: ${reason}`,
); );
} catch (error) { } catch (error) {
console.error(error); console.error(error);
await interaction.reply({ await interaction.editReply({
content: 'There was an error trying to warn the member.', 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'), .setDescription('Simulates a new member joining'),
execute: async (interaction) => { execute: async (interaction) => {
if (!interaction.isChatInputCommand() || !interaction.guild) return;
const guild = interaction.guild; const guild = interaction.guild;
await interaction.deferReply({ flags: ['Ephemeral'] });
if ( if (
!interaction.memberPermissions!.has( !interaction.memberPermissions!.has(
PermissionsBitField.Flags.Administrator, PermissionsBitField.Flags.Administrator,
) )
) { ) {
await interaction.reply({ await interaction.editReply({
content: 'You do not have permission to use this command.', content: 'You do not have permission to use this command.',
flags: ['Ephemeral'],
}); });
return;
} }
const fakeMember = await guild!.members.fetch(interaction.user.id); const fakeMember = await guild.members.fetch(interaction.user.id);
guild!.client.emit('guildMemberAdd', fakeMember); guild.client.emit('guildMemberAdd', fakeMember);
await interaction.reply({ await interaction.editReply({
content: 'Triggered the join event!', content: 'Triggered the join event!',
flags: ['Ephemeral'],
}); });
}, },
}; };

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,8 +7,10 @@ const command: Command = {
.setName('server') .setName('server')
.setDescription('Provides information about the server.'), .setDescription('Provides information about the server.'),
execute: async (interaction) => { execute: async (interaction) => {
if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.reply( 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), .setRequired(true),
), ),
execute: async (interaction) => { execute: async (interaction) => {
if (!interaction.isChatInputCommand() || !interaction.guild) return;
await interaction.deferReply();
const userOption = interaction.options.get( const userOption = interaction.options.get(
'user', 'user',
) as unknown as GuildMember; ) as unknown as GuildMember;
const user = userOption.user; const user = userOption.user;
if (!userOption || !user) { if (!userOption || !user) {
await interaction.reply('User not found'); await interaction.editReply('User not found');
return; return;
} }
if ( if (
@ -33,7 +37,7 @@ const command: OptionsCommand = {
PermissionsBitField.Flags.ModerateMembers, PermissionsBitField.Flags.ModerateMembers,
) )
) { ) {
await interaction.reply( await interaction.editReply(
'You do not have permission to view member information.', 'You do not have permission to view member information.',
); );
return; return;
@ -140,7 +144,7 @@ const command: OptionsCommand = {
iconURL: interaction.user.displayAvatarURL(), 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) => { 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, interaction.user.id,
); );
await interaction.deferReply({
flags: ['Ephemeral'],
});
const config = loadConfig(); const config = loadConfig();
const managerRoleId = config.roles.staffRoles.find( const managerRoleId = config.roles.staffRoles.find(
(role) => role.name === 'Manager', (role) => role.name === 'Manager',
@ -87,18 +91,12 @@ const command: SubcommandCommand = {
!managerRoleId || !managerRoleId ||
commandUser.roles.highest.comparePositionTo(managerRoleId) < 0 commandUser.roles.highest.comparePositionTo(managerRoleId) < 0
) { ) {
await interaction.reply({ await interaction.editReply({
content: 'You do not have permission to use this command', content: 'You do not have permission to use this command',
flags: ['Ephemeral'],
}); });
return; return;
} }
await interaction.deferReply({
flags: ['Ephemeral'],
});
await interaction.editReply('Processing...');
const subcommand = interaction.options.getSubcommand(); const subcommand = interaction.options.getSubcommand();
const user = interaction.options.getUser('user', true); const user = interaction.options.getUser('user', true);
const amount = interaction.options.getInteger('amount', false); const amount = interaction.options.getInteger('amount', false);

View file

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

View file

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

View file

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