From 416d7675f2762d57eedefc85b5de18c862adc73e Mon Sep 17 00:00:00 2001 From: Pihkaal Date: Wed, 7 May 2025 18:42:08 +0200 Subject: [PATCH] feat: check for new quests, post result embed to discord webhook and cache posted quests --- .env.example | 10 ++++++ .gitignore | 2 ++ bun.lockb | Bin 3121 -> 3456 bytes package.json | 3 ++ src/discord.ts | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ src/env.ts | 31 +++++++++++++++++++ src/index.ts | 15 ++++++++- src/wov.ts | 36 ++++++++++++++++++++++ tsconfig.json | 7 ++++- 9 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 .env.example create mode 100644 src/discord.ts create mode 100644 src/env.ts create mode 100644 src/wov.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a514fdf --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +WOV_CLAN_ID= +WOV_API_KEY= +WOV_FETCH_INTERVAL="14400000" # 4 hours + +QUEST_REWARDS= +QUEST_EXCLUDE= + +DISCORD_WEBHOOK_URL= +DISCORD_MENTION= +DISCORD_REWARDS_GIVER= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9b1ee42..2f85aa5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore +.quest_cache + # Logs logs diff --git a/bun.lockb b/bun.lockb index e22b888147340d7a416e644173ff611dd43e2875..093b2d970e13e3879297342a7d019807df957a39 100755 GIT binary patch delta 718 zcmdle(I7oRPqX}Ca7x+7&0fn^U;d^gSI+aWJZ1iq%@>9T^f}+%tlFZa1X&{daD1He@a{_5GMg|6MARP^)fhIKo^?>E` znpCcz+|iJ&YxelHyG#ASi3chKRe^H)KsC%jS^|h!CVo_(Y{1CDm@?Ut(VlV6&dl@)=Y|wlYcSVF{V$pWU^=6GP#n;dJ-E8FAGo}1iFWDcL)!NNP)mc^Tq zb#f<5_+$%K-pOx)Jod?!tjUuYMS%WgnY@6NgChj!L;+x6Ob%oV*u<92IN5>OkZS`p z{Eu)%4)vLDE z){==)3#b(YVA0N*Z*%Nb;H=$DjCE#uMuvI@3?0B!0s<$vCaZBeOwQmmkXivvBP|xD sXBaE^{{4U|dBHPz6Q}oN0d8p#piA_1^?{@=!~uFG=~bKma+)v!0KnO=k^lez delta 544 zcmZpW-Y79aPwR&HhEx~*L#$o>{(nj%4L5%?da`Vi`wsp|$`A52ZdPwv$jtx->2i2Uks!e6LP*pRbk;`RMYGJ2fIJtmRV)Az`waq@; +}; + +export type DiscordEmbed = { + title?: string; + description: string; + image?: { + url: string; + }; + color: number; +}; + +export const postEmbed = async (result: QuestResult): Promise => { + const embed = makeEmbed(result); + await fetch(env.DISCORD_WEBHOOK_URL, { + method: "POST", + body: JSON.stringify(embed), + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + }); +}; + +const makeEmbed = (result: QuestResult): DiscordMessage => { + 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: DiscordEmbed | undefined; + if (env.QUEST_REWARDS) { + console.log(env.QUEST_REWARDS); + const rewardedParticipants = participants + .map((x) => x.username) + .filter((x) => !env.QUEST_EXCLUDE.includes(x)); + const medals = ["đŸ„‡", "đŸ„ˆ", "đŸ„‰"].concat( + new Array(rewardedParticipants.length).fill("🏅"), + ); + + const rewards = rewardedParticipants + .slice(0, Math.min(rewardedParticipants.length, env.QUEST_REWARDS.length)) + .map( + (username, i) => + `- ${medals[i]} ${username} - ${env.QUEST_REWARDS![i]}`, + ); + + rewardsEmbed = { + title: "RĂ©compenses", + description: `${rewards.join("\n")}\n\n-# Voir avec ${env.DISCORD_REWARDS_GIVER} pour rĂ©cupĂ©rer les rĂ©compenses !`, + color, + }; + } + + return { + content: `-# ||${env.DISCORD_MENTION}||`, + embeds: [ + { + description: `# RĂ©sultats de quĂȘte\n\nMerci Ă  toutes et Ă  tous d'avoir participĂ© đŸ«Ą`, + color, + image: { + url: imageUrl, + }, + }, + ...(rewardsEmbed ? [rewardsEmbed] : []), + { + title: "Classement", + description: participants + .filter((x) => !env.QUEST_EXCLUDE.includes(x.username)) + .filter((_, i) => i < 8) + .map((p, i) => `${i + 1}. ${p.username} - ${p.xp}xp`) + .join("\n"), + color, + }, + ], + }; +}; diff --git a/src/env.ts b/src/env.ts new file mode 100644 index 0000000..d0dc981 --- /dev/null +++ b/src/env.ts @@ -0,0 +1,31 @@ +import { env as bunEnv } from "bun"; +import { z } from "zod"; + +const schema = z.object({ + DISCORD_WEBHOOK_URL: z.string(), + DISCORD_MENTION: z.string(), + DISCORD_REWARDS_GIVER: z.string(), + WOV_API_KEY: z.string(), + WOV_CLAN_ID: z.string(), + WOV_FETCH_INTERVAL: z.coerce.number(), + QUEST_REWARDS: z + .string() + .transform((x) => x.split(",").map((x) => x.trim())) + .optional(), + QUEST_EXCLUDE: z + .string() + .transform((x) => x.split(",").map((x) => x.trim())) + .optional() + .default(""), +}); + +const result = schema.safeParse(bunEnv); +if (!result.success) { + console.log("❌ Invalid environments variables:"); + console.log( + result.error.errors.map((x) => `- ${x.path.join(".")}: ${x.message}`), + ); + process.exit(1); +} + +export const env = result.data; diff --git a/src/index.ts b/src/index.ts index 2a5e4b8..c57ca14 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,14 @@ -console.log("Hello via Bun!"); +import { postEmbed } from "./discord"; +import { env } from "./env"; +import { checkForNewQuest } from "./wov"; + +const fn = async () => { + const quest = await checkForNewQuest(); + if (quest) { + await postEmbed(quest); + console.log(`Quest result posted at: ${new Date().toISOString()}`); + } +}; + +await fn(); +setInterval(fn, env.WOV_FETCH_INTERVAL); diff --git a/src/wov.ts b/src/wov.ts new file mode 100644 index 0000000..9306bf7 --- /dev/null +++ b/src/wov.ts @@ -0,0 +1,36 @@ +import { env } from "./env"; + +export type QuestResult = { + quest: { + id: string; + promoImageUrl: string; + promoImagePrimaryColor: string; + }; + participants: Array; +}; + +export type QuestParticipant = { + playerId: string; + username: string; + xp: number; +}; + +export const checkForNewQuest = async (): Promise => { + const response = await fetch( + `https://api.wolvesville.com/clans/${env.WOV_CLAN_ID}/quests/history`, + { + method: "GET", + headers: { Authorization: `Bot ${env.WOV_API_KEY}` }, + }, + ); + const history = (await response.json()) as Array; + + const lastId = history[0].quest.id; + const cacheFile = Bun.file(".quest_cache"); + if ((await cacheFile.exists()) && (await cacheFile.text()) === lastId) { + return null; + } + + cacheFile.write(lastId); + return history[0]; +}; diff --git a/tsconfig.json b/tsconfig.json index 238655f..c453b49 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,11 @@ // Some stricter flags (disabled by default) "noUnusedLocals": false, "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false + "noPropertyAccessFromIndexSignature": false, + + "baseUrl": ".", + "paths": { + "~/*": ["./src/*"] + } } }