161 lines
6.3 KiB
TypeScript
161 lines
6.3 KiB
TypeScript
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";
|
|
import { getAccountBalance, setAccountBalance } from "~/economy";
|
|
|
|
const logger = createLogger({ prefix: "quests" });
|
|
|
|
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);
|
|
const participants = result.participants.toSorted((a, b) => b.xp - a.xp);
|
|
|
|
let rewardsEmbed: EmbedBuilder | undefined;
|
|
if (env.QUEST_REWARDS) {
|
|
const rewarded = participants
|
|
.map((x) => ({ id: x.playerId, username: x.username }))
|
|
.filter((x) => !exclude.includes(x.username));
|
|
const medals = ["🥇", "🥈", "🥉"].concat(new Array(rewarded.length).fill("🏅"));
|
|
|
|
const rewards = rewarded
|
|
.slice(0, Math.min(rewarded.length, env.QUEST_REWARDS.length))
|
|
.map((x, i) => `- ${medals[i]} ${x.username} - ${env.QUEST_REWARDS![i]} ${env.QUEST_REWARDS_ARE_GEMS ? "gemmes" : ""}`);
|
|
|
|
if (env.QUEST_REWARDS_ARE_GEMS) {
|
|
const arr = rewarded.slice(0, Math.min(rewarded.length, env.QUEST_REWARDS.length));
|
|
for (let i = 0; i < arr.length; i++) {
|
|
const balance = await getAccountBalance(arr[i].id);
|
|
await setAccountBalance(arr[i].id, balance + parseInt(env.QUEST_REWARDS[i]));
|
|
}
|
|
}
|
|
|
|
rewardsEmbed = new EmbedBuilder()
|
|
.setTitle("Récompenses")
|
|
.setDescription(rewards.join("\n"))
|
|
.setColor(color);
|
|
}
|
|
|
|
return {
|
|
content: `-# ||${env.DISCORD_MENTION}||`,
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setDescription(`# Résultats de quête\n\nMerci à toutes et à tous d'avoir participé 🫡`)
|
|
.setColor(color)
|
|
.setImage(imageUrl),
|
|
...(rewardsEmbed ? [rewardsEmbed] : []),
|
|
new EmbedBuilder()
|
|
.setTitle("Classement")
|
|
.setColor(color)
|
|
.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"),
|
|
),
|
|
],
|
|
};
|
|
};
|
|
|
|
export const askForGrinders = async (quest: QuestResult, client: Client) => {
|
|
const adminChannel = await client.channels.fetch(env.DISCORD_ADMIN_CHANNEL);
|
|
if (!adminChannel || adminChannel.type !== ChannelType.GuildText) {
|
|
return logger.fatal("Invalid 'DISCORD_ADMIN_CHANNEL'");
|
|
}
|
|
|
|
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) && x.xp > 0)
|
|
.sort((a, b) => b.xp - a.xp)
|
|
.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 msg = await adminChannel.send({
|
|
content: `-# ||${env.DISCORD_ADMIN_MENTION}||`,
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setTitle("Quête terminée !")
|
|
.setColor(color),
|
|
new EmbedBuilder()
|
|
.setTitle("Top 10 XP")
|
|
.setDescription(top10Text)
|
|
.setColor(color),
|
|
new EmbedBuilder()
|
|
.setTitle("Qui a grind ?")
|
|
.setDescription("Sélectionne les joueurs qui ont grind (tricheurs en gros), puis clique sur **Valider**.")
|
|
.setColor(color),
|
|
],
|
|
components: [row1, row2],
|
|
});
|
|
|
|
let grinders: string[] = [];
|
|
|
|
let done = false;
|
|
while (!done) {
|
|
const interaction = await msg.awaitMessageComponent({ filter: (i) => !i.user.bot });
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
exclude = grinders;
|
|
}
|
|
|
|
const embed = await makeResultEmbed(quest, [...env.QUEST_EXCLUDE, ...exclude]);
|
|
const rewardChannel = await client.channels.fetch(env.DISCORD_REWARDS_CHANNEL);
|
|
if (!rewardChannel || rewardChannel.type !== ChannelType.GuildText) {
|
|
return logger.fatal("Invalid 'DISCORD_REWARDS_CHANNEL'");
|
|
}
|
|
|
|
await rewardChannel.send(embed);
|
|
|
|
if (env.QUEST_EXCLUDE) await adminChannel.send("Envoyé !");
|
|
logger.info(`Results posted at: ${new Date().toISOString()}`);
|
|
};
|
|
|
|
export const questCheckCron = async (client: Client) => {
|
|
logger.info("Checking for new quest");
|
|
const quest = await checkForNewQuest();
|
|
if (quest) {
|
|
logger.info(`New quest found: '${quest.quest.id}'`);
|
|
await askForGrinders(quest, client);
|
|
} else {
|
|
logger.info("No new quest found");
|
|
}
|
|
};
|