diff --git a/.env.example b/.env.example index a514fdf..af51208 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,11 @@ WOV_CLAN_ID= WOV_API_KEY= WOV_FETCH_INTERVAL="14400000" # 4 hours +WOV_TRACKING_INTERVAL="432000000" # 12 hours QUEST_REWARDS= QUEST_EXCLUDE= DISCORD_WEBHOOK_URL= DISCORD_MENTION= -DISCORD_REWARDS_GIVER= \ No newline at end of file +DISCORD_REWARDS_GIVER= diff --git a/src/env.ts b/src/env.ts index d238356..39a183e 100644 --- a/src/env.ts +++ b/src/env.ts @@ -8,9 +8,11 @@ const schema = z.object({ DISCORD_REWARDS_CHANNEL: z.string(), DISCORD_ADMIN_MENTION: z.string(), DISCORD_ADMIN_CHANNEL: z.string(), + DISCORD_TRACKING_CHANNEL: z.string(), WOV_API_KEY: z.string(), WOV_CLAN_ID: z.string(), WOV_FETCH_INTERVAL: z.coerce.number(), + WOV_TRACKING_INTERVAL: z.coerce.number(), QUEST_REWARDS: z .string() .transform((x) => x.split(",").map((x) => x.trim())) diff --git a/src/index.ts b/src/index.ts index 8593bd3..c1c07c1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import { getAccountBalance, initAccounts, setAccountBalance } from "./account"; import { makeResultEmbed } from "./discord"; import { env } from "./env"; +import { initTracking, listTrackedPlayers, trackWovPlayer } from "./tracking"; import { checkForNewQuest, getClanInfos, @@ -13,6 +14,7 @@ import { import { ChannelType, Client, + EmbedBuilder, GatewayIntentBits, Message, Partials, @@ -163,6 +165,35 @@ const fn = async () => { } }; +const trackingCron = async () => { + const trackedPlayers = await listTrackedPlayers(); + for (const playerId of trackedPlayers) { + const res = await trackWovPlayer(playerId); + if (res.event !== "changed") return; + + const chan = client.channels.cache.get(env.DISCORD_TRACKING_CHANNEL); + if (!chan?.isSendable()) throw "Invalid tracking channel"; + + const lastUsername = res.oldUsernames[res.oldUsernames.length - 1]; + + await chan.send({ + embeds: [ + { + description: `### [UPDATE] \`${lastUsername}\` -> \`${res.newUsername}\` [\`${playerId}\`]`, + fields: [ + { name: "Nouveau pseudo", value: `\`${res.newUsername}\`` }, + { + name: "Anciens pseudos", + value: res.oldUsernames.map((x) => `- \`${x}\``).join("\n"), + }, + ], + color: 0x89cff0, + }, + ], + }); + } +}; + client.on("ready", async (client) => { console.log(`Logged in as ${client.user.username}`); @@ -179,9 +210,13 @@ client.on("ready", async (client) => { } } else { await initAccounts(); + await initTracking(); await fn(); setInterval(fn, env.WOV_FETCH_INTERVAL); + + await trackingCron(); + setInterval(trackingCron, env.WOV_TRACKING_INTERVAL); } }); @@ -197,6 +232,88 @@ client.on("messageCreate", async (message) => { .split(" "); if (command === "ping") { await message.reply("pong"); + } else if (command === "track") { + let playerName = args[0]; + if (!playerName) { + await message.reply({ + embeds: [ + { + description: `### ❌ Erreur\n\n\n\nUsage:\`@LBF track NOM_JOUEUR\`, exemple: \`@LBF track Yuno\`.\n**Attention les majuscules sont importantes**`, + color: 15335424, + }, + ], + }); + return; + } + + const player = await searchPlayer(playerName); + if (!player) { + await message.reply({ + embeds: [ + { + description: `### ❌ Erreur\n\n\n\nCette personne n'existe pas.\n**Attention les majuscules sont importantes**`, + color: 15335424, + }, + ], + }); + return; + } + + // 0x89cff0 + + const res = await trackWovPlayer(player.id); + switch (res.event) { + case "notFound": { + await message.reply({ + embeds: [ + { + description: `### ❌ Erreur\n\n\n\nCette personne n'existe pas.\n**Attention les majuscules sont importantes**`, + color: 15335424, + }, + ], + }); + return; + } + + case "registered": { + await message.reply({ + embeds: [ + { + description: `Tracker enregistré pour \`${playerName}\` [\`${player.id}\`]`, + color: 0x89cff0, + }, + ], + }); + + const chan = client.channels.cache.get(env.DISCORD_TRACKING_CHANNEL); + if (!chan?.isSendable()) throw "Invalid tracking channel"; + + await chan.send({ + embeds: [ + { + description: `### [NEW] \`${playerName}\` [\`${player.id}\`]`, + color: 0x89cff0, + }, + ], + }); + return; + } + case "none": { + await message.reply({ + embeds: [ + { + description: `Tracker déjà enregistré pour \`${playerName}\` [\`${player.id}\`]`, + color: 0x89cff0, + }, + ], + }); + return; + } + case "changed": { + // ignored + break; + } + } } else if (command === "icone") { let playerName = args[0]; if (!playerName) { diff --git a/src/tracking.ts b/src/tracking.ts new file mode 100644 index 0000000..7be7bfd --- /dev/null +++ b/src/tracking.ts @@ -0,0 +1,57 @@ +import { getPlayer } from "./wov"; + +const TRACKED_PLAYER_FILE = "./.cache/tracked.json"; + +type TrackedPlayers = Record; + +export async function initTracking(): Promise { + if (!(await Bun.file(TRACKED_PLAYER_FILE).exists())) { + Bun.file(TRACKED_PLAYER_FILE).write("{}"); + } +} + +export async function listTrackedPlayers(): Promise { + const trackedPlayers: TrackedPlayers = + await Bun.file(TRACKED_PLAYER_FILE).json(); + + return Object.keys(trackedPlayers); +} + +export async function trackWovPlayer(playerId: string): Promise< + | { event: "notFound" } + | { + event: "registered"; + } + | { event: "changed"; oldUsernames: string[]; newUsername: string } + | { event: "none" } +> { + const trackedPlayers: TrackedPlayers = + await Bun.file(TRACKED_PLAYER_FILE).json(); + + const player = await getPlayer(playerId); + if (!player) return { event: "notFound" }; + + const currentUsernames = trackedPlayers[playerId]; + if (currentUsernames) { + const oldUsernames = [...currentUsernames]; + if (!currentUsernames.includes(player.username)) { + currentUsernames.push(player.username); + + await Bun.file(TRACKED_PLAYER_FILE).write(JSON.stringify(trackedPlayers)); + + return { + event: "changed", + oldUsernames, + newUsername: player.username, + }; + } else { + return { + event: "none", + }; + } + } else { + trackedPlayers[playerId] = [player.username]; + await Bun.file(TRACKED_PLAYER_FILE).write(JSON.stringify(trackedPlayers)); + return { event: "registered" }; + } +} diff --git a/src/wov.ts b/src/wov.ts index 76834ce..62ce64a 100644 --- a/src/wov.ts +++ b/src/wov.ts @@ -91,6 +91,7 @@ export const searchPlayer = async (username: string) => { if (response.status === 404) return null; const data = (await response.json()) as { + id: string; clanId: string | null; }; @@ -115,3 +116,27 @@ export const getClanInfos = async (clanId: string) => { return data; }; + +export async function getPlayer(playerId: string) { + try { + const response = await fetch( + `https://api.wolvesville.com/players/${playerId}`, + { + method: "GET", + headers: { Authorization: `Bot ${env.WOV_API_KEY}` }, + }, + ); + + if (response.status === 404) return null; + + return { username: "test" }; + + const data = (await response.json()) as { + username: string; + }; + + return data; + } catch { + return null; + } +}