Skip to content

Commit 03d759d

Browse files
authored
Merge pull request #17 from ToastedDev/dev
feat(commands): improve /xp
2 parents 01c3363 + f028869 commit 03d759d

File tree

4 files changed

+114
-28
lines changed

4 files changed

+114
-28
lines changed

bot/commands.ts

Lines changed: 109 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
// Commands taken from https://github.com/NiaAxern/discord-youtube-subscriber-count/blob/main/src/commands/utilities.ts
22

33
import client from '.';
4-
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, type CommandInteraction, ChannelType, type APIApplicationCommandOption } from 'discord.js';
4+
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, type CommandInteraction, ChannelType, type APIApplicationCommandOption, GuildMember, AttachmentBuilder, ComponentType } from 'discord.js';
55
import { heapStats } from 'bun:jsc';
66
import { getGuildLeaderboard, makeGETRequest, getRoles, removeRole, addRole, enableUpdates, disableUpdates, getCooldown, setCooldown, checkIfGuildHasUpdatesEnabled } from './utils/requestAPI';
77
import convertToLevels from './utils/convertToLevels';
88
import quickEmbed from './utils/quickEmbed';
9+
import { Font, RankCardBuilder } from 'canvacord';
10+
11+
Font.loadDefault();
912

1013
interface Command {
1114
data: {
@@ -122,58 +125,136 @@ const commands: Record<string, Command> = {
122125
},
123126
xp: {
124127
data: {
125-
options: [],
128+
options: [{
129+
name: 'user',
130+
description: 'The user you want to check the XP of.',
131+
type: 6,
132+
required: false,
133+
}],
126134
name: 'xp',
127135
description: 'Get your XP and Points',
128136
integration_types: [0],
129137
contexts: [0, 2],
130138
},
131139
execute: async (interaction) => {
132-
if (interaction?.guildId) {
133-
const guild = interaction.guild?.id
134-
const user = interaction.user.id
135-
const xp = await makeGETRequest(guild as string, user)
140+
await interaction.deferReply()
136141

137-
if (!xp) {
138-
await interaction.reply({
139-
ephemeral: true,
140-
content: "No XP data available."
141-
});
142-
return;
143-
}
144-
145-
const progress = xp.user_progress_next_level;
146-
const progressBar = createProgressBar(progress);
142+
const optionUser = interaction.options.get('user')?.value as string | null;
143+
const member = (optionUser ? interaction.guild!.members.cache.get(optionUser) : interaction.member) as GuildMember;
144+
await interaction.guild!.members.fetch({ user: member.id, force: true })
145+
const guild = interaction.guild?.id
146+
const user = member.id;
147+
const leaderboard = await getGuildLeaderboard(guild as string);
148+
const xp = await makeGETRequest(guild as string, user)
147149

150+
if (!xp || leaderboard.length === 0) {
148151
await interaction.reply({
149-
embeds: [
152+
ephemeral: true,
153+
content: "No XP data available."
154+
});
155+
return;
156+
}
157+
158+
const rank = leaderboard.leaderboard.findIndex((entry: ({ id: string; })) => entry.id === user) + 1;
159+
160+
const card = new RankCardBuilder()
161+
.setDisplayName(member.displayName)
162+
.setAvatar(member.displayAvatarURL({ forceStatic: true, size: 4096 })) // user avatar
163+
.setCurrentXP(xp.xp) // current xp
164+
.setRequiredXP(xp.xp_needed_next_level) // required xp
165+
.setLevel(xp.level) // user level
166+
.setRank(rank) // user rank
167+
.setOverlay(member.user.banner ? 95 : 90) // overlay percentage. Overlay is a semi-transparent layer on top of the background
168+
.setBackground(member.user.bannerURL({ forceStatic: true, size: 4096 }) ?? "#23272a")
169+
170+
if (interaction.user.discriminator !== "0") {
171+
card.setUsername("#" + member.user.discriminator)
172+
} else {
173+
card.setUsername("@" + member.user.username)
174+
}
175+
176+
const color = member.roles.highest.hexColor ?? "#ffffff"
177+
178+
card.setStyles({
179+
progressbar: {
180+
thumb: {
181+
style: {
182+
backgroundColor: color
183+
}
184+
}
185+
},
186+
})
187+
188+
const image = await card.build({
189+
format: "png"
190+
});
191+
const attachment = new AttachmentBuilder(image, { name: `${user}.png` });
192+
193+
const msg = await interaction.followUp({
194+
files: [attachment],
195+
components: [
196+
new ActionRowBuilder<ButtonBuilder>().setComponents(
197+
new ButtonBuilder()
198+
.setCustomId("text-mode")
199+
.setLabel("Use text mode")
200+
.setStyle(ButtonStyle.Secondary)
201+
)
202+
],
203+
fetchReply: true
204+
});
205+
206+
const collector = msg.createMessageComponentCollector({
207+
componentType: ComponentType.Button,
208+
time: 60 * 1000
209+
});
210+
211+
collector.on("collect", async (i) => {
212+
if (i.user.id !== user)
213+
return i.reply({
214+
content: "You're not the one who initialized this message! Try running /xp on your own.",
215+
ephemeral: true
216+
});
217+
218+
if (i.customId !== "text-mode") return;
219+
220+
const progress = xp.progress_next_level;
221+
const progressBar = createProgressBar(progress);
222+
223+
await i.update({
224+
embeds: [
150225
quickEmbed(
151226
{
152-
color: 'Blurple',
227+
color,
153228
title: 'XP',
154-
description: `<@${user}> you have ${xp.xp.toLocaleString("en-US")} XP! (Level ${convertToLevels(xp.xp).toLocaleString("en-US")})`,
229+
description: `<@${user}> you have ${xp.xp.toLocaleString()} XP! (Level ${convertToLevels(xp.xp)})`,
155230
},
156231
interaction
157232
).addFields([
233+
{
234+
name: 'Rank',
235+
value: `#${rank.toLocaleString()}`,
236+
},
158237
{
159238
name: 'Progress To Next Level',
160239
value: `${progressBar} ${progress}%`,
161240
inline: true,
162241
},
163242
{
164243
name: 'XP Required',
165-
value: `${xp.user_xp_needed_next_level.toLocaleString("en-US")} XP`,
244+
value: `${xp.xp_needed_next_level.toLocaleString()} XP`,
166245
inline: true,
167246
},
168247
]),
169248
],
170-
});
171-
172-
function createProgressBar(progress: number): string {
173-
const filled = Math.floor(progress / 10);
174-
const empty = 10 - filled;
175-
return '▰'.repeat(filled) + '▱'.repeat(empty);
176-
}
249+
files: [],
250+
components: []
251+
})
252+
})
253+
254+
function createProgressBar(progress: number): string {
255+
const filled = Math.floor(progress / 10);
256+
const empty = 10 - filled;
257+
return '▰'.repeat(filled) + '▱'.repeat(empty);
177258
}
178259
}
179260
},
@@ -487,7 +568,7 @@ const commands: Record<string, Command> = {
487568

488569
const action = interaction.options.get('action')?.value;
489570
const cooldown = interaction.options.get('cooldown')?.value;
490-
571+
491572
let cooldownData;
492573
let apiSuccess;
493574

bot/types.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
declare module "colorthief" {
2+
function getColor(url: string): Promise<[number, number, number]>;
3+
}

bun.lockb

46.8 KB
Binary file not shown.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
"typescript": "^5.0.0"
2222
},
2323
"dependencies": {
24+
"canvacord": "^6.0.2",
25+
"colorthief": "^2.4.0",
2426
"cors": "^2.8.5",
2527
"discord.js": "^14.15.3",
2628
"ejs": "^3.1.10",

0 commit comments

Comments
 (0)