feat: implement user tracking

This commit is contained in:
Pihkaal
2025-11-08 13:12:14 +01:00
parent bc88c83c9a
commit 35a4121740
5 changed files with 203 additions and 1 deletions

View File

@@ -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=
DISCORD_REWARDS_GIVER=

View File

@@ -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()))

View File

@@ -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) {

57
src/tracking.ts Normal file
View File

@@ -0,0 +1,57 @@
import { getPlayer } from "./wov";
const TRACKED_PLAYER_FILE = "./.cache/tracked.json";
type TrackedPlayers = Record<string, string[]>;
export async function initTracking(): Promise<void> {
if (!(await Bun.file(TRACKED_PLAYER_FILE).exists())) {
Bun.file(TRACKED_PLAYER_FILE).write("{}");
}
}
export async function listTrackedPlayers(): Promise<string[]> {
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" };
}
}

View File

@@ -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;
}
}