mirror of
https://github.com/ahmadk953/poixpixel-discord-bot.git
synced 2025-05-14 12:43:05 +00:00
Added Basic Leveling System and QoL Updates
This commit is contained in:
parent
7af6d5914d
commit
b5ce514397
15 changed files with 970 additions and 39 deletions
|
@ -74,6 +74,12 @@ const command: SubcommandCommand = {
|
|||
|
||||
execute: async (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
await interaction.deferReply({
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
await interaction.editReply('Processing...');
|
||||
|
||||
const config = loadConfig();
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
|
||||
|
@ -112,13 +118,15 @@ const command: SubcommandCommand = {
|
|||
)
|
||||
.setTimestamp();
|
||||
|
||||
const factId = await getLastInsertedFactId();
|
||||
|
||||
const approveButton = new ButtonBuilder()
|
||||
.setCustomId(`approve_fact_${await getLastInsertedFactId()}`)
|
||||
.setCustomId(`approve_fact_${factId}`)
|
||||
.setLabel('Approve')
|
||||
.setStyle(ButtonStyle.Success);
|
||||
|
||||
const rejectButton = new ButtonBuilder()
|
||||
.setCustomId(`reject_fact_${await getLastInsertedFactId()}`)
|
||||
.setCustomId(`reject_fact_${factId}`)
|
||||
.setLabel('Reject')
|
||||
.setStyle(ButtonStyle.Danger);
|
||||
|
||||
|
@ -136,11 +144,10 @@ const command: SubcommandCommand = {
|
|||
}
|
||||
}
|
||||
|
||||
await interaction.reply({
|
||||
await interaction.editReply({
|
||||
content: isAdmin
|
||||
? 'Your fact has been automatically approved and added to the database!'
|
||||
: 'Your fact has been submitted for approval!',
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
} else if (subcommand === 'approve') {
|
||||
if (
|
||||
|
@ -148,9 +155,8 @@ const command: SubcommandCommand = {
|
|||
PermissionsBitField.Flags.ModerateMembers,
|
||||
)
|
||||
) {
|
||||
await interaction.reply({
|
||||
await interaction.editReply({
|
||||
content: 'You do not have permission to approve facts.',
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -158,9 +164,8 @@ const command: SubcommandCommand = {
|
|||
const id = interaction.options.getInteger('id', true);
|
||||
await approveFact(id);
|
||||
|
||||
await interaction.reply({
|
||||
await interaction.editReply({
|
||||
content: `Fact #${id} has been approved!`,
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
} else if (subcommand === 'delete') {
|
||||
if (
|
||||
|
@ -168,9 +173,8 @@ const command: SubcommandCommand = {
|
|||
PermissionsBitField.Flags.ModerateMembers,
|
||||
)
|
||||
) {
|
||||
await interaction.reply({
|
||||
await interaction.editReply({
|
||||
content: 'You do not have permission to delete facts.',
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -178,9 +182,8 @@ const command: SubcommandCommand = {
|
|||
const id = interaction.options.getInteger('id', true);
|
||||
await deleteFact(id);
|
||||
|
||||
await interaction.reply({
|
||||
await interaction.editReply({
|
||||
content: `Fact #${id} has been deleted!`,
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
} else if (subcommand === 'pending') {
|
||||
if (
|
||||
|
@ -188,9 +191,8 @@ const command: SubcommandCommand = {
|
|||
PermissionsBitField.Flags.ModerateMembers,
|
||||
)
|
||||
) {
|
||||
await interaction.reply({
|
||||
await interaction.editReply({
|
||||
content: 'You do not have permission to view pending facts.',
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -198,9 +200,8 @@ const command: SubcommandCommand = {
|
|||
const pendingFacts = await getPendingFacts();
|
||||
|
||||
if (pendingFacts.length === 0) {
|
||||
await interaction.reply({
|
||||
await interaction.editReply({
|
||||
content: 'There are no pending facts.',
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -217,9 +218,8 @@ const command: SubcommandCommand = {
|
|||
)
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.reply({
|
||||
await interaction.editReply({
|
||||
embeds: [embed],
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
} else if (subcommand === 'post') {
|
||||
if (
|
||||
|
@ -227,18 +227,16 @@ const command: SubcommandCommand = {
|
|||
PermissionsBitField.Flags.Administrator,
|
||||
)
|
||||
) {
|
||||
await interaction.reply({
|
||||
await interaction.editReply({
|
||||
content: 'You do not have permission to manually post facts.',
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await postFactOfTheDay(interaction.client);
|
||||
|
||||
await interaction.reply({
|
||||
await interaction.editReply({
|
||||
content: 'Fact of the day has been posted!',
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
171
src/commands/fun/leaderboard.ts
Normal file
171
src/commands/fun/leaderboard.ts
Normal file
|
@ -0,0 +1,171 @@
|
|||
import {
|
||||
SlashCommandBuilder,
|
||||
EmbedBuilder,
|
||||
ButtonBuilder,
|
||||
ActionRowBuilder,
|
||||
ButtonStyle,
|
||||
StringSelectMenuBuilder,
|
||||
APIEmbed,
|
||||
JSONEncodable,
|
||||
} from 'discord.js';
|
||||
|
||||
import { OptionsCommand } from '../../types/CommandTypes.js';
|
||||
import { getLevelLeaderboard } from '../../db/db.js';
|
||||
|
||||
const command: OptionsCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('leaderboard')
|
||||
.setDescription('Shows the server XP leaderboard')
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName('limit')
|
||||
.setDescription('Number of users per page (default: 10)')
|
||||
.setRequired(false),
|
||||
),
|
||||
execute: async (interaction) => {
|
||||
if (!interaction.guild) return;
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
try {
|
||||
const usersPerPage =
|
||||
(interaction.options.get('limit')?.value as number) || 10;
|
||||
|
||||
const allUsers = await getLevelLeaderboard(100);
|
||||
|
||||
if (allUsers.length === 0) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle('🏆 Server Leaderboard')
|
||||
.setColor(0x5865f2)
|
||||
.setDescription('No users found on the leaderboard yet.')
|
||||
.setTimestamp();
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
}
|
||||
|
||||
const pages: (APIEmbed | JSONEncodable<APIEmbed>)[] = [];
|
||||
|
||||
for (let i = 0; i < allUsers.length; i += usersPerPage) {
|
||||
const pageUsers = allUsers.slice(i, i + usersPerPage);
|
||||
let leaderboardText = '';
|
||||
|
||||
for (let j = 0; j < pageUsers.length; j++) {
|
||||
const user = pageUsers[j];
|
||||
const position = i + j + 1;
|
||||
|
||||
try {
|
||||
const member = await interaction.guild.members.fetch(
|
||||
user.discordId,
|
||||
);
|
||||
leaderboardText += `**${position}.** ${member} - Level ${user.level} (${user.xp} XP)\n`;
|
||||
} catch (error) {
|
||||
leaderboardText += `**${position}.** <@${user.discordId}> - Level ${user.level} (${user.xp} XP)\n`;
|
||||
}
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle('🏆 Server Leaderboard')
|
||||
.setColor(0x5865f2)
|
||||
.setDescription(leaderboardText)
|
||||
.setTimestamp()
|
||||
.setFooter({
|
||||
text: `Page ${Math.floor(i / usersPerPage) + 1} of ${Math.ceil(allUsers.length / usersPerPage)}`,
|
||||
});
|
||||
|
||||
pages.push(embed);
|
||||
}
|
||||
|
||||
let currentPage = 0;
|
||||
|
||||
const getButtonActionRow = () =>
|
||||
new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId('previous')
|
||||
.setLabel('Previous')
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(currentPage === 0),
|
||||
new ButtonBuilder()
|
||||
.setCustomId('next')
|
||||
.setLabel('Next')
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(currentPage === pages.length - 1),
|
||||
);
|
||||
|
||||
const getSelectMenuRow = () => {
|
||||
const options = pages.map((_, index) => ({
|
||||
label: `Page ${index + 1}`,
|
||||
value: index.toString(),
|
||||
default: index === currentPage,
|
||||
}));
|
||||
|
||||
const select = new StringSelectMenuBuilder()
|
||||
.setCustomId('select_page')
|
||||
.setPlaceholder('Jump to a page')
|
||||
.addOptions(options);
|
||||
|
||||
return new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
|
||||
select,
|
||||
);
|
||||
};
|
||||
|
||||
const components =
|
||||
pages.length > 1 ? [getButtonActionRow(), getSelectMenuRow()] : [];
|
||||
|
||||
const message = await interaction.editReply({
|
||||
embeds: [pages[currentPage]],
|
||||
components,
|
||||
});
|
||||
|
||||
if (pages.length <= 1) return;
|
||||
|
||||
const collector = message.createMessageComponentCollector({
|
||||
time: 60000,
|
||||
});
|
||||
|
||||
collector.on('collect', async (i) => {
|
||||
if (i.user.id !== interaction.user.id) {
|
||||
await i.reply({
|
||||
content: 'These controls are not for you!',
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (i.isButton()) {
|
||||
if (i.customId === 'previous' && currentPage > 0) {
|
||||
currentPage--;
|
||||
} else if (i.customId === 'next' && currentPage < pages.length - 1) {
|
||||
currentPage++;
|
||||
}
|
||||
}
|
||||
|
||||
if (i.isStringSelectMenu()) {
|
||||
const selected = parseInt(i.values[0]);
|
||||
if (!isNaN(selected) && selected >= 0 && selected < pages.length) {
|
||||
currentPage = selected;
|
||||
}
|
||||
}
|
||||
|
||||
await i.update({
|
||||
embeds: [pages[currentPage]],
|
||||
components: [getButtonActionRow(), getSelectMenuRow()],
|
||||
});
|
||||
});
|
||||
|
||||
collector.on('end', async () => {
|
||||
if (message) {
|
||||
try {
|
||||
await interaction.editReply({ components: [] });
|
||||
} catch (error) {
|
||||
console.error('Error removing components:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error getting leaderboard:', error);
|
||||
await interaction.editReply('Failed to get leaderboard information.');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default command;
|
49
src/commands/fun/rank.ts
Normal file
49
src/commands/fun/rank.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { GuildMember, SlashCommandBuilder } from 'discord.js';
|
||||
|
||||
import { OptionsCommand } from '../../types/CommandTypes.js';
|
||||
import {
|
||||
generateRankCard,
|
||||
getXpToNextLevel,
|
||||
} from '../../util/levelingSystem.js';
|
||||
import { getUserLevel } from '../../db/db.js';
|
||||
|
||||
const command: OptionsCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('rank')
|
||||
.setDescription('Shows your current rank and level')
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('The user to check rank for (defaults to yourself)')
|
||||
.setRequired(false),
|
||||
),
|
||||
execute: async (interaction) => {
|
||||
const member = await interaction.guild?.members.fetch(
|
||||
(interaction.options.get('user')?.value as string) || interaction.user.id,
|
||||
);
|
||||
|
||||
if (!member) {
|
||||
await interaction.reply('User not found in this server.');
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
try {
|
||||
const userData = await getUserLevel(member.id);
|
||||
const rankCard = await generateRankCard(member, userData);
|
||||
|
||||
const xpToNextLevel = getXpToNextLevel(userData.level, userData.xp);
|
||||
|
||||
await interaction.editReply({
|
||||
content: `${member}'s rank - Level ${userData.level} (${userData.xp} XP, ${xpToNextLevel} XP until next level)`,
|
||||
files: [rankCard],
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error getting rank:', error);
|
||||
await interaction.editReply('Failed to get rank information.');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default command;
|
Loading…
Add table
Add a link
Reference in a new issue