mirror of
https://github.com/ahmadk953/poixpixel-discord-bot.git
synced 2025-05-10 02:33:06 +00:00
feat(bot): merge pull request #343 from ahmadk953/utils
chore(bot): finish base bot
This commit is contained in:
commit
15cb83be7d
37 changed files with 1276 additions and 172 deletions
|
@ -11,7 +11,7 @@
|
|||
> [!WARNING]
|
||||
> Documentation is still under construction. Expect incomplete and undocumented features.
|
||||
|
||||
All documentation and setup instructions can be found at [https://docs.poixpixel.ahmadk953.org/](https://docs.poixpixel.ahmadk953.org/)
|
||||
All documentation and setup instructions can be found at [https://docs.poixpixel.ahmadk953.org/](https://docs.poixpixel.ahmadk953.org/?utm_source=github&utm_medium=readme&utm_campaign=repository&utm_content=docs_link)
|
||||
|
||||
## Development Commands
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -10,8 +10,10 @@
|
|||
"compile": "npx tsc",
|
||||
"target": "node ./target/discord-bot.js",
|
||||
"start:dev": "yarn run compile && yarn run target",
|
||||
"start:dev:no-deploy": "cross-env SKIP_COMMAND_DEPLOY=true yarn run start:dev",
|
||||
"start:prod": "yarn compile && pm2 start ./target/discord-bot.js --name poixpixel-discord-bot",
|
||||
"restart": "pm2 restart poixpixel-discord-bot",
|
||||
"undeploy-commands": "yarn compile && node --experimental-specifier-resolution=node ./target/util/undeployCommands.js",
|
||||
"lint": "npx eslint ./src && npx tsc --noEmit",
|
||||
"format": "prettier --check --ignore-path .prettierignore .",
|
||||
"format:fix": "prettier --write --ignore-path .prettierignore .",
|
||||
|
@ -34,6 +36,7 @@
|
|||
"@types/pg": "^8.11.13",
|
||||
"@typescript-eslint/eslint-plugin": "^8.30.1",
|
||||
"@typescript-eslint/parser": "^8.30.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"drizzle-kit": "^0.31.0",
|
||||
"eslint": "^9.24.0",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,40 @@ 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;
|
||||
}
|
||||
|
||||
try {
|
||||
await setCount(count);
|
||||
await interaction.reply({
|
||||
await interaction.editReply({
|
||||
content: `Count has been set to **${count}**. The next number should be **${count + 1}**.`,
|
||||
});
|
||||
} catch (error) {
|
||||
await interaction.editReply({
|
||||
content: `Failed to set the count: ${error}`,
|
||||
});
|
||||
}
|
||||
|
||||
await interaction.editReply({
|
||||
content: `Count has been set to **${count}**. The next number should be **${count + 1}**.`,
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,10 +31,15 @@ const command: OptionsCommand = {
|
|||
.setRequired(false),
|
||||
),
|
||||
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,
|
||||
);
|
||||
const member = await interaction.guild?.members.fetch(
|
||||
const member = await interaction.guild.members.fetch(
|
||||
interaction.options.get('member')!.value as string,
|
||||
);
|
||||
const reason = interaction.options.get('reason')?.value as string;
|
||||
|
@ -44,26 +50,44 @@ const command: OptionsCommand = {
|
|||
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'],
|
||||
await interaction.editReply({
|
||||
content: 'You do not have permission to ban members.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (moderator.roles.highest.position <= member.roles.highest.position) {
|
||||
await interaction.editReply({
|
||||
content:
|
||||
'You cannot ban a member with equal or higher role than yours.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!member.bannable) {
|
||||
await interaction.editReply({
|
||||
content: 'I do not have permission to ban this member.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const config = loadConfig();
|
||||
const invite = interaction.guild.vanityURLCode ?? config.serverInvite;
|
||||
const until = banDuration
|
||||
? new Date(Date.now() + parseDuration(banDuration)).toUTCString()
|
||||
: 'indefinitely';
|
||||
|
||||
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}.`,
|
||||
? `You have been banned from ${interaction.guild.name} for ${banDuration}. Reason: ${reason}. You can join back at ${until} using the link below:\n${invite}`
|
||||
: `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) {
|
||||
|
@ -72,7 +96,7 @@ const command: OptionsCommand = {
|
|||
|
||||
await scheduleUnban(
|
||||
interaction.client,
|
||||
interaction.guild!.id,
|
||||
interaction.guild.id,
|
||||
member.id,
|
||||
expiresAt,
|
||||
);
|
||||
|
@ -94,23 +118,22 @@ const command: OptionsCommand = {
|
|||
});
|
||||
|
||||
await logAction({
|
||||
guild: interaction.guild!,
|
||||
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'],
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
103
src/commands/moderation/kick.ts
Normal file
103
src/commands/moderation/kick.ts
Normal file
|
@ -0,0 +1,103 @@
|
|||
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 = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('kick')
|
||||
.setDescription('Kick a member from the server')
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('member')
|
||||
.setDescription('The member to kick')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('reason')
|
||||
.setDescription('The reason for the kick')
|
||||
.setRequired(true),
|
||||
),
|
||||
execute: async (interaction) => {
|
||||
if (!interaction.isChatInputCommand() || !interaction.guild) 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: \n${interaction.guild.vanityURLCode ?? loadConfig().serverInvite}`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to send DM to kicked user:', error);
|
||||
}
|
||||
|
||||
await member.kick(reason);
|
||||
|
||||
await updateMemberModerationHistory({
|
||||
discordId: member.id,
|
||||
moderatorDiscordId: interaction.user.id,
|
||||
action: 'kick',
|
||||
reason,
|
||||
duration: '',
|
||||
createdAt: new Date(),
|
||||
});
|
||||
|
||||
await logAction({
|
||||
guild: interaction.guild!,
|
||||
action: 'kick',
|
||||
target: member,
|
||||
moderator,
|
||||
reason,
|
||||
});
|
||||
|
||||
await interaction.editReply({
|
||||
content: `<@${member.id}> has been kicked. Reason: ${reason}`,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Kick command error:', error);
|
||||
await interaction.editReply({
|
||||
content: 'Unable to kick member.',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default command;
|
128
src/commands/moderation/mute.ts
Normal file
128
src/commands/moderation/mute.ts
Normal file
|
@ -0,0 +1,128 @@
|
|||
import { PermissionsBitField, SlashCommandBuilder } from 'discord.js';
|
||||
|
||||
import { updateMember, updateMemberModerationHistory } from '@/db/db.js';
|
||||
import { parseDuration } from '@/util/helpers.js';
|
||||
import { OptionsCommand } from '@/types/CommandTypes.js';
|
||||
import logAction from '@/util/logging/logAction.js';
|
||||
|
||||
const command: OptionsCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('mute')
|
||||
.setDescription('Timeout a member in the server')
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('member')
|
||||
.setDescription('The member to timeout')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('reason')
|
||||
.setDescription('The reason for the timeout')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('duration')
|
||||
.setDescription(
|
||||
'The duration of the timeout (ex. 5m, 1h, 1d, 1w). Max 28 days.',
|
||||
)
|
||||
.setRequired(true),
|
||||
),
|
||||
execute: async (interaction) => {
|
||||
if (!interaction.isChatInputCommand() || !interaction.guild) 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.editReply({
|
||||
content: 'Timeout duration cannot exceed 28 days.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await member.user.send(
|
||||
`You have been timed out in ${interaction.guild!.name} for ${muteDuration}. Reason: ${reason}.`,
|
||||
);
|
||||
|
||||
await member.timeout(durationMs, reason);
|
||||
|
||||
const expiresAt = new Date(Date.now() + durationMs);
|
||||
|
||||
await updateMemberModerationHistory({
|
||||
discordId: member.id,
|
||||
moderatorDiscordId: interaction.user.id,
|
||||
action: 'mute',
|
||||
reason,
|
||||
duration: muteDuration,
|
||||
createdAt: new Date(),
|
||||
expiresAt,
|
||||
active: true,
|
||||
});
|
||||
|
||||
await updateMember({
|
||||
discordId: member.id,
|
||||
currentlyMuted: true,
|
||||
});
|
||||
|
||||
await logAction({
|
||||
guild: interaction.guild!,
|
||||
action: 'mute',
|
||||
target: member,
|
||||
moderator,
|
||||
reason,
|
||||
duration: muteDuration,
|
||||
});
|
||||
|
||||
await interaction.editReply({
|
||||
content: `<@${member.id}> has been muted for ${muteDuration}. Reason: ${reason}`,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Mute command error:', error);
|
||||
await interaction.editReply({
|
||||
content: 'Unable to timeout member.',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default command;
|
|
@ -20,52 +20,54 @@ const command: OptionsCommand = {
|
|||
.setRequired(true),
|
||||
),
|
||||
execute: async (interaction) => {
|
||||
const userId = interaction.options.get('userid')!.value as string;
|
||||
if (!interaction.isChatInputCommand() || !interaction.guild) return;
|
||||
|
||||
await 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)
|
||||
!interaction.memberPermissions?.has(
|
||||
PermissionsBitField.Flags.BanMembers,
|
||||
)
|
||||
) {
|
||||
await interaction.reply({
|
||||
await interaction.editReply({
|
||||
content: 'You do not have permission to unban users.',
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
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({
|
||||
console.error(`Unable to unban user: ${error}`);
|
||||
await interaction.editReply({
|
||||
content: 'Unable to unban user.',
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
66
src/commands/moderation/unmute.ts
Normal file
66
src/commands/moderation/unmute.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { PermissionsBitField, SlashCommandBuilder } from 'discord.js';
|
||||
|
||||
import { executeUnmute } from '@/util/helpers.js';
|
||||
import { OptionsCommand } from '@/types/CommandTypes.js';
|
||||
|
||||
const command: OptionsCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('unmute')
|
||||
.setDescription('Remove a timeout from a member')
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('member')
|
||||
.setDescription('The member to unmute')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('reason')
|
||||
.setDescription('The reason for removing the timeout')
|
||||
.setRequired(true),
|
||||
),
|
||||
execute: async (interaction) => {
|
||||
if (!interaction.isChatInputCommand() || !interaction.guild) 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,
|
||||
reason,
|
||||
moderator,
|
||||
);
|
||||
|
||||
await interaction.editReply({
|
||||
content: `<@${member.id}>'s timeout has been removed. Reason: ${reason}`,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Unmute command error:', error);
|
||||
await interaction.editReply({
|
||||
content: 'Unable to unmute member.',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default command;
|
|
@ -21,29 +21,38 @@ const command: OptionsCommand = {
|
|||
.setRequired(true),
|
||||
),
|
||||
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,
|
||||
);
|
||||
const member = await interaction.guild?.members.fetch(
|
||||
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;
|
||||
const reason = interaction.options.getString('reason')!;
|
||||
|
||||
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'],
|
||||
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;
|
||||
}
|
||||
|
||||
try {
|
||||
await updateMemberModerationHistory({
|
||||
discordId: member!.user.id,
|
||||
moderatorDiscordId: interaction.user.id,
|
||||
|
@ -54,9 +63,6 @@ const command: OptionsCommand = {
|
|||
await member!.user.send(
|
||||
`You have been warned in **${interaction?.guild?.name}**. Reason: **${reason}**.`,
|
||||
);
|
||||
await interaction.reply(
|
||||
`<@${member!.user.id}> has been warned. Reason: ${reason}`,
|
||||
);
|
||||
await logAction({
|
||||
guild: interaction.guild!,
|
||||
action: 'warn',
|
||||
|
@ -64,11 +70,13 @@ const command: OptionsCommand = {
|
|||
moderator: moderator!,
|
||||
reason: reason,
|
||||
});
|
||||
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'],
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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'],
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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({
|
||||
|
|
237
src/commands/util/config.ts
Normal file
237
src/commands/util/config.ts
Normal file
|
@ -0,0 +1,237 @@
|
|||
import {
|
||||
SlashCommandBuilder,
|
||||
EmbedBuilder,
|
||||
PermissionFlagsBits,
|
||||
} from 'discord.js';
|
||||
|
||||
import { Command } from '@/types/CommandTypes.js';
|
||||
import { loadConfig } from '@/util/configLoader.js';
|
||||
import { createPaginationButtons } from '@/util/helpers.js';
|
||||
|
||||
const command: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('config')
|
||||
.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.editReply({
|
||||
content: 'You do not have permission to use this command.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const config = loadConfig();
|
||||
const displayConfig = JSON.parse(JSON.stringify(config));
|
||||
|
||||
if (displayConfig.token) displayConfig.token = '••••••••••••••••••••••••••';
|
||||
if (displayConfig.database?.dbConnectionString) {
|
||||
displayConfig.database.dbConnectionString = '••••••••••••••••••••••••••';
|
||||
}
|
||||
if (displayConfig.redis?.redisConnectionString) {
|
||||
displayConfig.redis.redisConnectionString = '••••••••••••••••••••••••••';
|
||||
}
|
||||
|
||||
const pages: EmbedBuilder[] = [];
|
||||
|
||||
const basicConfigEmbed = new EmbedBuilder()
|
||||
.setColor(0x0099ff)
|
||||
.setTitle('Bot Configuration')
|
||||
.setDescription(
|
||||
'Current configuration settings (sensitive data redacted)',
|
||||
)
|
||||
.addFields(
|
||||
{
|
||||
name: 'Client ID',
|
||||
value: displayConfig.clientId || 'Not set',
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Guild ID',
|
||||
value: displayConfig.guildId || 'Not set',
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Token',
|
||||
value: displayConfig.token || 'Not set',
|
||||
inline: true,
|
||||
},
|
||||
);
|
||||
|
||||
pages.push(basicConfigEmbed);
|
||||
|
||||
if (displayConfig.database || displayConfig.redis) {
|
||||
const dbRedisEmbed = new EmbedBuilder()
|
||||
.setColor(0x0099ff)
|
||||
.setTitle('Database and Redis Configuration')
|
||||
.setDescription('Database and cache settings');
|
||||
|
||||
if (displayConfig.database) {
|
||||
dbRedisEmbed.addFields({
|
||||
name: 'Database',
|
||||
value: `Connection: ${displayConfig.database.dbConnectionString}\nMax Retry: ${displayConfig.database.maxRetryAttempts}\nRetry Delay: ${displayConfig.database.retryDelay}ms`,
|
||||
});
|
||||
}
|
||||
|
||||
if (displayConfig.redis) {
|
||||
dbRedisEmbed.addFields({
|
||||
name: 'Redis',
|
||||
value: `Connection: ${displayConfig.redis.redisConnectionString}\nRetry Attempts: ${displayConfig.redis.retryAttempts}\nInitial Retry Delay: ${displayConfig.redis.initialRetryDelay}ms`,
|
||||
});
|
||||
}
|
||||
|
||||
pages.push(dbRedisEmbed);
|
||||
}
|
||||
|
||||
if (displayConfig.channels || displayConfig.roles) {
|
||||
const channelsRolesEmbed = new EmbedBuilder()
|
||||
.setColor(0x0099ff)
|
||||
.setTitle('Channels and Roles Configuration')
|
||||
.setDescription('Server channel and role settings');
|
||||
|
||||
if (displayConfig.channels) {
|
||||
const channelsText = Object.entries(displayConfig.channels)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join('\n');
|
||||
|
||||
channelsRolesEmbed.addFields({
|
||||
name: 'Channels',
|
||||
value: channelsText || 'None configured',
|
||||
});
|
||||
}
|
||||
|
||||
if (displayConfig.roles) {
|
||||
let rolesText = '';
|
||||
|
||||
if (displayConfig.roles.joinRoles?.length) {
|
||||
rolesText += `Join Roles: ${displayConfig.roles.joinRoles.join(', ')}\n`;
|
||||
}
|
||||
|
||||
if (displayConfig.roles.levelRoles?.length) {
|
||||
rolesText += `Level Roles: ${displayConfig.roles.levelRoles.length} configured\n`;
|
||||
}
|
||||
|
||||
if (displayConfig.roles.staffRoles?.length) {
|
||||
rolesText += `Staff Roles: ${displayConfig.roles.staffRoles.length} configured\n`;
|
||||
}
|
||||
|
||||
if (displayConfig.roles.factPingRole) {
|
||||
rolesText += `Fact Ping Role: ${displayConfig.roles.factPingRole}`;
|
||||
}
|
||||
|
||||
channelsRolesEmbed.addFields({
|
||||
name: 'Roles',
|
||||
value: rolesText || 'None configured',
|
||||
});
|
||||
}
|
||||
|
||||
pages.push(channelsRolesEmbed);
|
||||
}
|
||||
|
||||
if (
|
||||
displayConfig.leveling ||
|
||||
displayConfig.counting ||
|
||||
displayConfig.giveaways
|
||||
) {
|
||||
const featuresEmbed = new EmbedBuilder()
|
||||
.setColor(0x0099ff)
|
||||
.setTitle('Feature Configurations')
|
||||
.setDescription('Settings for bot features');
|
||||
|
||||
if (displayConfig.leveling) {
|
||||
featuresEmbed.addFields({
|
||||
name: 'Leveling',
|
||||
value: `XP Cooldown: ${displayConfig.leveling.xpCooldown}s\nMin XP: ${displayConfig.leveling.minXpAwarded}\nMax XP: ${displayConfig.leveling.maxXpAwarded}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (displayConfig.counting) {
|
||||
const countingText = Object.entries(displayConfig.counting)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join('\n');
|
||||
|
||||
featuresEmbed.addFields({
|
||||
name: 'Counting',
|
||||
value: countingText || 'Default settings',
|
||||
});
|
||||
}
|
||||
|
||||
if (displayConfig.giveaways) {
|
||||
const giveawaysText = Object.entries(displayConfig.giveaways)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join('\n');
|
||||
|
||||
featuresEmbed.addFields({
|
||||
name: 'Giveaways',
|
||||
value: giveawaysText || 'Default settings',
|
||||
});
|
||||
}
|
||||
|
||||
pages.push(featuresEmbed);
|
||||
}
|
||||
|
||||
let currentPage = 0;
|
||||
|
||||
const components =
|
||||
pages.length > 1
|
||||
? [createPaginationButtons(pages.length, currentPage)]
|
||||
: [];
|
||||
|
||||
const reply = await interaction.editReply({
|
||||
embeds: [pages[currentPage]],
|
||||
components,
|
||||
});
|
||||
|
||||
if (pages.length <= 1) return;
|
||||
|
||||
const collector = reply.createMessageComponentCollector({
|
||||
time: 300000,
|
||||
});
|
||||
|
||||
collector.on('collect', async (i) => {
|
||||
if (i.user.id !== interaction.user.id) {
|
||||
await i.reply({
|
||||
content: 'You cannot use these buttons.',
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
switch (i.customId) {
|
||||
case 'first':
|
||||
currentPage = 0;
|
||||
break;
|
||||
case 'prev':
|
||||
if (currentPage > 0) currentPage--;
|
||||
break;
|
||||
case 'next':
|
||||
if (currentPage < pages.length - 1) currentPage++;
|
||||
break;
|
||||
case 'last':
|
||||
currentPage = pages.length - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
await i.update({
|
||||
embeds: [pages[currentPage]],
|
||||
components: [createPaginationButtons(pages.length, currentPage)],
|
||||
});
|
||||
});
|
||||
|
||||
collector.on('end', async () => {
|
||||
try {
|
||||
await interaction.editReply({ components: [] });
|
||||
} catch (error) {
|
||||
console.error('Failed to remove pagination buttons:', error);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default command;
|
273
src/commands/util/help.ts
Normal file
273
src/commands/util/help.ts
Normal file
|
@ -0,0 +1,273 @@
|
|||
import {
|
||||
SlashCommandBuilder,
|
||||
EmbedBuilder,
|
||||
ActionRowBuilder,
|
||||
StringSelectMenuBuilder,
|
||||
StringSelectMenuOptionBuilder,
|
||||
ComponentType,
|
||||
} from 'discord.js';
|
||||
|
||||
import { OptionsCommand } from '@/types/CommandTypes.js';
|
||||
import { ExtendedClient } from '@/structures/ExtendedClient.js';
|
||||
|
||||
const DOC_BASE_URL = 'https://docs.poixpixel.ahmadk953.org/';
|
||||
const getDocUrl = (location: string) =>
|
||||
`${DOC_BASE_URL}?utm_source=discord&utm_medium=bot&utm_campaign=help_command&utm_content=${location}`;
|
||||
|
||||
const command: OptionsCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('help')
|
||||
.setDescription('Shows a list of all available commands')
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('command')
|
||||
.setDescription('Get detailed help for a specific command')
|
||||
.setRequired(false),
|
||||
),
|
||||
|
||||
execute: async (interaction) => {
|
||||
if (!interaction.isChatInputCommand() || !interaction.guild) return;
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
try {
|
||||
const client = interaction.client as ExtendedClient;
|
||||
const commandName = interaction.options.getString('command');
|
||||
|
||||
if (commandName) {
|
||||
return handleSpecificCommand(interaction, client, commandName);
|
||||
}
|
||||
|
||||
const categories = new Map();
|
||||
|
||||
for (const [name, cmd] of client.commands) {
|
||||
const category = getCategoryFromCommand(name);
|
||||
|
||||
if (!categories.has(category)) {
|
||||
categories.set(category, []);
|
||||
}
|
||||
|
||||
categories.get(category).push({
|
||||
name,
|
||||
description: cmd.data.toJSON().description,
|
||||
});
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor('#0099ff')
|
||||
.setTitle('Poixpixel Bot Commands')
|
||||
.setDescription(
|
||||
'**Welcome to Poixpixel Discord Bot!**\n\n' +
|
||||
'Select a category from the dropdown menu below to see available commands.\n\n' +
|
||||
`📚 **Documentation:** [Visit Our Documentation](${getDocUrl('main_description')})`,
|
||||
)
|
||||
.setThumbnail(client.user!.displayAvatarURL())
|
||||
.setFooter({
|
||||
text: 'Use /help [command] for detailed info about a command',
|
||||
});
|
||||
|
||||
const categoryEmojis: Record<string, string> = {
|
||||
fun: '🎮',
|
||||
moderation: '🛡️',
|
||||
util: '🔧',
|
||||
testing: '🧪',
|
||||
};
|
||||
|
||||
Array.from(categories.keys()).forEach((category) => {
|
||||
const emoji = categoryEmojis[category] || '📁';
|
||||
embed.addFields({
|
||||
name: `${emoji} ${category.charAt(0).toUpperCase() + category.slice(1)}`,
|
||||
value: `Use the dropdown to see ${category} commands`,
|
||||
inline: true,
|
||||
});
|
||||
});
|
||||
|
||||
embed.addFields({
|
||||
name: '📚 Documentation',
|
||||
value: `[Click here to access our full documentation](${getDocUrl('main_footer_field')})`,
|
||||
inline: false,
|
||||
});
|
||||
|
||||
const selectMenu =
|
||||
new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
|
||||
new StringSelectMenuBuilder()
|
||||
.setCustomId('help_category_select')
|
||||
.setPlaceholder('Select a command category')
|
||||
.addOptions(
|
||||
Array.from(categories.keys()).map((category) => {
|
||||
const emoji = categoryEmojis[category] || '📁';
|
||||
return new StringSelectMenuOptionBuilder()
|
||||
.setLabel(
|
||||
category.charAt(0).toUpperCase() + category.slice(1),
|
||||
)
|
||||
.setDescription(`View ${category} commands`)
|
||||
.setValue(category)
|
||||
.setEmoji(emoji);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const message = await interaction.editReply({
|
||||
embeds: [embed],
|
||||
components: [selectMenu],
|
||||
});
|
||||
|
||||
const collector = message.createMessageComponentCollector({
|
||||
componentType: ComponentType.StringSelect,
|
||||
time: 60000,
|
||||
});
|
||||
|
||||
collector.on('collect', async (i) => {
|
||||
if (i.user.id !== interaction.user.id) {
|
||||
await i.reply({
|
||||
content: 'You cannot use this menu.',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedCategory = i.values[0];
|
||||
const commands = categories.get(selectedCategory);
|
||||
const emoji = categoryEmojis[selectedCategory] || '📁';
|
||||
|
||||
const categoryEmbed = new EmbedBuilder()
|
||||
.setColor('#0099ff')
|
||||
.setTitle(
|
||||
`${emoji} ${selectedCategory.charAt(0).toUpperCase() + selectedCategory.slice(1)} Commands`,
|
||||
)
|
||||
.setDescription('Here are all the commands in this category:')
|
||||
.setFooter({
|
||||
text: 'Use /help [command] for detailed info about a command',
|
||||
});
|
||||
|
||||
commands.forEach((cmd: any) => {
|
||||
categoryEmbed.addFields({
|
||||
name: `/${cmd.name}`,
|
||||
value: cmd.description || 'No description available',
|
||||
inline: false,
|
||||
});
|
||||
});
|
||||
|
||||
categoryEmbed.addFields({
|
||||
name: '📚 Documentation',
|
||||
value: `[Click here to access our full documentation](${getDocUrl(`category_${selectedCategory}`)})`,
|
||||
inline: false,
|
||||
});
|
||||
|
||||
await i.update({ embeds: [categoryEmbed], components: [selectMenu] });
|
||||
});
|
||||
|
||||
collector.on('end', () => {
|
||||
interaction.editReply({ components: [] }).catch(console.error);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in help command:', error);
|
||||
await interaction.editReply({
|
||||
content: 'An error occurred while processing your request.',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle showing help for a specific command
|
||||
*/
|
||||
async function handleSpecificCommand(
|
||||
interaction: any,
|
||||
client: ExtendedClient,
|
||||
commandName: string,
|
||||
) {
|
||||
const cmd = client.commands.get(commandName);
|
||||
|
||||
if (!cmd) {
|
||||
return interaction.editReply({
|
||||
content: `Command \`${commandName}\` not found.`,
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor('#0099ff')
|
||||
.setTitle(`Help: /${commandName}`)
|
||||
.setDescription(cmd.data.toJSON().description || 'No description available')
|
||||
.addFields({
|
||||
name: 'Category',
|
||||
value: getCategoryFromCommand(commandName),
|
||||
inline: true,
|
||||
})
|
||||
.setFooter({
|
||||
text: `Poixpixel Discord Bot • Documentation: ${getDocUrl(`cmd_footer_${commandName}`)}`,
|
||||
});
|
||||
|
||||
const options = cmd.data.toJSON().options;
|
||||
if (options && options.length > 0) {
|
||||
if (options[0].type === 1) {
|
||||
embed.addFields({
|
||||
name: 'Subcommands',
|
||||
value: options
|
||||
.map((opt: any) => `\`${opt.name}\`: ${opt.description}`)
|
||||
.join('\n'),
|
||||
inline: false,
|
||||
});
|
||||
} else {
|
||||
embed.addFields({
|
||||
name: 'Options',
|
||||
value: options
|
||||
.map(
|
||||
(opt: any) =>
|
||||
`\`${opt.name}\`: ${opt.description} ${opt.required ? '(Required)' : '(Optional)'}`,
|
||||
)
|
||||
.join('\n'),
|
||||
inline: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
embed.addFields({
|
||||
name: '📚 Documentation',
|
||||
value: `[Click here to access our full documentation](${getDocUrl(`cmd_field_${commandName}`)})`,
|
||||
inline: false,
|
||||
});
|
||||
|
||||
return interaction.editReply({ embeds: [embed] });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the category of a command based on its name
|
||||
*/
|
||||
function getCategoryFromCommand(commandName: string): string {
|
||||
const commandCategories: Record<string, string> = {
|
||||
achievement: 'fun',
|
||||
fact: 'fun',
|
||||
rank: 'fun',
|
||||
counting: 'fun',
|
||||
giveaway: 'fun',
|
||||
leaderboard: 'fun',
|
||||
|
||||
ban: 'moderation',
|
||||
kick: 'moderation',
|
||||
mute: 'moderation',
|
||||
unmute: 'moderation',
|
||||
warn: 'moderation',
|
||||
unban: 'moderation',
|
||||
|
||||
ping: 'util',
|
||||
server: 'util',
|
||||
userinfo: 'util',
|
||||
members: 'util',
|
||||
rules: 'util',
|
||||
restart: 'util',
|
||||
reconnect: 'util',
|
||||
xp: 'util',
|
||||
recalculatelevels: 'util',
|
||||
help: 'util',
|
||||
config: 'util',
|
||||
|
||||
testjoin: 'testing',
|
||||
testleave: 'testing',
|
||||
};
|
||||
|
||||
return commandCategories[commandName.toLowerCase()] || 'other';
|
||||
}
|
||||
|
||||
export default command;
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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`,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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!');
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -18,12 +18,16 @@ 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',
|
||||
)?.roleId;
|
||||
|
||||
const member = await interaction.guild?.members.fetch(interaction.user.id);
|
||||
const member = await interaction.guild.members.fetch(interaction.user.id);
|
||||
const hasManagerRole = member?.roles.cache.has(managerRoleId || '');
|
||||
|
||||
if (
|
||||
|
@ -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();
|
||||
|
|
|
@ -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.`,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -19,21 +19,25 @@ 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 (
|
||||
!interaction.memberPermissions!.has(
|
||||
!interaction.memberPermissions?.has(
|
||||
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] });
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -133,6 +133,7 @@ export async function updateMember({
|
|||
discordUsername,
|
||||
currentlyInServer,
|
||||
currentlyBanned,
|
||||
currentlyMuted,
|
||||
}: schema.memberTableTypes): Promise<void> {
|
||||
try {
|
||||
await ensureDbInitialized();
|
||||
|
@ -147,6 +148,7 @@ export async function updateMember({
|
|||
discordUsername,
|
||||
currentlyInServer,
|
||||
currentlyBanned,
|
||||
currentlyMuted,
|
||||
})
|
||||
.where(eq(schema.memberTable.discordId, discordId));
|
||||
|
||||
|
|
|
@ -12,9 +12,10 @@ async function startBot() {
|
|||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.GuildModeration,
|
||||
GatewayIntentBits.GuildInvites,
|
||||
GatewayIntentBits.MessageContent,
|
||||
GatewayIntentBits.GuildMessageReactions,
|
||||
GatewayIntentBits.GuildModeration,
|
||||
],
|
||||
},
|
||||
config,
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
} from 'discord.js';
|
||||
|
||||
import { updateMember, setMembers } from '@/db/db.js';
|
||||
import { generateMemberBanner } from '@/util/helpers.js';
|
||||
import { executeUnmute, generateMemberBanner } from '@/util/helpers.js';
|
||||
import { loadConfig } from '@/util/configLoader.js';
|
||||
import { Event } from '@/types/EventTypes.js';
|
||||
import logAction from '@/util/logging/logAction.js';
|
||||
|
@ -144,6 +144,21 @@ export const memberUpdate: Event<typeof Events.GuildMemberUpdate> = {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
oldMember.communicationDisabledUntil !==
|
||||
newMember.communicationDisabledUntil &&
|
||||
newMember.communicationDisabledUntil === null
|
||||
) {
|
||||
await executeUnmute(
|
||||
newMember.client,
|
||||
guild.id,
|
||||
newMember.user.id,
|
||||
undefined,
|
||||
guild.members.me!,
|
||||
true,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error handling member update:', error);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
setDiscordClient as setRedisDiscordClient,
|
||||
} from '@/db/redis.js';
|
||||
import { setDiscordClient as setDbDiscordClient } from '@/db/db.js';
|
||||
import { loadActiveBans, loadActiveMutes } from '@/util/helpers.js';
|
||||
|
||||
export default {
|
||||
name: Events.ClientReady,
|
||||
|
@ -36,6 +37,9 @@ export default {
|
|||
const nonBotMembers = members.filter((m) => !m.user.bot);
|
||||
await setMembers(nonBotMembers);
|
||||
|
||||
await loadActiveBans(client, guild);
|
||||
await loadActiveMutes(client, guild);
|
||||
|
||||
await scheduleFactOfTheDay(client);
|
||||
await scheduleGiveaways(client);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Client, ClientOptions, Collection } from 'discord.js';
|
||||
import { Command } from '@/types/CommandTypes.js';
|
||||
import { Config } from '@/types/ConfigTypes.js';
|
||||
import { deployCommands } from '@/util/deployCommand.js';
|
||||
import { deployCommands, getFilesRecursively } from '@/util/deployCommand.js';
|
||||
import { registerEvents } from '@/util/eventLoader.js';
|
||||
|
||||
/**
|
||||
|
@ -29,6 +29,19 @@ export class ExtendedClient extends Client {
|
|||
|
||||
private async loadModules() {
|
||||
try {
|
||||
if (process.env.SKIP_COMMAND_DEPLOY === 'true') {
|
||||
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)`,
|
||||
);
|
||||
} else {
|
||||
const commands = await deployCommands();
|
||||
if (!commands?.length) {
|
||||
throw new Error('No commands found');
|
||||
|
@ -40,9 +53,49 @@ export class ExtendedClient extends Client {
|
|||
|
||||
await registerEvents(this);
|
||||
console.log(`Loaded ${commands.length} commands and registered events`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading modules:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads commands without deploying them to Discord
|
||||
* @returns Array of command objects
|
||||
*/
|
||||
private async loadCommandsWithoutDeploying(): Promise<Command[]> {
|
||||
try {
|
||||
const path = await import('path');
|
||||
|
||||
const __dirname = path.resolve();
|
||||
const commandsPath = path.join(__dirname, 'target', 'commands');
|
||||
|
||||
const commandFiles = getFilesRecursively(commandsPath);
|
||||
|
||||
const commands: Command[] = [];
|
||||
for (const file of commandFiles) {
|
||||
const commandModule = await import(`file://${file}`);
|
||||
const command = commandModule.default;
|
||||
|
||||
if (
|
||||
command instanceof Object &&
|
||||
'data' in command &&
|
||||
'execute' in command
|
||||
) {
|
||||
commands.push(command);
|
||||
this.commands.set(command.data.name, command);
|
||||
} else {
|
||||
console.warn(
|
||||
`[WARNING] The command at ${file} is missing a required "data" or "execute" property.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return commands;
|
||||
} catch (error) {
|
||||
console.error('Error loading commands:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ export interface Config {
|
|||
token: string;
|
||||
clientId: string;
|
||||
guildId: string;
|
||||
serverInvite: string;
|
||||
database: {
|
||||
dbConnectionString: string;
|
||||
maxRetryAttempts: number;
|
||||
|
|
|
@ -17,7 +17,7 @@ const rest = new REST({ version: '10' }).setToken(token);
|
|||
* @param directory - The directory to get files from
|
||||
* @returns - An array of file paths
|
||||
*/
|
||||
const getFilesRecursively = (directory: string): string[] => {
|
||||
export const getFilesRecursively = (directory: string): string[] => {
|
||||
const files: string[] = [];
|
||||
const filesInDirectory = fs.readdirSync(directory);
|
||||
|
||||
|
|
|
@ -10,11 +10,12 @@ import {
|
|||
ButtonStyle,
|
||||
ButtonBuilder,
|
||||
ActionRowBuilder,
|
||||
DiscordAPIError,
|
||||
} from 'discord.js';
|
||||
import { and, eq } from 'drizzle-orm';
|
||||
|
||||
import { moderationTable } from '@/db/schema.js';
|
||||
import { db, handleDbError, updateMember } from '@/db/db.js';
|
||||
import { db, getMember, handleDbError, updateMember } from '@/db/db.js';
|
||||
import logAction from './logging/logAction.js';
|
||||
|
||||
const __dirname = path.resolve();
|
||||
|
@ -116,6 +117,107 @@ export async function generateMemberBanner({
|
|||
return attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an unmute for a user
|
||||
* @param client - The client to use
|
||||
* @param guildId - The guild ID to unmute the user in
|
||||
* @param userId - The user ID to unmute
|
||||
* @param reason - The reason for the unmute
|
||||
* @param moderator - The moderator who is unmuting the user
|
||||
* @param alreadyUnmuted - Whether the user is already unmuted
|
||||
*/
|
||||
export async function executeUnmute(
|
||||
client: Client,
|
||||
guildId: string,
|
||||
userId: string,
|
||||
reason?: string,
|
||||
moderator?: GuildMember,
|
||||
alreadyUnmuted: boolean = false,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const guild = await client.guilds.fetch(guildId);
|
||||
let member;
|
||||
|
||||
try {
|
||||
member = await guild.members.fetch(userId);
|
||||
if (!alreadyUnmuted) {
|
||||
await member.timeout(null, reason ?? 'Temporary mute expired');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`Member ${userId} not found in server, just updating database`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!(await getMember(userId))?.currentlyMuted) return;
|
||||
|
||||
await db
|
||||
.update(moderationTable)
|
||||
.set({ active: false })
|
||||
.where(
|
||||
and(
|
||||
eq(moderationTable.discordId, userId),
|
||||
eq(moderationTable.action, 'mute'),
|
||||
eq(moderationTable.active, true),
|
||||
),
|
||||
);
|
||||
|
||||
await updateMember({
|
||||
discordId: userId,
|
||||
currentlyMuted: false,
|
||||
});
|
||||
|
||||
if (member) {
|
||||
await logAction({
|
||||
guild,
|
||||
action: 'unmute',
|
||||
target: member,
|
||||
reason: reason ?? 'Temporary mute expired',
|
||||
moderator: moderator ? moderator : guild.members.me!,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error executing unmute:', error);
|
||||
|
||||
if (!(error instanceof DiscordAPIError && error.code === 10007)) {
|
||||
handleDbError('Failed to execute unmute', error as Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all active mutes and schedules unmute events
|
||||
* @param client - The client to use
|
||||
* @param guild - The guild to load mutes for
|
||||
*/
|
||||
export async function loadActiveMutes(
|
||||
client: Client,
|
||||
guild: Guild,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const activeMutes = await db
|
||||
.select()
|
||||
.from(moderationTable)
|
||||
.where(
|
||||
and(
|
||||
eq(moderationTable.action, 'mute'),
|
||||
eq(moderationTable.active, true),
|
||||
),
|
||||
);
|
||||
|
||||
for (const mute of activeMutes) {
|
||||
if (!mute.expiresAt) continue;
|
||||
|
||||
const timeUntilUnmute = mute.expiresAt.getTime() - Date.now();
|
||||
if (timeUntilUnmute <= 0) {
|
||||
await executeUnmute(client, guild.id, mute.discordId);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
handleDbError('Failed to load active mutes', error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules an unban for a user
|
||||
* @param client - The client to use
|
||||
|
@ -174,7 +276,7 @@ export async function executeUnban(
|
|||
guild,
|
||||
action: 'unban',
|
||||
target: guild.members.cache.get(userId)!,
|
||||
moderator: guild.members.cache.get(client.user!.id)!,
|
||||
moderator: guild.members.me!,
|
||||
reason: reason ?? 'Temporary ban expired',
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
36
src/util/undeployCommands.ts
Normal file
36
src/util/undeployCommands.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { REST, Routes } from 'discord.js';
|
||||
import { loadConfig } from './configLoader.js';
|
||||
|
||||
const config = loadConfig();
|
||||
const { token, clientId, guildId } = config;
|
||||
|
||||
const rest = new REST({ version: '10' }).setToken(token);
|
||||
|
||||
/**
|
||||
* Undeploys all commands from the Discord API
|
||||
*/
|
||||
export const undeployCommands = async () => {
|
||||
try {
|
||||
console.log('Undeploying all commands from the Discord API...');
|
||||
|
||||
await rest.put(Routes.applicationGuildCommands(clientId, guildId), {
|
||||
body: [],
|
||||
});
|
||||
|
||||
console.log('Successfully undeployed all commands');
|
||||
} catch (error) {
|
||||
console.error('Error undeploying commands:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
if (import.meta.url.endsWith(process.argv[1].replace(/\\/g, '/'))) {
|
||||
undeployCommands()
|
||||
.then(() => {
|
||||
console.log('Undeploy process completed successfully');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Undeploy process failed:', err);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
15
yarn.lock
15
yarn.lock
|
@ -1882,7 +1882,19 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6":
|
||||
"cross-env@npm:^7.0.3":
|
||||
version: 7.0.3
|
||||
resolution: "cross-env@npm:7.0.3"
|
||||
dependencies:
|
||||
cross-spawn: "npm:^7.0.1"
|
||||
bin:
|
||||
cross-env: src/bin/cross-env.js
|
||||
cross-env-shell: src/bin/cross-env-shell.js
|
||||
checksum: 10c0/f3765c25746c69fcca369655c442c6c886e54ccf3ab8c16847d5ad0e91e2f337d36eedc6599c1227904bf2a228d721e690324446876115bc8e7b32a866735ecf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6":
|
||||
version: 7.0.6
|
||||
resolution: "cross-spawn@npm:7.0.6"
|
||||
dependencies:
|
||||
|
@ -4201,6 +4213,7 @@ __metadata:
|
|||
"@types/pg": "npm:^8.11.13"
|
||||
"@typescript-eslint/eslint-plugin": "npm:^8.30.1"
|
||||
"@typescript-eslint/parser": "npm:^8.30.1"
|
||||
cross-env: "npm:^7.0.3"
|
||||
discord.js: "npm:^14.18.0"
|
||||
drizzle-kit: "npm:^0.31.0"
|
||||
drizzle-orm: "npm:^0.42.0"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue