feat(discord-bot): change bot prefix and improve quest results interaction
Some checks failed
Build and Push Docker Image / typecheck (push) Failing after 48s
Build and Push Docker Image / build (push) Has been skipped

This commit is contained in:
2026-05-12 16:01:14 +02:00
parent 528fff3a5b
commit 8629cf246b
3 changed files with 59 additions and 70 deletions

View File

@@ -1,4 +1,4 @@
import { ChannelType, EmbedBuilder, type Client, type Message, type MessageCreateOptions } from "discord.js";
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType, ComponentType, EmbedBuilder, StringSelectMenuBuilder, type Client, type MessageCreateOptions } from "discord.js";
import { createLogger } from "@lbf-bot/utils";
import { env } from "~/env";
import { checkForNewQuest, type QuestResult } from "~/wov";
@@ -6,8 +6,6 @@ import { getAccountBalance, setAccountBalance } from "~/economy";
const logger = createLogger({ prefix: "quests" });
// --- rewards ---
export const makeResultEmbed = async (result: QuestResult, exclude: Array<string>): Promise<MessageCreateOptions> => {
const imageUrl = result.quest.promoImageUrl;
const color = parseInt(result.quest.promoImagePrimaryColor.substring(1), 16);
@@ -34,7 +32,7 @@ export const makeResultEmbed = async (result: QuestResult, exclude: Array<string
rewardsEmbed = new EmbedBuilder()
.setTitle("Récompenses")
.setDescription(`${rewards.join("\n")}\n\n-# \`@LBF gemmes\` pour voir votre nombre de gemmes. Puis avec ${env.DISCORD_REWARDS_GIVER} pour échanger contre des cadeaux !`)
.setDescription(`${rewards.join("\n")}\n\n-# \`lbf gemmes\` pour voir votre nombre de gemmes. Puis avec ${env.DISCORD_REWARDS_GIVER} pour échanger contre des cadeaux !`)
.setColor(color);
}
@@ -49,12 +47,11 @@ export const makeResultEmbed = async (result: QuestResult, exclude: Array<string
new EmbedBuilder()
.setTitle("Classement")
.setColor(color)
.setDescription(
participants
.filter((x) => !exclude.includes(x.username))
.filter((_, i) => i < 8)
.map((p, i) => `${i + 1}. ${p.username} - ${p.xp}xp`)
.join("\n"),
.setDescription(participants
.filter((x) => !exclude.includes(x.username) && x.xp > 0)
.filter((_, i) => i < 8)
.map((p, i) => `${i + 1}. ${p.username} - ${p.xp}xp`)
.join("\n"),
),
],
};
@@ -68,16 +65,42 @@ export const askForGrinders = async (quest: QuestResult, client: Client) => {
let exclude: string[] = [];
if (env.QUEST_REWARDS) {
const color = parseInt(quest.quest.promoImagePrimaryColor.substring(1), 16);
const top10 = quest.participants
.filter((x) => !env.QUEST_EXCLUDE.includes(x.username))
.filter((x) => !env.QUEST_EXCLUDE.includes(x.username) && x.xp > 0)
.sort((a, b) => b.xp - a.xp)
.slice(0, 10)
.slice(0, 10);
if (!top10.length) {
logger.error("No eligible participants for grinder selection");
return;
}
const selectOptions = top10
.map((p) => ({ label: `${p.username} (${p.xp}xp)`, value: p.username }));
const row1 = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
new StringSelectMenuBuilder()
.setCustomId("grinders")
.setPlaceholder("Sélectionne les tricheurs")
.setMinValues(0)
.setMaxValues(selectOptions.length)
.addOptions(selectOptions),
);
const row2 = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("validate")
.setLabel("Valider")
.setStyle(ButtonStyle.Primary),
);
const top10Text = top10
.map((p, i) => `${i + 1}. ${p.username} - ${p.xp}xp`)
.join("\n");
const color = parseInt(quest.quest.promoImagePrimaryColor.substring(1), 16);
await adminChannel.send({
const msg = await adminChannel.send({
content: `-# ||${env.DISCORD_ADMIN_MENTION}||`,
embeds: [
new EmbedBuilder()
@@ -85,64 +108,32 @@ export const askForGrinders = async (quest: QuestResult, client: Client) => {
.setColor(color),
new EmbedBuilder()
.setTitle("Top 10 XP")
.setDescription(top10)
.setDescription(top10Text)
.setColor(color),
new EmbedBuilder()
.setTitle("Qui a grind ?")
.setDescription("Merci d'entrer les pseudos des joueurs qui ont grind.\n\nFormat:```@LBF onion,Yuno,...```\n**Attention les majuscules comptent**\nPour entrer la liste des joueurs, il faut __mentionner le bot__, si personne n'a grind, `@LBF tg`")
.setDescription("Sélectionne les joueurs qui ont grind (tricheurs en gros), puis clique sur **Valider**.")
.setColor(color),
],
components: [row1, row2],
});
const filter = (msg: Message) =>
msg.channel.id === adminChannel.id &&
!msg.author.bot &&
msg.content.startsWith(`<@${client.user!.id}>`);
let grinders: string[] = [];
let confirmed = false;
let answer: string | null = null;
while (!confirmed) {
const collected = await adminChannel.awaitMessages({ filter, max: 1 });
answer = collected.first()?.content || null;
if (!answer) continue;
let done = false;
while (!done) {
const interaction = await msg.awaitMessageComponent({ filter: (i) => !i.user.bot });
answer = answer.replace(`<@${client.user!.id}>`, "").trim();
if (answer.toLowerCase() === "tg") {
answer = "";
break;
}
const players = answer.split(",").map((x) => x.trim()).filter(Boolean);
await adminChannel.send({
content: `Est-ce correct ? (oui/non)`,
embeds: [
new EmbedBuilder()
.setTitle("Joueurs entrés")
.setDescription(players.length ? players.map((name) => `- ${name}`).join("\n") : "*Aucun joueur entré*")
.setColor(color),
],
});
const confirmFilter = (msg: Message) =>
msg.channel.id === adminChannel.id &&
!msg.author.bot &&
["oui", "non", "yes", "no"].includes(msg.content.toLowerCase());
const confirmCollected = await adminChannel.awaitMessages({ filter: confirmFilter, max: 1 });
const confirmation = confirmCollected.first()?.content.toLowerCase();
if (confirmation === "oui" || confirmation === "yes") {
confirmed = true;
await adminChannel.send({ content: "Ok" });
} else {
await adminChannel.send({ content: "D'accord, veuillez réessayer. Qui a grind ?" });
if (interaction.componentType === ComponentType.StringSelect) {
grinders = interaction.values;
await interaction.deferUpdate();
} else if (interaction.componentType === ComponentType.Button && interaction.customId === "validate") {
await interaction.update({ components: [] });
done = true;
}
}
if (answer === null) {
return logger.fatal("Answer was 'null', this should be unreachable");
}
exclude = answer.split(",").map((x) => x.trim()).filter(Boolean);
exclude = grinders;
}
const embed = await makeResultEmbed(quest, [...env.QUEST_EXCLUDE, ...exclude]);