Spaces:
Configuration error
Configuration error
| import { mkdir, readFile, writeFile } from "node:fs/promises"; | |
| import path from "node:path"; | |
| const cloneWallet = (wallet) => ({ ...wallet }); | |
| const createWallet = (userId, displayName, startingBalance) => { | |
| return { | |
| userId, | |
| displayName, | |
| balance: startingBalance, | |
| wins: 0, | |
| losses: 0, | |
| streak: 0, | |
| bestStreak: 0, | |
| lastDailyAt: null, | |
| rivalries: {}, | |
| updatedAt: new Date().toISOString(), | |
| }; | |
| }; | |
| export const resolveDataFile = (rootDir, fileName) => { | |
| return path.resolve(rootDir, fileName); | |
| }; | |
| export class JsonWalletRepository { | |
| filePath; | |
| startingBalance; | |
| state; | |
| constructor(filePath, startingBalance) { | |
| this.filePath = filePath; | |
| this.startingBalance = startingBalance; | |
| } | |
| async getWallet(userId, displayName) { | |
| const state = await this.ensureState(); | |
| const wallet = state.wallets[userId] ?? this.upsertWallet(state, userId, displayName); | |
| return cloneWallet(wallet); | |
| } | |
| async canCover(userId, displayName, amount) { | |
| const wallet = await this.getWallet(userId, displayName); | |
| return wallet.balance >= amount; | |
| } | |
| async listWallets() { | |
| const state = await this.ensureState(); | |
| return Object.values(state.wallets) | |
| .map(cloneWallet) | |
| .sort((left, right) => right.balance - left.balance || right.wins - left.wins || left.losses - right.losses); | |
| } | |
| async claimDailyBonus(userId, displayName, bonusAmount, cooldownMs) { | |
| const state = await this.ensureState(); | |
| const wallet = this.upsertWallet(state, userId, displayName); | |
| const now = Date.now(); | |
| const lastDaily = wallet.lastDailyAt ? new Date(wallet.lastDailyAt).getTime() : 0; | |
| if (now - lastDaily < cooldownMs) { | |
| return { credited: false, amount: 0, nextEligibleAt: lastDaily + cooldownMs }; | |
| } | |
| wallet.balance += bonusAmount; | |
| wallet.lastDailyAt = new Date().toISOString(); | |
| wallet.updatedAt = new Date().toISOString(); | |
| await this.persist(state); | |
| return { credited: true, amount: bonusAmount, nextEligibleAt: now + cooldownMs }; | |
| } | |
| async recordMatch(options) { | |
| const state = await this.ensureState(); | |
| const winner = this.upsertWallet(state, options.winnerId, options.winnerName); | |
| const loser = this.upsertWallet(state, options.loserId, options.loserName); | |
| loser.balance = Math.max(0, loser.balance - options.wager); | |
| loser.losses += 1; | |
| loser.streak = 0; | |
| loser.updatedAt = new Date().toISOString(); | |
| winner.balance += options.wager; | |
| winner.wins += 1; | |
| winner.streak += 1; | |
| winner.bestStreak = Math.max(winner.bestStreak, winner.streak); | |
| winner.updatedAt = new Date().toISOString(); | |
| this.upsertRivalry(winner, options.loserId, options.loserName, "win"); | |
| this.upsertRivalry(loser, options.winnerId, options.winnerName, "loss"); | |
| await this.persist(state); | |
| return { | |
| winner: cloneWallet(winner), | |
| loser: cloneWallet(loser), | |
| }; | |
| } | |
| topRivalry(wallet) { | |
| const entries = Object.values(wallet.rivalries); | |
| if (entries.length === 0) | |
| return undefined; | |
| return entries.reduce((best, entry) => { | |
| const bestTotal = best.wins + best.losses; | |
| const entryTotal = entry.wins + entry.losses; | |
| return entryTotal > bestTotal ? entry : best; | |
| }); | |
| } | |
| allRivalries(wallet) { | |
| return Object.values(wallet.rivalries).sort((a, b) => (b.wins + b.losses) - (a.wins + a.losses)); | |
| } | |
| /** | |
| * Directly adjust a wallet balance by a signed delta (positive = add, negative = subtract). | |
| * Balance is floored at 0. Returns the updated wallet record. | |
| */ | |
| async adjustBalance(userId, displayName, delta) { | |
| const state = await this.ensureState(); | |
| const wallet = this.upsertWallet(state, userId, displayName); | |
| wallet.balance = Math.max(0, wallet.balance + delta); | |
| wallet.updatedAt = new Date().toISOString(); | |
| await this.persist(state); | |
| return cloneWallet(wallet); | |
| } | |
| async ensureState() { | |
| if (this.state) { | |
| return this.state; | |
| } | |
| await mkdir(path.dirname(this.filePath), { recursive: true }); | |
| try { | |
| const raw = await readFile(this.filePath, "utf8"); | |
| this.state = JSON.parse(raw); | |
| } | |
| catch { | |
| this.state = { | |
| version: 1, | |
| wallets: {}, | |
| }; | |
| await this.persist(this.state); | |
| } | |
| return this.state; | |
| } | |
| upsertWallet(state, userId, displayName) { | |
| const existing = state.wallets[userId]; | |
| if (existing) { | |
| existing.displayName = displayName; | |
| return existing; | |
| } | |
| const created = createWallet(userId, displayName, this.startingBalance); | |
| state.wallets[userId] = created; | |
| return created; | |
| } | |
| upsertRivalry(wallet, opponentId, opponentName, outcome) { | |
| const existing = wallet.rivalries[opponentId]; | |
| if (existing) { | |
| existing.opponentName = opponentName; | |
| if (outcome === "win") | |
| existing.wins += 1; | |
| else | |
| existing.losses += 1; | |
| return; | |
| } | |
| wallet.rivalries[opponentId] = { | |
| opponentId, | |
| opponentName, | |
| wins: outcome === "win" ? 1 : 0, | |
| losses: outcome === "loss" ? 1 : 0, | |
| }; | |
| } | |
| async persist(state) { | |
| await writeFile(this.filePath, JSON.stringify(state, null, 2), "utf8"); | |
| } | |
| } | |
| const presetKey = (guildId, userId) => `${guildId}:${userId}`; | |
| export class JsonDesignationPresetRepository { | |
| filePath; | |
| state; | |
| constructor(filePath) { | |
| this.filePath = filePath; | |
| } | |
| async getPreset(guildId, userId) { | |
| const state = await this.ensureState(); | |
| return state.presets[presetKey(guildId, userId)]?.roleIds; | |
| } | |
| async savePreset(guildId, userId, roleIds) { | |
| const state = await this.ensureState(); | |
| state.presets[presetKey(guildId, userId)] = { | |
| userId, | |
| guildId, | |
| roleIds: [...roleIds], | |
| updatedAt: new Date().toISOString(), | |
| }; | |
| await this.persist(state); | |
| } | |
| async ensureState() { | |
| if (this.state) { | |
| return this.state; | |
| } | |
| await mkdir(path.dirname(this.filePath), { recursive: true }); | |
| try { | |
| const raw = await readFile(this.filePath, "utf8"); | |
| this.state = JSON.parse(raw); | |
| } | |
| catch { | |
| this.state = { version: 1, presets: {} }; | |
| await this.persist(this.state); | |
| } | |
| return this.state; | |
| } | |
| async persist(state) { | |
| await writeFile(this.filePath, JSON.stringify(state, null, 2), "utf8"); | |
| } | |
| } | |
| //# sourceMappingURL=index.js.map |