feat: ask for any grinders before sending the results
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
"typescript": "^5.0.0"
|
"typescript": "^5.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"discord.js": "^14.21.0",
|
||||||
"zod": "^3.24.4"
|
"zod": "^3.24.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,19 +15,10 @@ export type DiscordEmbed = {
|
|||||||
color: number;
|
color: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const postEmbed = async (result: QuestResult): Promise<void> => {
|
export const makeResultEmbed = (
|
||||||
const embed = makeEmbed(result);
|
result: QuestResult,
|
||||||
await fetch(env.DISCORD_WEBHOOK_URL, {
|
exclude: Array<string>,
|
||||||
method: "POST",
|
): DiscordMessage => {
|
||||||
body: JSON.stringify(embed),
|
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const makeEmbed = (result: QuestResult): DiscordMessage => {
|
|
||||||
const imageUrl = result.quest.promoImageUrl;
|
const imageUrl = result.quest.promoImageUrl;
|
||||||
const color = parseInt(result.quest.promoImagePrimaryColor.substring(1), 16);
|
const color = parseInt(result.quest.promoImagePrimaryColor.substring(1), 16);
|
||||||
const participants = result.participants.toSorted((a, b) => b.xp - a.xp);
|
const participants = result.participants.toSorted((a, b) => b.xp - a.xp);
|
||||||
@@ -36,7 +27,7 @@ const makeEmbed = (result: QuestResult): DiscordMessage => {
|
|||||||
if (env.QUEST_REWARDS) {
|
if (env.QUEST_REWARDS) {
|
||||||
const rewardedParticipants = participants
|
const rewardedParticipants = participants
|
||||||
.map((x) => x.username)
|
.map((x) => x.username)
|
||||||
.filter((x) => !env.QUEST_EXCLUDE.includes(x));
|
.filter((x) => !exclude.includes(x));
|
||||||
const medals = ["🥇", "🥈", "🥉"].concat(
|
const medals = ["🥇", "🥈", "🥉"].concat(
|
||||||
new Array(rewardedParticipants.length).fill("🏅"),
|
new Array(rewardedParticipants.length).fill("🏅"),
|
||||||
);
|
);
|
||||||
@@ -69,7 +60,7 @@ const makeEmbed = (result: QuestResult): DiscordMessage => {
|
|||||||
{
|
{
|
||||||
title: "Classement",
|
title: "Classement",
|
||||||
description: participants
|
description: participants
|
||||||
.filter((x) => !env.QUEST_EXCLUDE.includes(x.username))
|
.filter((x) => !exclude.includes(x.username))
|
||||||
.filter((_, i) => i < 8)
|
.filter((_, i) => i < 8)
|
||||||
.map((p, i) => `${i + 1}. ${p.username} - ${p.xp}xp`)
|
.map((p, i) => `${i + 1}. ${p.username} - ${p.xp}xp`)
|
||||||
.join("\n"),
|
.join("\n"),
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { env as bunEnv } from "bun";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
DISCORD_WEBHOOK_URL: z.string(),
|
DISCORD_BOT_TOKEN: z.string(),
|
||||||
DISCORD_MENTION: z.string(),
|
DISCORD_MENTION: z.string(),
|
||||||
DISCORD_REWARDS_GIVER: z.string(),
|
DISCORD_REWARDS_GIVER: z.string(),
|
||||||
|
DISCORD_ADMIN_MENTION: z.string(),
|
||||||
|
DISCORD_ADMIN_CHANNEL: z.string(),
|
||||||
WOV_API_KEY: z.string(),
|
WOV_API_KEY: z.string(),
|
||||||
WOV_CLAN_ID: z.string(),
|
WOV_CLAN_ID: z.string(),
|
||||||
WOV_FETCH_INTERVAL: z.coerce.number(),
|
WOV_FETCH_INTERVAL: z.coerce.number(),
|
||||||
|
|||||||
142
src/index.ts
142
src/index.ts
@@ -1,14 +1,144 @@
|
|||||||
import { postEmbed } from "./discord";
|
import { makeResultEmbed } from "./discord";
|
||||||
import { env } from "./env";
|
import { env } from "./env";
|
||||||
import { checkForNewQuest } from "./wov";
|
import { checkForNewQuest, getLatestQuest, type QuestResult } from "./wov";
|
||||||
|
|
||||||
|
import { ChannelType, Client, GatewayIntentBits, Message } from "discord.js";
|
||||||
|
|
||||||
|
const client = new Client({
|
||||||
|
intents: [
|
||||||
|
GatewayIntentBits.Guilds,
|
||||||
|
GatewayIntentBits.GuildMessages,
|
||||||
|
GatewayIntentBits.MessageContent,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const askForGrinders = async (quest: QuestResult) => {
|
||||||
|
const channel = await client.channels.fetch(env.DISCORD_ADMIN_CHANNEL);
|
||||||
|
if (!channel || channel.type !== ChannelType.GuildText)
|
||||||
|
throw "Invalid admin channel provided";
|
||||||
|
|
||||||
|
const top10 = quest.participants
|
||||||
|
.sort((a, b) => b.xp - a.xp)
|
||||||
|
.slice(0, 10)
|
||||||
|
.map((p, i) => `${i + 1}. ${p.username} - ${p.xp}xp`)
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
const color = parseInt(quest.quest.promoImagePrimaryColor.substring(1), 16);
|
||||||
|
|
||||||
|
await channel.send({
|
||||||
|
content: `-# ||${env.DISCORD_ADMIN_MENTION}||`,
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
title: "Quête terminée !",
|
||||||
|
color,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Top 10 XP",
|
||||||
|
description: top10,
|
||||||
|
color,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Qui a grind ?",
|
||||||
|
description:
|
||||||
|
"Merci d'entrer les pseudos des joueurs qui ont grind.\n\nFormat:```laulau18,Yuno,...```\n**Attention aux majuscules**",
|
||||||
|
color,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const filter = (msg: Message) =>
|
||||||
|
msg.channel.id === channel.id && !msg.author.bot;
|
||||||
|
|
||||||
|
let confirmed = false;
|
||||||
|
let answer: string | null = null;
|
||||||
|
while (!confirmed) {
|
||||||
|
const collected = await channel.awaitMessages({ filter, max: 1 });
|
||||||
|
answer = collected.first()?.content || null;
|
||||||
|
if (!answer) continue;
|
||||||
|
|
||||||
|
const players = answer
|
||||||
|
.split(",")
|
||||||
|
.map((x) => x.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
await channel.send({
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
title: "Joueurs entrés",
|
||||||
|
description: players.length
|
||||||
|
? players.map((name) => `- ${name}`).join("\n")
|
||||||
|
: "*Aucun joueur entré*",
|
||||||
|
color,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
content: `Est-ce correct ? (oui/non)`,
|
||||||
|
});
|
||||||
|
const confirmFilter = (msg: Message) =>
|
||||||
|
msg.channel.id === channel.id &&
|
||||||
|
!msg.author.bot &&
|
||||||
|
["oui", "non", "yes", "no"].includes(msg.content.toLowerCase());
|
||||||
|
const confirmCollected = await channel.awaitMessages({
|
||||||
|
filter: confirmFilter,
|
||||||
|
max: 1,
|
||||||
|
});
|
||||||
|
const confirmation = confirmCollected.first()?.content.toLowerCase();
|
||||||
|
if (confirmation === "oui" || confirmation === "yes") {
|
||||||
|
confirmed = true;
|
||||||
|
await channel.send({ content: "Ok" });
|
||||||
|
} else {
|
||||||
|
await channel.send({
|
||||||
|
content: "D'accord, veuillez réessayer. Qui a grind ?",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!answer) throw "unreachable";
|
||||||
|
|
||||||
|
const exclude = answer
|
||||||
|
.split(",")
|
||||||
|
.map((x) => x.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const embed = makeResultEmbed(quest, [...env.QUEST_EXCLUDE, ...exclude]);
|
||||||
|
const rewardChannel = await client.channels.fetch(env.DISCORD_ADMIN_CHANNEL);
|
||||||
|
if (rewardChannel && rewardChannel.type === ChannelType.GuildText) {
|
||||||
|
await rewardChannel.send(embed);
|
||||||
|
} else {
|
||||||
|
throw "Invalid reward channel";
|
||||||
|
}
|
||||||
|
console.log(`Quest result posted at: ${new Date().toISOString()}`);
|
||||||
|
};
|
||||||
|
|
||||||
const fn = async () => {
|
const fn = async () => {
|
||||||
const quest = await checkForNewQuest();
|
const quest = await checkForNewQuest();
|
||||||
if (quest) {
|
if (quest) {
|
||||||
await postEmbed(quest);
|
await askForGrinders(quest);
|
||||||
console.log(`Quest result posted at: ${new Date().toISOString()}`);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await fn();
|
client.on("ready", async (client) => {
|
||||||
setInterval(fn, env.WOV_FETCH_INTERVAL);
|
console.log(`Logged in as ${client.user.username}`);
|
||||||
|
|
||||||
|
const quest = await getLatestQuest();
|
||||||
|
await askForGrinders(quest);
|
||||||
|
|
||||||
|
// await fn();
|
||||||
|
// setInterval(fn, env.WOV_FETCH_INTERVAL);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("messageCreate", async (message) => {
|
||||||
|
if (message.author.bot) return;
|
||||||
|
|
||||||
|
message.channel;
|
||||||
|
|
||||||
|
// await message.channel.send({
|
||||||
|
// content: `-# ||${env.DISCORD_ADMIN_MENTION}||`,
|
||||||
|
// embeds: [
|
||||||
|
// {
|
||||||
|
// title: "Quête terminée !",
|
||||||
|
// description: "Entrez les pseudos des gens à exclure de la quête",
|
||||||
|
// color: 0x3498db,
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// })
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.login(env.DISCORD_BOT_TOKEN);
|
||||||
|
|||||||
11
src/wov.ts
11
src/wov.ts
@@ -16,7 +16,7 @@ export type QuestParticipant = {
|
|||||||
xp: number;
|
xp: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkForNewQuest = async (): Promise<QuestResult | null> => {
|
export const getLatestQuest = async (): Promise<QuestResult> => {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`https://api.wolvesville.com/clans/${env.WOV_CLAN_ID}/quests/history`,
|
`https://api.wolvesville.com/clans/${env.WOV_CLAN_ID}/quests/history`,
|
||||||
{
|
{
|
||||||
@@ -25,8 +25,13 @@ export const checkForNewQuest = async (): Promise<QuestResult | null> => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
const history = (await response.json()) as Array<QuestResult>;
|
const history = (await response.json()) as Array<QuestResult>;
|
||||||
|
return history[0];
|
||||||
|
};
|
||||||
|
|
||||||
const lastId = history[0].quest.id;
|
export const checkForNewQuest = async (): Promise<QuestResult | null> => {
|
||||||
|
const lastQuest = await getLatestQuest();
|
||||||
|
|
||||||
|
const lastId = lastQuest.quest.id;
|
||||||
const cacheFile = Bun.file(".cache/.quest_cache");
|
const cacheFile = Bun.file(".cache/.quest_cache");
|
||||||
await mkdir(".cache", { recursive: true });
|
await mkdir(".cache", { recursive: true });
|
||||||
if ((await cacheFile.exists()) && (await cacheFile.text()) === lastId) {
|
if ((await cacheFile.exists()) && (await cacheFile.text()) === lastId) {
|
||||||
@@ -34,5 +39,5 @@ export const checkForNewQuest = async (): Promise<QuestResult | null> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await cacheFile.write(lastId);
|
await cacheFile.write(lastId);
|
||||||
return history[0];
|
return lastQuest;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user