- Added new models for leveling and economy functionalities in the Prisma schema, including LevelingConfig, MemberLevel, LevelReward, EconomyConfig, MemberEconomy, ShopItem, and InventoryItem. - Introduced commands for managing XP, economy transactions, and shop interactions, enhancing user engagement. - Updated job handling to include reminders and poll closing functionalities. - Enhanced localization support for new commands and features in both German and English. - Improved the bot's job processing capabilities with a dedicated reminder worker for scheduled tasks.
161 lines
5.6 KiB
TypeScript
161 lines
5.6 KiB
TypeScript
import { AttachmentBuilder, EmbedBuilder, PermissionFlagsBits } from 'discord.js';
|
|
import { t, tf } from '@nexumi/shared';
|
|
import type { SlashCommand } from '../../types.js';
|
|
import { getGuildLocale } from '../../i18n.js';
|
|
import { requirePermission } from '../../permissions.js';
|
|
import {
|
|
addXp,
|
|
formatLeaderboardLine,
|
|
getConfig,
|
|
getLeaderboard,
|
|
getOrCreateMemberLevel,
|
|
getRankPosition,
|
|
memberProgress,
|
|
removeXp,
|
|
resetXp
|
|
} from './service.js';
|
|
import { renderRankCard } from './rank-card.js';
|
|
import {
|
|
leaderboardCommandData,
|
|
rankCommandData,
|
|
xpCommandData
|
|
} from './command-definitions.js';
|
|
|
|
const rankCommand: SlashCommand = {
|
|
data: rankCommandData,
|
|
async execute(interaction, context) {
|
|
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
|
if (!interaction.inGuild() || !interaction.guild) {
|
|
await interaction.reply({ content: t(locale, 'generic.guildOnly'), ephemeral: true });
|
|
return;
|
|
}
|
|
|
|
const targetUser = interaction.options.getUser('user') ?? interaction.user;
|
|
const member = await interaction.guild.members.fetch(targetUser.id).catch(() => null);
|
|
if (!member) {
|
|
await interaction.reply({ content: t(locale, 'leveling.user.notFound'), ephemeral: true });
|
|
return;
|
|
}
|
|
|
|
const config = await getConfig(context.prisma, interaction.guild.id);
|
|
const memberLevel = await getOrCreateMemberLevel(context.prisma, interaction.guild.id, member.id);
|
|
const rank = await getRankPosition(context.prisma, interaction.guild.id, member.id);
|
|
const progress = memberProgress(memberLevel);
|
|
|
|
const buffer = await renderRankCard({
|
|
displayName: member.displayName,
|
|
avatarUrl: member.user.displayAvatarURL({ extension: 'png', size: 256 }),
|
|
level: progress.level,
|
|
rank,
|
|
intoLevel: progress.intoLevel,
|
|
needed: progress.needed,
|
|
totalXp: memberLevel.xp,
|
|
accentColor: config.cardAccentColor,
|
|
backgroundColor: config.cardBackgroundColor
|
|
});
|
|
|
|
const attachment = new AttachmentBuilder(buffer, { name: 'rank.png' });
|
|
await interaction.reply({ files: [attachment] });
|
|
}
|
|
};
|
|
|
|
const leaderboardCommand: SlashCommand = {
|
|
data: leaderboardCommandData,
|
|
async execute(interaction, context) {
|
|
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
|
if (!interaction.inGuild() || !interaction.guild) {
|
|
await interaction.reply({ content: t(locale, 'generic.guildOnly'), ephemeral: true });
|
|
return;
|
|
}
|
|
|
|
const entries = await getLeaderboard(context.prisma, interaction.guild.id, 10);
|
|
if (entries.length === 0) {
|
|
await interaction.reply({ content: t(locale, 'leveling.leaderboard.empty'), ephemeral: true });
|
|
return;
|
|
}
|
|
|
|
const lines = await Promise.all(
|
|
entries.map(async (entry, index) => {
|
|
const user = await context.client.users.fetch(entry.userId).catch(() => null);
|
|
const label = user ? `<@${entry.userId}>` : entry.userId;
|
|
return formatLeaderboardLine(locale, index + 1, label, entry.level, entry.xp);
|
|
})
|
|
);
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setTitle(t(locale, 'leveling.leaderboard.title'))
|
|
.setDescription(lines.join('\n'))
|
|
.setFooter({ text: tf(locale, 'leveling.leaderboard.footer', { count: entries.length }) })
|
|
.setColor(0x6366f1);
|
|
|
|
await interaction.reply({ embeds: [embed] });
|
|
}
|
|
};
|
|
|
|
const xpCommand: SlashCommand = {
|
|
data: xpCommandData,
|
|
async execute(interaction, context) {
|
|
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
|
if (!interaction.inGuild() || !interaction.guild) {
|
|
await interaction.reply({ content: t(locale, 'generic.guildOnly'), ephemeral: true });
|
|
return;
|
|
}
|
|
if (!(await requirePermission(interaction, PermissionFlagsBits.ManageGuild, locale))) {
|
|
return;
|
|
}
|
|
|
|
const sub = interaction.options.getSubcommand();
|
|
const user = interaction.options.getUser('user', true);
|
|
const member = await interaction.guild.members.fetch(user.id).catch(() => null);
|
|
if (!member) {
|
|
await interaction.reply({ content: t(locale, 'leveling.user.notFound'), ephemeral: true });
|
|
return;
|
|
}
|
|
|
|
const channelId = interaction.channelId ?? undefined;
|
|
|
|
if (sub === 'give') {
|
|
const amount = interaction.options.getInteger('amount', true);
|
|
const result = await addXp(context, interaction.guild, member, amount, {
|
|
channelId,
|
|
force: true
|
|
});
|
|
const level = result?.newLevel ?? (await getOrCreateMemberLevel(context.prisma, interaction.guild.id, member.id)).level;
|
|
await interaction.reply({
|
|
content: tf(locale, 'leveling.xp.give.success', {
|
|
amount,
|
|
user: `<@${member.id}>`,
|
|
level
|
|
}),
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (sub === 'remove') {
|
|
const amount = interaction.options.getInteger('amount', true);
|
|
const result = await removeXp(context, interaction.guild, member, amount, channelId);
|
|
const level =
|
|
result?.newLevel ??
|
|
(await getOrCreateMemberLevel(context.prisma, interaction.guild.id, member.id)).level;
|
|
await interaction.reply({
|
|
content: tf(locale, 'leveling.xp.remove.success', {
|
|
amount,
|
|
user: `<@${member.id}>`,
|
|
level
|
|
}),
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
await resetXp(context, interaction.guild, member, channelId);
|
|
await interaction.reply({
|
|
content: tf(locale, 'leveling.xp.reset.success', { user: `<@${member.id}>` }),
|
|
ephemeral: true
|
|
});
|
|
}
|
|
};
|
|
|
|
export const levelingCommands: SlashCommand[] = [rankCommand, leaderboardCommand, xpCommand];
|