|
1 |
| -import { APIApplicationCommandOptionChoice, GuildMember } from 'discord.js' |
| 1 | +import {APIApplicationCommandOptionChoice, GuildMember} from 'discord.js' |
2 | 2 |
|
3 |
| -import { DDUser } from '../../store/models/DDUser.js' |
4 |
| -import { Command } from 'djs-slash-helper' |
5 |
| -import { ApplicationCommandOptionType, ApplicationCommandType } from 'discord-api-types/v10' |
6 |
| -import { createStandardEmbed } from '../../util/embeds.js' |
7 |
| -import { branding } from '../../util/branding.js' |
8 |
| -import { actualMention } from '../../util/users.js' |
9 |
| -import { getActualDailyStreak } from './dailyReward.command.js' |
10 |
| -import { wrapInTransaction } from '../../sentry.js' |
| 3 | +import {DDUser} from '../../store/models/DDUser.js' |
| 4 | +import {Command} from 'djs-slash-helper' |
| 5 | +import {ApplicationCommandOptionType, ApplicationCommandType} from 'discord-api-types/v10' |
| 6 | +import {createStandardEmbed} from '../../util/embeds.js' |
| 7 | +import {branding} from '../../util/branding.js' |
| 8 | +import {actualMention} from '../../util/users.js' |
| 9 | +import {getActualDailyStreak} from './dailyReward.command.js' |
| 10 | +import {wrapInTransaction} from '../../sentry.js' |
11 | 11 |
|
12 | 12 | type KeysMatching<T, V> = { [K in keyof T]-?: T[K] extends V ? K : never }[keyof T]
|
13 | 13 |
|
14 | 14 | interface LeaderboardType extends APIApplicationCommandOptionChoice<string> {
|
15 |
| - calculate?: (user: DDUser) => Promise<number> |
16 |
| - value: KeysMatching<DDUser, number | bigint> |
| 15 | + calculate?: (user: DDUser) => Promise<number> |
| 16 | + value: KeysMatching<DDUser, number | bigint> |
17 | 17 |
|
18 |
| - format: (value: number | bigint) => string |
| 18 | + format: (value: number | bigint) => string |
19 | 19 | }
|
20 | 20 |
|
21 | 21 | const info: LeaderboardType[] = [
|
22 |
| - { |
23 |
| - value: 'xp', |
24 |
| - name: 'XP', |
25 |
| - format: (value) => `${value.toLocaleString()} XP` |
26 |
| - }, |
27 |
| - { |
28 |
| - value: 'level', |
29 |
| - name: 'Level', |
30 |
| - format: (value) => `Level ${value}` |
31 |
| - }, |
32 |
| - { |
33 |
| - value: 'currentDailyStreak', |
34 |
| - calculate: async (user) => await getActualDailyStreak(user), |
35 |
| - name: 'Current Daily Streak', |
36 |
| - format: (s) => `${formatDays(s)}` |
37 |
| - }, |
38 |
| - { |
39 |
| - value: 'highestDailyStreak', |
40 |
| - name: 'Highest Daily Streak', |
41 |
| - format: (s) => `${formatDays(s)}` |
42 |
| - } |
| 22 | + { |
| 23 | + value: 'xp', |
| 24 | + name: 'XP', |
| 25 | + format: (value) => `${value.toLocaleString()} XP` |
| 26 | + }, |
| 27 | + { |
| 28 | + value: 'level', |
| 29 | + name: 'Level', |
| 30 | + format: (value) => `Level ${value}` |
| 31 | + }, |
| 32 | + { |
| 33 | + value: 'currentDailyStreak', |
| 34 | + calculate: async (user) => await getActualDailyStreak(user), |
| 35 | + name: 'Current Daily Streak', |
| 36 | + format: (s) => `${formatDays(s)}` |
| 37 | + }, |
| 38 | + { |
| 39 | + value: 'highestDailyStreak', |
| 40 | + name: 'Highest Daily Streak', |
| 41 | + format: (s) => `${formatDays(s)}` |
| 42 | + }, |
| 43 | + { |
| 44 | + value: 'bumps', |
| 45 | + name: 'Disboard Bumps', |
| 46 | + format: (value) => value == 1 ? "1 Bump" : `${value.toLocaleString()} Bumps` |
| 47 | + } |
43 | 48 | ]
|
44 | 49 |
|
45 | 50 | export const LeaderboardCommand: Command<ApplicationCommandType.ChatInput> = {
|
46 |
| - type: ApplicationCommandType.ChatInput, |
47 |
| - name: 'leaderboard', |
48 |
| - description: 'Show the top 10 users based on XP, Level, or Daily Streak', |
49 |
| - options: [ |
50 |
| - { |
51 |
| - type: ApplicationCommandOptionType.String, |
52 |
| - name: 'type', |
53 |
| - description: 'The type of leaderboard to show', |
54 |
| - required: true, |
55 |
| - choices: info |
56 |
| - } |
57 |
| - ], |
| 51 | + type: ApplicationCommandType.ChatInput, |
| 52 | + name: 'leaderboard', |
| 53 | + description: 'Show the top 10 users based on XP, Level, or Daily Streak', |
| 54 | + options: [ |
| 55 | + { |
| 56 | + type: ApplicationCommandOptionType.String, |
| 57 | + name: 'type', |
| 58 | + description: 'The type of leaderboard to show', |
| 59 | + required: true, |
| 60 | + choices: info |
| 61 | + } |
| 62 | + ], |
58 | 63 |
|
59 |
| - handle: wrapInTransaction('leaderboard', async (span, interaction) => { |
60 |
| - await interaction.deferReply() |
61 |
| - const guild = interaction.guild |
62 |
| - if (guild == null) { |
63 |
| - await interaction.followUp('This command can only be used in a server') |
64 |
| - return |
65 |
| - } |
66 |
| - const option = interaction.options.get('type', true).value as string |
67 |
| - const traitInfo = info.find((it) => it.value === option) |
68 |
| - if (traitInfo == null) { |
69 |
| - await interaction.followUp('Invalid leaderboard type') |
70 |
| - return |
71 |
| - } |
72 |
| - if (traitInfo.value === 'currentDailyStreak') { |
73 |
| - // manually refresh all the dailies. this is not very efficient |
74 |
| - const all = await DDUser.findAll() |
75 |
| - await Promise.all(all.map(getActualDailyStreak)) |
76 |
| - } |
77 |
| - const { |
78 |
| - format, |
79 |
| - value, |
80 |
| - name |
81 |
| - } = traitInfo |
| 64 | + handle: wrapInTransaction('leaderboard', async (span, interaction) => { |
| 65 | + await interaction.deferReply() |
| 66 | + const guild = interaction.guild |
| 67 | + if (guild == null) { |
| 68 | + await interaction.followUp('This command can only be used in a server') |
| 69 | + return |
| 70 | + } |
| 71 | + const option = interaction.options.get('type', true).value as string |
| 72 | + const traitInfo = info.find((it) => it.value === option) |
| 73 | + if (traitInfo == null) { |
| 74 | + await interaction.followUp('Invalid leaderboard type') |
| 75 | + return |
| 76 | + } |
| 77 | + if (traitInfo.value === 'currentDailyStreak') { |
| 78 | + // manually refresh all the dailies. this is not very efficient |
| 79 | + const all = await DDUser.findAll() |
| 80 | + await Promise.all(all.map(getActualDailyStreak)) |
| 81 | + } |
| 82 | + const { |
| 83 | + format, |
| 84 | + value, |
| 85 | + name |
| 86 | + } = traitInfo |
82 | 87 |
|
83 |
| - const calculate: (user: DDUser) => Promise<number | bigint> = traitInfo.calculate ?? |
84 |
| - (async (user: DDUser) => user[value]) |
| 88 | + const calculate: (user: DDUser) => Promise<number | bigint> = traitInfo.calculate ?? |
| 89 | + (async (user: DDUser) => user[value]) |
85 | 90 |
|
86 |
| - const users = await DDUser.findAll({ |
87 |
| - order: [[value, 'DESC']], |
88 |
| - limit: 10 |
89 |
| - }).then((users) => users.filter(async (it) => await calculate(it) > 0)) |
| 91 | + const users = await DDUser.findAll({ |
| 92 | + order: [[value, 'DESC']], |
| 93 | + limit: 10 |
| 94 | + }).then((users) => users.filter(async (it) => await calculate(it) > 0)) |
90 | 95 |
|
91 |
| - if (users.length === 0) { |
92 |
| - await interaction.followUp('No applicable users') |
93 |
| - return |
94 |
| - } |
95 |
| - const embed = { |
96 |
| - ...createStandardEmbed(interaction.member as GuildMember), |
97 |
| - title: `${branding.name} Leaderboard`, |
98 |
| - description: `The top ${users.length} users based on ${name}`, |
99 |
| - fields: await Promise.all(users.map(async (user, index) => { |
100 |
| - const discordUser = await guild.client.users.fetch(user.id.toString()) |
101 |
| - .catch(() => null) |
| 96 | + if (users.length === 0) { |
| 97 | + await interaction.followUp('No applicable users') |
| 98 | + return |
| 99 | + } |
| 100 | + const embed = { |
| 101 | + ...createStandardEmbed(interaction.member as GuildMember), |
| 102 | + title: `${branding.name} Leaderboard`, |
| 103 | + description: `The top ${users.length} users based on ${name}`, |
| 104 | + fields: await Promise.all(users.map(async (user, index) => { |
| 105 | + const discordUser = await guild.client.users.fetch(user.id.toString()) |
| 106 | + .catch(() => null) |
102 | 107 |
|
103 |
| - return { |
104 |
| - name: `${medal(index)} #${index + 1} - ${format(await calculate(user))}`.trimStart(), |
105 |
| - value: discordUser == null |
106 |
| - ? 'Unknown User' |
107 |
| - : actualMention( |
108 |
| - discordUser |
109 |
| - ) |
| 108 | + return { |
| 109 | + name: `${medal(index)} #${index + 1} - ${format(await calculate(user))}`.trimStart(), |
| 110 | + value: discordUser == null |
| 111 | + ? 'Unknown User' |
| 112 | + : actualMention( |
| 113 | + discordUser |
| 114 | + ) |
| 115 | + } |
| 116 | + })) |
110 | 117 | }
|
111 |
| - })) |
112 |
| - } |
113 |
| - await interaction.followUp({ embeds: [embed] }) |
114 |
| - }) |
| 118 | + await interaction.followUp({embeds: [embed]}) |
| 119 | + }) |
115 | 120 | }
|
116 | 121 |
|
117 |
| -function medal (index: number): string { |
118 |
| - switch (index) { |
119 |
| - case 0: |
120 |
| - return '🥇' |
121 |
| - case 1: |
122 |
| - return '🥈' |
123 |
| - case 2: |
124 |
| - return '🥉' |
125 |
| - default: |
126 |
| - return '' |
127 |
| - } |
| 122 | +function medal(index: number): string { |
| 123 | + switch (index) { |
| 124 | + case 0: |
| 125 | + return '🥇' |
| 126 | + case 1: |
| 127 | + return '🥈' |
| 128 | + case 2: |
| 129 | + return '🥉' |
| 130 | + default: |
| 131 | + return '' |
| 132 | + } |
128 | 133 | }
|
129 | 134 |
|
130 |
| -function formatDays (days: number | bigint) { |
131 |
| - if (days === 1) { |
132 |
| - return '1 day' |
133 |
| - } |
134 |
| - return `${days} days` |
| 135 | +function formatDays(days: number | bigint) { |
| 136 | + if (days === 1) { |
| 137 | + return '1 day' |
| 138 | + } |
| 139 | + return `${days} days` |
135 | 140 | }
|
0 commit comments