From 622b881d8953f94b6d2a23c091f430a35c33b18e Mon Sep 17 00:00:00 2001 From: Pihkaal Date: Thu, 4 Dec 2025 17:37:57 +0100 Subject: [PATCH] feat(discord-bot): drop files to use postgres and redis --- apps/discord-bot/.env.example | 6 ++ apps/discord-bot/package.json | 1 + apps/discord-bot/src/index.ts | 10 +-- apps/discord-bot/src/services/account.ts | 42 ++++++------ apps/discord-bot/src/services/tracking.ts | 78 +++++++++++++---------- apps/discord-bot/src/services/wov.ts | 45 +++---------- 6 files changed, 83 insertions(+), 99 deletions(-) diff --git a/apps/discord-bot/.env.example b/apps/discord-bot/.env.example index 33d09eb..640e068 100644 --- a/apps/discord-bot/.env.example +++ b/apps/discord-bot/.env.example @@ -9,3 +9,9 @@ QUEST_EXCLUDE= DISCORD_WEBHOOK_URL= DISCORD_MENTION= DISCORD_REWARDS_GIVER= + +# Postgres database url +DATABASE_URL="" + +# Redis url +REDIS_URL="" diff --git a/apps/discord-bot/package.json b/apps/discord-bot/package.json index ec03ac9..7aabb06 100644 --- a/apps/discord-bot/package.json +++ b/apps/discord-bot/package.json @@ -14,6 +14,7 @@ "typescript": "^5.7.2" }, "dependencies": { + "@lbf-bot/database": "workspace:*", "discord.js": "^14.21.0", "dotenv": "^17.2.3", "zod": "^3.24.4" diff --git a/apps/discord-bot/src/index.ts b/apps/discord-bot/src/index.ts index c3507fc..c489b80 100644 --- a/apps/discord-bot/src/index.ts +++ b/apps/discord-bot/src/index.ts @@ -1,10 +1,5 @@ -import { initAccounts } from "~/services/account"; import { env } from "~/env"; -import { - initTracking, - listTrackedPlayers, - trackWovPlayer, -} from "~/services/tracking"; +import { listTrackedPlayers, trackWovPlayer } from "~/services/tracking"; import { checkForNewQuest } from "~/services/wov"; import { createInfoEmbed } from "~/utils/discord"; import { askForGrinders } from "~/utils/quest"; @@ -93,9 +88,6 @@ client.on("ready", async (client) => { process.exit(0); }); } else { - await initAccounts(); - await initTracking(); - await questCheckCron(); setInterval(questCheckCron, env.WOV_FETCH_INTERVAL); diff --git a/apps/discord-bot/src/services/account.ts b/apps/discord-bot/src/services/account.ts index fdf6ae0..6c43239 100644 --- a/apps/discord-bot/src/services/account.ts +++ b/apps/discord-bot/src/services/account.ts @@ -1,23 +1,16 @@ -import { readFile, writeFile, access } from "node:fs/promises"; -import { constants } from "node:fs"; - -const ACCOUNTS_FILE = "./.cache/accounts.json"; - -export const initAccounts = async (): Promise => { - try { - await access(ACCOUNTS_FILE, constants.F_OK); - } catch { - await writeFile(ACCOUNTS_FILE, "{}"); - } -}; +import { db, tables, eq } from "@lbf-bot/database"; export const getAccountBalance = async (playerId: string): Promise => { - const content = await readFile(ACCOUNTS_FILE, "utf-8"); - const accounts: Record = JSON.parse(content); - if (accounts[playerId]) return accounts[playerId]; + const account = await db.query.accounts.findFirst({ + where: eq(tables.accounts.playerId, playerId), + }); - accounts[playerId] = 0; - await writeFile(ACCOUNTS_FILE, JSON.stringify(accounts)); + if (account) return account.balance; + + await db.insert(tables.accounts).values({ + playerId, + balance: 0, + }); return 0; }; @@ -26,9 +19,14 @@ export const setAccountBalance = async ( playerId: string, balance: number, ): Promise => { - const content = await readFile(ACCOUNTS_FILE, "utf-8"); - const accounts: Record = JSON.parse(content); - accounts[playerId] = balance; - - await writeFile(ACCOUNTS_FILE, JSON.stringify(accounts)); + await db + .insert(tables.accounts) + .values({ + playerId, + balance, + }) + .onConflictDoUpdate({ + target: tables.accounts.playerId, + set: { balance, updatedAt: new Date() }, + }); }; diff --git a/apps/discord-bot/src/services/tracking.ts b/apps/discord-bot/src/services/tracking.ts index 422f9a7..65720e8 100644 --- a/apps/discord-bot/src/services/tracking.ts +++ b/apps/discord-bot/src/services/tracking.ts @@ -1,35 +1,28 @@ import { getPlayer } from "~/services/wov"; -import { readFile, writeFile, access } from "node:fs/promises"; -import { constants } from "node:fs"; -import type { TrackedPlayers } from "~/types"; - -const TRACKED_PLAYER_FILE = "./.cache/tracked.json"; - -export async function initTracking(): Promise { - try { - await access(TRACKED_PLAYER_FILE, constants.F_OK); - } catch { - await writeFile(TRACKED_PLAYER_FILE, "{}"); - } -} +import { db, tables, eq } from "@lbf-bot/database"; export async function listTrackedPlayers(): Promise { - const content = await readFile(TRACKED_PLAYER_FILE, "utf-8"); - const trackedPlayers: TrackedPlayers = JSON.parse(content); + const players = await db.query.trackedPlayers.findMany({ + columns: { + playerId: true, + }, + }); - return Object.keys(trackedPlayers); + return players.map((p) => p.playerId); } export async function untrackWovPlayer( playerId: string, ): Promise<{ event: "notTracked" } | { event: "trackerRemoved" }> { - const content = await readFile(TRACKED_PLAYER_FILE, "utf-8"); - const trackedPlayers: TrackedPlayers = JSON.parse(content); + const player = await db.query.trackedPlayers.findFirst({ + where: eq(tables.trackedPlayers.playerId, playerId), + }); - if (!trackedPlayers[playerId]) return { event: "notTracked" }; + if (!player) return { event: "notTracked" }; - delete trackedPlayers[playerId]; - await writeFile(TRACKED_PLAYER_FILE, JSON.stringify(trackedPlayers)); + await db + .delete(tables.trackedPlayers) + .where(eq(tables.trackedPlayers.playerId, playerId)); return { event: "trackerRemoved" }; } @@ -42,23 +35,35 @@ export async function trackWovPlayer(playerId: string): Promise< | { event: "changed"; oldUsernames: string[]; newUsername: string } | { event: "none" } > { - const content = await readFile(TRACKED_PLAYER_FILE, "utf-8"); - const trackedPlayers: TrackedPlayers = JSON.parse(content); - 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); + const tracked = await db.query.trackedPlayers.findFirst({ + where: eq(tables.trackedPlayers.playerId, playerId), + with: { + usernameHistory: { + orderBy: (history, { asc }) => [asc(history.firstSeenAt)], + }, + }, + }); - await writeFile(TRACKED_PLAYER_FILE, JSON.stringify(trackedPlayers)); + if (tracked) { + const currentUsernames = tracked.usernameHistory.map((h) => h.username); + + if (!currentUsernames.includes(player.username)) { + await db.insert(tables.usernameHistory).values({ + playerId, + username: player.username, + }); + + await db + .update(tables.trackedPlayers) + .set({ updatedAt: new Date() }) + .where(eq(tables.trackedPlayers.playerId, playerId)); return { event: "changed", - oldUsernames, + oldUsernames: currentUsernames, newUsername: player.username, }; } else { @@ -67,8 +72,15 @@ export async function trackWovPlayer(playerId: string): Promise< }; } } else { - trackedPlayers[playerId] = [player.username]; - await writeFile(TRACKED_PLAYER_FILE, JSON.stringify(trackedPlayers)); + await db.insert(tables.trackedPlayers).values({ + playerId, + }); + + await db.insert(tables.usernameHistory).values({ + playerId, + username: player.username, + }); + return { event: "registered" }; } } diff --git a/apps/discord-bot/src/services/wov.ts b/apps/discord-bot/src/services/wov.ts index 3ee5a44..489a1d9 100644 --- a/apps/discord-bot/src/services/wov.ts +++ b/apps/discord-bot/src/services/wov.ts @@ -1,6 +1,5 @@ import { env } from "~/env"; -import { mkdir, readFile, writeFile, access } from "node:fs/promises"; -import { constants } from "node:fs"; +import { redis } from "@lbf-bot/database"; import type { QuestResult } from "~/types"; export const getLatestQuest = async (): Promise => { @@ -17,45 +16,23 @@ export const getLatestQuest = async (): Promise => { export const checkForNewQuest = async (): Promise => { const lastQuest = await getLatestQuest(); - const lastId = lastQuest.quest.id; - const cacheFilePath = ".cache/.quest_cache"; - await mkdir(".cache", { recursive: true }); - try { - await access(cacheFilePath, constants.F_OK); - const cachedQuestId = await readFile(cacheFilePath, "utf-8"); - if (cachedQuestId === lastId || cachedQuestId === "IGNORE") { - return null; - } - } catch { - // File doesn't exist, continue + const cachedQuestId = await redis.get("quest:last_id"); + if (cachedQuestId === lastId || cachedQuestId === "IGNORE") { + return null; } - await writeFile(cacheFilePath, lastId); + await redis.set("quest:last_id", lastId); return lastQuest; }; export const getClanMembers = async (): Promise< Array<{ playerId: string; username: string }> > => { - const cacheFilePath = ".clan_members_cache"; - await mkdir(".cache", { recursive: true }); - - let cached: { - timestamp: number; - data: Array<{ playerId: string; username: string }>; - } | null = null; - - try { - await access(cacheFilePath, constants.F_OK); - const content = await readFile(cacheFilePath, "utf-8"); - cached = JSON.parse(content); - if (cached && Date.now() - cached.timestamp < 60 * 60 * 1000) { - return cached.data; - } - } catch { - // File doesn't exist or is invalid, continue + const cached = await redis.get("clan:members"); + if (cached) { + return JSON.parse(cached); } const response = await fetch( @@ -69,10 +46,8 @@ export const getClanMembers = async (): Promise< playerId: string; username: string; }>; - await writeFile( - cacheFilePath, - JSON.stringify({ timestamp: Date.now(), data }), - ); + + await redis.set("clan:members", JSON.stringify(data), "EX", 60 * 60); return data; };