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]
|
> [!WARNING]
|
||||||
> Documentation is still under construction. Expect incomplete and undocumented features.
|
> 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
|
## Development Commands
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -10,8 +10,10 @@
|
||||||
"compile": "npx tsc",
|
"compile": "npx tsc",
|
||||||
"target": "node ./target/discord-bot.js",
|
"target": "node ./target/discord-bot.js",
|
||||||
"start:dev": "yarn run compile && yarn run target",
|
"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",
|
"start:prod": "yarn compile && pm2 start ./target/discord-bot.js --name poixpixel-discord-bot",
|
||||||
"restart": "pm2 restart 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",
|
"lint": "npx eslint ./src && npx tsc --noEmit",
|
||||||
"format": "prettier --check --ignore-path .prettierignore .",
|
"format": "prettier --check --ignore-path .prettierignore .",
|
||||||
"format:fix": "prettier --write --ignore-path .prettierignore .",
|
"format:fix": "prettier --write --ignore-path .prettierignore .",
|
||||||
|
@ -34,6 +36,7 @@
|
||||||
"@types/pg": "^8.11.13",
|
"@types/pg": "^8.11.13",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.30.1",
|
"@typescript-eslint/eslint-plugin": "^8.30.1",
|
||||||
"@typescript-eslint/parser": "^8.30.1",
|
"@typescript-eslint/parser": "^8.30.1",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"drizzle-kit": "^0.31.0",
|
"drizzle-kit": "^0.31.0",
|
||||||
"eslint": "^9.24.0",
|
"eslint": "^9.24.0",
|
||||||
"eslint-config-prettier": "^10.1.2",
|
"eslint-config-prettier": "^10.1.2",
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,40 @@ 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);
|
try {
|
||||||
await interaction.reply({
|
await setCount(count);
|
||||||
|
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}**.`,
|
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) => {
|
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,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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,40 +31,63 @@ 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;
|
||||||
interaction.user.id,
|
|
||||||
);
|
|
||||||
const member = await interaction.guild?.members.fetch(
|
|
||||||
interaction.options.get('member')!.value as string,
|
|
||||||
);
|
|
||||||
const reason = interaction.options.get('reason')?.value as string;
|
|
||||||
const banDuration = interaction.options.get('duration')?.value as
|
|
||||||
| string
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
if (
|
await interaction.deferReply({ flags: ['Ephemeral'] });
|
||||||
!interaction.memberPermissions?.has(
|
|
||||||
PermissionsBitField.Flags.BanMembers,
|
|
||||||
) ||
|
|
||||||
moderator!.roles.highest.position <= member!.roles.highest.position ||
|
|
||||||
!member?.bannable
|
|
||||||
) {
|
|
||||||
await interaction.reply({
|
|
||||||
content:
|
|
||||||
'You do not have permission to ban members or this member cannot be banned.',
|
|
||||||
flags: ['Ephemeral'],
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await member.user.send(
|
const moderator = await interaction.guild.members.fetch(
|
||||||
banDuration
|
interaction.user.id,
|
||||||
? `You have been banned from ${interaction.guild!.name} for ${banDuration}. Reason: ${reason}. You can join back at ${new Date(
|
|
||||||
Date.now() + parseDuration(banDuration),
|
|
||||||
).toUTCString()} using the link below:\nhttps://discord.gg/KRTGjxx7gY`
|
|
||||||
: `You been indefinitely banned from ${interaction.guild!.name}. Reason: ${reason}.`,
|
|
||||||
);
|
);
|
||||||
|
const member = await interaction.guild.members.fetch(
|
||||||
|
interaction.options.get('member')!.value as string,
|
||||||
|
);
|
||||||
|
const reason = interaction.options.get('reason')?.value as string;
|
||||||
|
const banDuration = interaction.options.get('duration')?.value as
|
||||||
|
| string
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!interaction.memberPermissions?.has(
|
||||||
|
PermissionsBitField.Flags.BanMembers,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
await interaction.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 ${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 });
|
await member.ban({ reason });
|
||||||
|
|
||||||
if (banDuration) {
|
if (banDuration) {
|
||||||
|
@ -72,7 +96,7 @@ const command: OptionsCommand = {
|
||||||
|
|
||||||
await scheduleUnban(
|
await scheduleUnban(
|
||||||
interaction.client,
|
interaction.client,
|
||||||
interaction.guild!.id,
|
interaction.guild.id,
|
||||||
member.id,
|
member.id,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
);
|
);
|
||||||
|
@ -94,23 +118,22 @@ const command: OptionsCommand = {
|
||||||
});
|
});
|
||||||
|
|
||||||
await logAction({
|
await logAction({
|
||||||
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'],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
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),
|
.setRequired(true),
|
||||||
),
|
),
|
||||||
execute: async (interaction) => {
|
execute: async (interaction) => {
|
||||||
const userId = interaction.options.get('userid')!.value as string;
|
if (!interaction.isChatInputCommand() || !interaction.guild) return;
|
||||||
const reason = interaction.options.get('reason')?.value as string;
|
|
||||||
|
|
||||||
if (
|
await interaction.deferReply({ flags: ['Ephemeral'] });
|
||||||
!interaction.memberPermissions?.has(PermissionsBitField.Flags.BanMembers)
|
|
||||||
) {
|
|
||||||
await interaction.reply({
|
|
||||||
content: 'You do not have permission to unban users.',
|
|
||||||
flags: ['Ephemeral'],
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const userId = interaction.options.get('userid')?.value as string;
|
||||||
|
const reason = interaction.options.get('reason')?.value as string;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!interaction.memberPermissions?.has(
|
||||||
|
PermissionsBitField.Flags.BanMembers,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
await interaction.editReply({
|
||||||
|
content: 'You do not have permission to unban users.',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
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(`Unable to unban user: ${error}`);
|
||||||
await interaction.reply({
|
await interaction.editReply({
|
||||||
content: 'Unable to unban user.',
|
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),
|
.setRequired(true),
|
||||||
),
|
),
|
||||||
execute: async (interaction) => {
|
execute: async (interaction) => {
|
||||||
const moderator = await interaction.guild?.members.fetch(
|
if (!interaction.isChatInputCommand() || !interaction.guild) return;
|
||||||
interaction.user.id,
|
|
||||||
);
|
|
||||||
const member = await interaction.guild?.members.fetch(
|
|
||||||
interaction.options.get('member')!.value as unknown as string,
|
|
||||||
);
|
|
||||||
const reason = interaction.options.get('reason')
|
|
||||||
?.value as unknown as string;
|
|
||||||
|
|
||||||
if (
|
await interaction.deferReply({ flags: ['Ephemeral'] });
|
||||||
!interaction.memberPermissions?.has(
|
|
||||||
PermissionsBitField.Flags.ModerateMembers,
|
|
||||||
) ||
|
|
||||||
moderator!.roles.highest.position <= member!.roles.highest.position
|
|
||||||
) {
|
|
||||||
await interaction.reply({
|
|
||||||
content: 'You do not have permission to warn this member.',
|
|
||||||
flags: ['Ephemeral'],
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const moderator = await interaction.guild.members.fetch(
|
||||||
|
interaction.user.id,
|
||||||
|
);
|
||||||
|
const member = await interaction.guild.members.fetch(
|
||||||
|
interaction.options.get('member')!.value as unknown as string,
|
||||||
|
);
|
||||||
|
const reason = interaction.options.getString('reason')!;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!interaction.memberPermissions?.has(
|
||||||
|
PermissionsBitField.Flags.ModerateMembers,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
await interaction.editReply({
|
||||||
|
content: 'You do not have permission to warn members.',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moderator.roles.highest.position <= member.roles.highest.position) {
|
||||||
|
await interaction.editReply({
|
||||||
|
content:
|
||||||
|
'You cannot warn a member with equal or higher role than yours.',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await updateMemberModerationHistory({
|
await updateMemberModerationHistory({
|
||||||
discordId: member!.user.id,
|
discordId: member!.user.id,
|
||||||
moderatorDiscordId: interaction.user.id,
|
moderatorDiscordId: interaction.user.id,
|
||||||
|
@ -54,9 +63,6 @@ const command: OptionsCommand = {
|
||||||
await member!.user.send(
|
await member!.user.send(
|
||||||
`You have been warned in **${interaction?.guild?.name}**. Reason: **${reason}**.`,
|
`You have been warned in **${interaction?.guild?.name}**. Reason: **${reason}**.`,
|
||||||
);
|
);
|
||||||
await interaction.reply(
|
|
||||||
`<@${member!.user.id}> has been warned. Reason: ${reason}`,
|
|
||||||
);
|
|
||||||
await logAction({
|
await logAction({
|
||||||
guild: interaction.guild!,
|
guild: interaction.guild!,
|
||||||
action: 'warn',
|
action: 'warn',
|
||||||
|
@ -64,11 +70,13 @@ const command: OptionsCommand = {
|
||||||
moderator: moderator!,
|
moderator: moderator!,
|
||||||
reason: reason,
|
reason: reason,
|
||||||
});
|
});
|
||||||
|
await interaction.editReply(
|
||||||
|
`<@${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'],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -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'],
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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({
|
||||||
|
|
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')
|
.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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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!');
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -18,12 +18,16 @@ 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',
|
||||||
)?.roleId;
|
)?.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 || '');
|
const hasManagerRole = member?.roles.cache.has(managerRoleId || '');
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -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();
|
||||||
|
|
|
@ -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.`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,21 +19,25 @@ 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 (
|
||||||
!interaction.memberPermissions!.has(
|
!interaction.memberPermissions?.has(
|
||||||
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] });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -133,6 +133,7 @@ export async function updateMember({
|
||||||
discordUsername,
|
discordUsername,
|
||||||
currentlyInServer,
|
currentlyInServer,
|
||||||
currentlyBanned,
|
currentlyBanned,
|
||||||
|
currentlyMuted,
|
||||||
}: schema.memberTableTypes): Promise<void> {
|
}: schema.memberTableTypes): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await ensureDbInitialized();
|
await ensureDbInitialized();
|
||||||
|
@ -147,6 +148,7 @@ export async function updateMember({
|
||||||
discordUsername,
|
discordUsername,
|
||||||
currentlyInServer,
|
currentlyInServer,
|
||||||
currentlyBanned,
|
currentlyBanned,
|
||||||
|
currentlyMuted,
|
||||||
})
|
})
|
||||||
.where(eq(schema.memberTable.discordId, discordId));
|
.where(eq(schema.memberTable.discordId, discordId));
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
|
|
||||||
import { updateMember, setMembers } from '@/db/db.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 { loadConfig } from '@/util/configLoader.js';
|
||||||
import { Event } from '@/types/EventTypes.js';
|
import { Event } from '@/types/EventTypes.js';
|
||||||
import logAction from '@/util/logging/logAction.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) {
|
} catch (error) {
|
||||||
console.error('Error handling member update:', error);
|
console.error('Error handling member update:', error);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
setDiscordClient as setRedisDiscordClient,
|
setDiscordClient as setRedisDiscordClient,
|
||||||
} from '@/db/redis.js';
|
} from '@/db/redis.js';
|
||||||
import { setDiscordClient as setDbDiscordClient } from '@/db/db.js';
|
import { setDiscordClient as setDbDiscordClient } from '@/db/db.js';
|
||||||
|
import { loadActiveBans, loadActiveMutes } from '@/util/helpers.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: Events.ClientReady,
|
name: Events.ClientReady,
|
||||||
|
@ -36,6 +37,9 @@ export default {
|
||||||
const nonBotMembers = members.filter((m) => !m.user.bot);
|
const nonBotMembers = members.filter((m) => !m.user.bot);
|
||||||
await setMembers(nonBotMembers);
|
await setMembers(nonBotMembers);
|
||||||
|
|
||||||
|
await loadActiveBans(client, guild);
|
||||||
|
await loadActiveMutes(client, guild);
|
||||||
|
|
||||||
await scheduleFactOfTheDay(client);
|
await scheduleFactOfTheDay(client);
|
||||||
await scheduleGiveaways(client);
|
await scheduleGiveaways(client);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Client, ClientOptions, Collection } from 'discord.js';
|
import { Client, ClientOptions, Collection } from 'discord.js';
|
||||||
import { Command } from '@/types/CommandTypes.js';
|
import { Command } from '@/types/CommandTypes.js';
|
||||||
import { Config } from '@/types/ConfigTypes.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';
|
import { registerEvents } from '@/util/eventLoader.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,20 +29,73 @@ export class ExtendedClient extends Client {
|
||||||
|
|
||||||
private async loadModules() {
|
private async loadModules() {
|
||||||
try {
|
try {
|
||||||
const commands = await deployCommands();
|
if (process.env.SKIP_COMMAND_DEPLOY === 'true') {
|
||||||
if (!commands?.length) {
|
console.log('Skipping command deployment (SKIP_COMMAND_DEPLOY=true)');
|
||||||
throw new Error('No commands found');
|
const commandFiles = await this.loadCommandsWithoutDeploying();
|
||||||
}
|
|
||||||
|
|
||||||
for (const command of commands) {
|
if (!commandFiles?.length) {
|
||||||
this.commands.set(command.data.name, command);
|
throw new Error('No commands found');
|
||||||
}
|
}
|
||||||
|
|
||||||
await registerEvents(this);
|
await registerEvents(this);
|
||||||
console.log(`Loaded ${commands.length} commands and registered events`);
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const command of commands) {
|
||||||
|
this.commands.set(command.data.name, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
await registerEvents(this);
|
||||||
|
console.log(`Loaded ${commands.length} commands and registered events`);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading modules:', error);
|
console.error('Error loading modules:', error);
|
||||||
process.exit(1);
|
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;
|
token: string;
|
||||||
clientId: string;
|
clientId: string;
|
||||||
guildId: string;
|
guildId: string;
|
||||||
|
serverInvite: string;
|
||||||
database: {
|
database: {
|
||||||
dbConnectionString: string;
|
dbConnectionString: string;
|
||||||
maxRetryAttempts: number;
|
maxRetryAttempts: number;
|
||||||
|
|
|
@ -17,7 +17,7 @@ const rest = new REST({ version: '10' }).setToken(token);
|
||||||
* @param directory - The directory to get files from
|
* @param directory - The directory to get files from
|
||||||
* @returns - An array of file paths
|
* @returns - An array of file paths
|
||||||
*/
|
*/
|
||||||
const getFilesRecursively = (directory: string): string[] => {
|
export const getFilesRecursively = (directory: string): string[] => {
|
||||||
const files: string[] = [];
|
const files: string[] = [];
|
||||||
const filesInDirectory = fs.readdirSync(directory);
|
const filesInDirectory = fs.readdirSync(directory);
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,12 @@ import {
|
||||||
ButtonStyle,
|
ButtonStyle,
|
||||||
ButtonBuilder,
|
ButtonBuilder,
|
||||||
ActionRowBuilder,
|
ActionRowBuilder,
|
||||||
|
DiscordAPIError,
|
||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
import { and, eq } from 'drizzle-orm';
|
import { and, eq } from 'drizzle-orm';
|
||||||
|
|
||||||
import { moderationTable } from '@/db/schema.js';
|
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';
|
import logAction from './logging/logAction.js';
|
||||||
|
|
||||||
const __dirname = path.resolve();
|
const __dirname = path.resolve();
|
||||||
|
@ -116,6 +117,107 @@ export async function generateMemberBanner({
|
||||||
return attachment;
|
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
|
* Schedules an unban for a user
|
||||||
* @param client - The client to use
|
* @param client - The client to use
|
||||||
|
@ -174,7 +276,7 @@ export async function executeUnban(
|
||||||
guild,
|
guild,
|
||||||
action: 'unban',
|
action: 'unban',
|
||||||
target: guild.members.cache.get(userId)!,
|
target: guild.members.cache.get(userId)!,
|
||||||
moderator: guild.members.cache.get(client.user!.id)!,
|
moderator: guild.members.me!,
|
||||||
reason: reason ?? 'Temporary ban expired',
|
reason: reason ?? 'Temporary ban expired',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} 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
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 7.0.6
|
||||||
resolution: "cross-spawn@npm:7.0.6"
|
resolution: "cross-spawn@npm:7.0.6"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -4201,6 +4213,7 @@ __metadata:
|
||||||
"@types/pg": "npm:^8.11.13"
|
"@types/pg": "npm:^8.11.13"
|
||||||
"@typescript-eslint/eslint-plugin": "npm:^8.30.1"
|
"@typescript-eslint/eslint-plugin": "npm:^8.30.1"
|
||||||
"@typescript-eslint/parser": "npm:^8.30.1"
|
"@typescript-eslint/parser": "npm:^8.30.1"
|
||||||
|
cross-env: "npm:^7.0.3"
|
||||||
discord.js: "npm:^14.18.0"
|
discord.js: "npm:^14.18.0"
|
||||||
drizzle-kit: "npm:^0.31.0"
|
drizzle-kit: "npm:^0.31.0"
|
||||||
drizzle-orm: "npm:^0.42.0"
|
drizzle-orm: "npm:^0.42.0"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue