feat(discord-bot): drop files to use postgres and redis
This commit is contained in:
@@ -9,3 +9,9 @@ QUEST_EXCLUDE=
|
|||||||
DISCORD_WEBHOOK_URL=
|
DISCORD_WEBHOOK_URL=
|
||||||
DISCORD_MENTION=
|
DISCORD_MENTION=
|
||||||
DISCORD_REWARDS_GIVER=
|
DISCORD_REWARDS_GIVER=
|
||||||
|
|
||||||
|
# Postgres database url
|
||||||
|
DATABASE_URL=""
|
||||||
|
|
||||||
|
# Redis url
|
||||||
|
REDIS_URL=""
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"typescript": "^5.7.2"
|
"typescript": "^5.7.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@lbf-bot/database": "workspace:*",
|
||||||
"discord.js": "^14.21.0",
|
"discord.js": "^14.21.0",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"zod": "^3.24.4"
|
"zod": "^3.24.4"
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import { initAccounts } from "~/services/account";
|
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
import {
|
import { listTrackedPlayers, trackWovPlayer } from "~/services/tracking";
|
||||||
initTracking,
|
|
||||||
listTrackedPlayers,
|
|
||||||
trackWovPlayer,
|
|
||||||
} from "~/services/tracking";
|
|
||||||
import { checkForNewQuest } from "~/services/wov";
|
import { checkForNewQuest } from "~/services/wov";
|
||||||
import { createInfoEmbed } from "~/utils/discord";
|
import { createInfoEmbed } from "~/utils/discord";
|
||||||
import { askForGrinders } from "~/utils/quest";
|
import { askForGrinders } from "~/utils/quest";
|
||||||
@@ -93,9 +88,6 @@ client.on("ready", async (client) => {
|
|||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await initAccounts();
|
|
||||||
await initTracking();
|
|
||||||
|
|
||||||
await questCheckCron();
|
await questCheckCron();
|
||||||
setInterval(questCheckCron, env.WOV_FETCH_INTERVAL);
|
setInterval(questCheckCron, env.WOV_FETCH_INTERVAL);
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,16 @@
|
|||||||
import { readFile, writeFile, access } from "node:fs/promises";
|
import { db, tables, eq } from "@lbf-bot/database";
|
||||||
import { constants } from "node:fs";
|
|
||||||
|
|
||||||
const ACCOUNTS_FILE = "./.cache/accounts.json";
|
|
||||||
|
|
||||||
export const initAccounts = async (): Promise<void> => {
|
|
||||||
try {
|
|
||||||
await access(ACCOUNTS_FILE, constants.F_OK);
|
|
||||||
} catch {
|
|
||||||
await writeFile(ACCOUNTS_FILE, "{}");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAccountBalance = async (playerId: string): Promise<number> => {
|
export const getAccountBalance = async (playerId: string): Promise<number> => {
|
||||||
const content = await readFile(ACCOUNTS_FILE, "utf-8");
|
const account = await db.query.accounts.findFirst({
|
||||||
const accounts: Record<string, number> = JSON.parse(content);
|
where: eq(tables.accounts.playerId, playerId),
|
||||||
if (accounts[playerId]) return accounts[playerId];
|
});
|
||||||
|
|
||||||
accounts[playerId] = 0;
|
if (account) return account.balance;
|
||||||
await writeFile(ACCOUNTS_FILE, JSON.stringify(accounts));
|
|
||||||
|
await db.insert(tables.accounts).values({
|
||||||
|
playerId,
|
||||||
|
balance: 0,
|
||||||
|
});
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
@@ -26,9 +19,14 @@ export const setAccountBalance = async (
|
|||||||
playerId: string,
|
playerId: string,
|
||||||
balance: number,
|
balance: number,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const content = await readFile(ACCOUNTS_FILE, "utf-8");
|
await db
|
||||||
const accounts: Record<string, number> = JSON.parse(content);
|
.insert(tables.accounts)
|
||||||
accounts[playerId] = balance;
|
.values({
|
||||||
|
playerId,
|
||||||
await writeFile(ACCOUNTS_FILE, JSON.stringify(accounts));
|
balance,
|
||||||
|
})
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: tables.accounts.playerId,
|
||||||
|
set: { balance, updatedAt: new Date() },
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,35 +1,28 @@
|
|||||||
import { getPlayer } from "~/services/wov";
|
import { getPlayer } from "~/services/wov";
|
||||||
import { readFile, writeFile, access } from "node:fs/promises";
|
import { db, tables, eq } from "@lbf-bot/database";
|
||||||
import { constants } from "node:fs";
|
|
||||||
import type { TrackedPlayers } from "~/types";
|
|
||||||
|
|
||||||
const TRACKED_PLAYER_FILE = "./.cache/tracked.json";
|
|
||||||
|
|
||||||
export async function initTracking(): Promise<void> {
|
|
||||||
try {
|
|
||||||
await access(TRACKED_PLAYER_FILE, constants.F_OK);
|
|
||||||
} catch {
|
|
||||||
await writeFile(TRACKED_PLAYER_FILE, "{}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function listTrackedPlayers(): Promise<string[]> {
|
export async function listTrackedPlayers(): Promise<string[]> {
|
||||||
const content = await readFile(TRACKED_PLAYER_FILE, "utf-8");
|
const players = await db.query.trackedPlayers.findMany({
|
||||||
const trackedPlayers: TrackedPlayers = JSON.parse(content);
|
columns: {
|
||||||
|
playerId: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return Object.keys(trackedPlayers);
|
return players.map((p) => p.playerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function untrackWovPlayer(
|
export async function untrackWovPlayer(
|
||||||
playerId: string,
|
playerId: string,
|
||||||
): Promise<{ event: "notTracked" } | { event: "trackerRemoved" }> {
|
): Promise<{ event: "notTracked" } | { event: "trackerRemoved" }> {
|
||||||
const content = await readFile(TRACKED_PLAYER_FILE, "utf-8");
|
const player = await db.query.trackedPlayers.findFirst({
|
||||||
const trackedPlayers: TrackedPlayers = JSON.parse(content);
|
where: eq(tables.trackedPlayers.playerId, playerId),
|
||||||
|
});
|
||||||
|
|
||||||
if (!trackedPlayers[playerId]) return { event: "notTracked" };
|
if (!player) return { event: "notTracked" };
|
||||||
|
|
||||||
delete trackedPlayers[playerId];
|
await db
|
||||||
await writeFile(TRACKED_PLAYER_FILE, JSON.stringify(trackedPlayers));
|
.delete(tables.trackedPlayers)
|
||||||
|
.where(eq(tables.trackedPlayers.playerId, playerId));
|
||||||
|
|
||||||
return { event: "trackerRemoved" };
|
return { event: "trackerRemoved" };
|
||||||
}
|
}
|
||||||
@@ -42,23 +35,35 @@ export async function trackWovPlayer(playerId: string): Promise<
|
|||||||
| { event: "changed"; oldUsernames: string[]; newUsername: string }
|
| { event: "changed"; oldUsernames: string[]; newUsername: string }
|
||||||
| { event: "none" }
|
| { event: "none" }
|
||||||
> {
|
> {
|
||||||
const content = await readFile(TRACKED_PLAYER_FILE, "utf-8");
|
|
||||||
const trackedPlayers: TrackedPlayers = JSON.parse(content);
|
|
||||||
|
|
||||||
const player = await getPlayer(playerId);
|
const player = await getPlayer(playerId);
|
||||||
if (!player) return { event: "notFound" };
|
if (!player) return { event: "notFound" };
|
||||||
|
|
||||||
const currentUsernames = trackedPlayers[playerId];
|
const tracked = await db.query.trackedPlayers.findFirst({
|
||||||
if (currentUsernames) {
|
where: eq(tables.trackedPlayers.playerId, playerId),
|
||||||
const oldUsernames = [...currentUsernames];
|
with: {
|
||||||
if (!currentUsernames.includes(player.username)) {
|
usernameHistory: {
|
||||||
currentUsernames.push(player.username);
|
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 {
|
return {
|
||||||
event: "changed",
|
event: "changed",
|
||||||
oldUsernames,
|
oldUsernames: currentUsernames,
|
||||||
newUsername: player.username,
|
newUsername: player.username,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@@ -67,8 +72,15 @@ export async function trackWovPlayer(playerId: string): Promise<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
trackedPlayers[playerId] = [player.username];
|
await db.insert(tables.trackedPlayers).values({
|
||||||
await writeFile(TRACKED_PLAYER_FILE, JSON.stringify(trackedPlayers));
|
playerId,
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.insert(tables.usernameHistory).values({
|
||||||
|
playerId,
|
||||||
|
username: player.username,
|
||||||
|
});
|
||||||
|
|
||||||
return { event: "registered" };
|
return { event: "registered" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
import { mkdir, readFile, writeFile, access } from "node:fs/promises";
|
import { redis } from "@lbf-bot/database";
|
||||||
import { constants } from "node:fs";
|
|
||||||
import type { QuestResult } from "~/types";
|
import type { QuestResult } from "~/types";
|
||||||
|
|
||||||
export const getLatestQuest = async (): Promise<QuestResult> => {
|
export const getLatestQuest = async (): Promise<QuestResult> => {
|
||||||
@@ -17,45 +16,23 @@ export const getLatestQuest = async (): Promise<QuestResult> => {
|
|||||||
|
|
||||||
export const checkForNewQuest = async (): Promise<QuestResult | null> => {
|
export const checkForNewQuest = async (): Promise<QuestResult | null> => {
|
||||||
const lastQuest = await getLatestQuest();
|
const lastQuest = await getLatestQuest();
|
||||||
|
|
||||||
const lastId = lastQuest.quest.id;
|
const lastId = lastQuest.quest.id;
|
||||||
const cacheFilePath = ".cache/.quest_cache";
|
|
||||||
await mkdir(".cache", { recursive: true });
|
|
||||||
|
|
||||||
try {
|
const cachedQuestId = await redis.get("quest:last_id");
|
||||||
await access(cacheFilePath, constants.F_OK);
|
|
||||||
const cachedQuestId = await readFile(cacheFilePath, "utf-8");
|
|
||||||
if (cachedQuestId === lastId || cachedQuestId === "IGNORE") {
|
if (cachedQuestId === lastId || cachedQuestId === "IGNORE") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
// File doesn't exist, continue
|
|
||||||
}
|
|
||||||
|
|
||||||
await writeFile(cacheFilePath, lastId);
|
await redis.set("quest:last_id", lastId);
|
||||||
return lastQuest;
|
return lastQuest;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getClanMembers = async (): Promise<
|
export const getClanMembers = async (): Promise<
|
||||||
Array<{ playerId: string; username: string }>
|
Array<{ playerId: string; username: string }>
|
||||||
> => {
|
> => {
|
||||||
const cacheFilePath = ".clan_members_cache";
|
const cached = await redis.get("clan:members");
|
||||||
await mkdir(".cache", { recursive: true });
|
if (cached) {
|
||||||
|
return JSON.parse(cached);
|
||||||
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 response = await fetch(
|
const response = await fetch(
|
||||||
@@ -69,10 +46,8 @@ export const getClanMembers = async (): Promise<
|
|||||||
playerId: string;
|
playerId: string;
|
||||||
username: string;
|
username: string;
|
||||||
}>;
|
}>;
|
||||||
await writeFile(
|
|
||||||
cacheFilePath,
|
await redis.set("clan:members", JSON.stringify(data), "EX", 60 * 60);
|
||||||
JSON.stringify({ timestamp: Date.now(), data }),
|
|
||||||
);
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user