diff --git a/src/commandDetails/coin/leaderboard.ts b/src/commandDetails/coin/leaderboard.ts new file mode 100644 index 00000000..d8251395 --- /dev/null +++ b/src/commandDetails/coin/leaderboard.ts @@ -0,0 +1,77 @@ +import { container, SapphireClient } from '@sapphire/framework'; +import { MessageEmbed } from 'discord.js'; +import { + CodeyCommandDetails, + CodeyCommandResponseType, + getUserIdFromMessage, + SapphireMessageExecuteType, + SapphireMessageResponse +} from '../../codeyCommand'; +import { + getCurrentCoinLeaderboard, + getCoinBalanceByUserId, + UserCoinEntry, + getUserIdCurrentCoinPosition +} from '../../components/coin'; +import { EMBED_COLOUR } from '../../utils/embeds'; + +// How many people are shown on the leaderboard +const limit = 10; + +const getCurrentCoinLeaderboardEmbed = async ( + client: SapphireClient, + leaderboard: UserCoinEntry[], + currentUserId: string +): Promise => { + // Initialise user's coin balance if they have not already + const userBalance = await getCoinBalanceByUserId(currentUserId); + const currentPosition = await getUserIdCurrentCoinPosition(currentUserId); + + let currentLeaderboardText = ''; + for (let i = 0; i < limit; i++) { + if (leaderboard.length <= i) break; + const userCoinEntry = leaderboard[i]; + const user = await client.users.fetch(userCoinEntry.user_id); + const userTag = user?.tag ?? ''; + const userCoinEntryText = `${i + 1}. ${userTag} - ${userCoinEntry.balance} 🪙\n`; + currentLeaderboardText += userCoinEntryText; + } + const currentLeaderboardEmbed = new MessageEmbed() + .setColor(EMBED_COLOUR) + .setTitle('CodeyCoin Leaderboard') + .setDescription(currentLeaderboardText); + + currentLeaderboardEmbed.addFields({ + name: 'Your Position', + value: `You are currently **#${currentPosition}** in the leaderboard with ${userBalance} 🪙.` + }); + + return currentLeaderboardEmbed; +}; + +const coinCurrentLeaderboardExecuteCommand: SapphireMessageExecuteType = async ( + client, + messageFromUser, + _args +): Promise => { + const userId = getUserIdFromMessage(messageFromUser); + const leaderboard = await getCurrentCoinLeaderboard(); + return getCurrentCoinLeaderboardEmbed(client, leaderboard, userId); +}; + +export const coinCurrentLeaderboardCommandDetails: CodeyCommandDetails = { + name: 'leaderboard', + aliases: ['lb'], + description: 'Get the current coin leaderboard.', + detailedDescription: `**Examples:** +\`${container.botPrefix}coin lb\` +\`${container.botPrefix}coin leaderboard\``, + + isCommandResponseEphemeral: false, + messageWhenExecutingCommand: 'Getting the current coin leaderboard...', + executeCommand: coinCurrentLeaderboardExecuteCommand, + codeyCommandResponseType: CodeyCommandResponseType.EMBED, + + options: [], + subcommandDetails: {} +}; diff --git a/src/commands/coin/coin.ts b/src/commands/coin/coin.ts index 31921ce5..46c1b5cc 100644 --- a/src/commands/coin/coin.ts +++ b/src/commands/coin/coin.ts @@ -5,6 +5,7 @@ import { coinBalanceCommandDetails } from '../../commandDetails/coin/balance'; import { coinCheckCommandDetails } from '../../commandDetails/coin/check'; import { coinInfoCommandDetails } from '../../commandDetails/coin/info'; import { coinUpdateCommandDetails } from '../../commandDetails/coin/update'; +import { coinCurrentLeaderboardCommandDetails } from '../../commandDetails/coin/leaderboard'; const coinCommandDetails: CodeyCommandDetails = { name: 'coin', @@ -28,7 +29,8 @@ const coinCommandDetails: CodeyCommandDetails = { balance: coinBalanceCommandDetails, check: coinCheckCommandDetails, info: coinInfoCommandDetails, - update: coinUpdateCommandDetails + update: coinUpdateCommandDetails, + leaderboard: coinCurrentLeaderboardCommandDetails }, defaultSubcommandDetails: coinBalanceCommandDetails }; diff --git a/src/components/coin.ts b/src/components/coin.ts index 5301f778..262f6c7b 100644 --- a/src/components/coin.ts +++ b/src/components/coin.ts @@ -61,6 +61,11 @@ export const coinBonusMap = new Map([ ] ]); +export interface UserCoinEntry { + user_id: string; + balance: number; +} + export interface UserCoinBonus { id: string; user_id: string; @@ -135,6 +140,39 @@ export const changeDbCoinBalanceByUserId = async ( await createCoinLedgerEntry(userId, oldBalance, newBalance, event, reason, adminId); }; +/* + Get the leaderboard for the current coin amounts. +*/ +export const getCurrentCoinLeaderboard = async (limit = 10): Promise => { + const db = await openDB(); + const res = await db.all( + ` + SELECT user_id, balance + FROM user_coin + ORDER BY balance DESC + LIMIT ? + `, + limit + ); + return res; +}; + +/* + Get the position for a user id's balance +*/ +export const getUserIdCurrentCoinPosition = async (userId: string): Promise => { + const db = await openDB(); + const res = await db.get( + ` + SELECT COUNT(user_id) AS count + FROM user_coin + WHERE balance >= ? + `, + await getCoinBalanceByUserId(userId) + ); + return _.get(res, 'count', 0); +}; + /* Adds an entry to the Codey coin ledger due to a change in a user's coin balance. reason is only applicable for admin commands and is optional.