mirror of
https://github.com/ahmadk953/poixpixel-discord-bot.git
synced 2025-05-10 10:43:06 +00:00
feat: added config and help commands
This commit is contained in:
parent
20af09b279
commit
b615d25964
3 changed files with 507 additions and 1 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
|
||||||
|
|
||||||
|
|
235
src/commands/util/config.ts
Normal file
235
src/commands/util/config.ts
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
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.memberPermissions?.has(PermissionFlagsBits.Administrator)
|
||||||
|
) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: 'You do not have permission to use this command.',
|
||||||
|
flags: ['Ephemeral'],
|
||||||
|
});
|
||||||
|
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.reply({
|
||||||
|
embeds: [pages[currentPage]],
|
||||||
|
components,
|
||||||
|
flags: ['Ephemeral'],
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
271
src/commands/util/help.ts
Normal file
271
src/commands/util/help.ts
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
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) => {
|
||||||
|
try {
|
||||||
|
await interaction.deferReply();
|
||||||
|
|
||||||
|
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.reply({
|
||||||
|
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.reply({ 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;
|
Loading…
Add table
Add a link
Reference in a new issue