From fbd02f5f928515a75d5675133e241385a5c63eb3 Mon Sep 17 00:00:00 2001 From: PartMan Date: Mon, 19 Oct 2026 16:07:43 +0530 Subject: [PATCH] chore: Support ps-client@6 - move away from static datacenters and use the showdown cache layer and call Yosh a nerd --- package-lock.json | 9 +++-- package.json | 2 +- src/cache/showdown/index.ts | 33 +++++++++++----- src/cache/showdown/types.ts | 8 +++- src/database/games.ts | 2 +- src/eval.ts | 2 +- src/index.ts | 20 ++++++---- src/ps/commands/filter.tsx | 3 +- src/ps/commands/kunc.ts | 7 +--- src/ps/commands/modnote.tsx | 5 ++- src/ps/commands/namecolour.tsx | 5 +-- src/ps/datacenters.ts | 60 +++++++++++++++++++++++++++++ src/ps/games/scrabble/checker.ts | 3 +- src/ps/games/snakesladders/index.ts | 5 +-- src/ps/handlers/cron/hindi.ts | 2 +- src/sentinel/hotpatch.ts | 5 +-- src/utils/components/username.tsx | 4 +- src/utils/queryMon.ts | 4 +- 18 files changed, 127 insertions(+), 52 deletions(-) create mode 100644 src/ps/datacenters.ts diff --git a/package-lock.json b/package-lock.json index 1d362cb8..746203c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "flat-cache": "^6.1.1", "mongoose": "^8.12.1", "obscenity": "^0.4.5", - "ps-client": "^5.4.0", + "ps-client": "^6.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-json-tree": "^0.20.0", @@ -6844,12 +6844,13 @@ } }, "node_modules/ps-client": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/ps-client/-/ps-client-5.4.0.tgz", - "integrity": "sha512-/Q6tlBQtV3P1NMsOGMTCaxG21vYrU78VM7776zBC4QuX7/DHi5Mvy7usrGRhLVVwJOkigJaiwaLdSdAO/ADStg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ps-client/-/ps-client-6.0.0.tgz", + "integrity": "sha512-88Mt/hUg/BQNDAAtPrex8xX7D0esWGjXKNBeQF525dtxViMI7OEnP6Yk36jJPK9G3owFW51Bksj7ZHbzkVn/wg==", "license": "ISC", "dependencies": { "isomorphic-ws": "^5.0.0", + "json5": "^2.2.3", "ws": "^8.18.0" }, "engines": { diff --git a/package.json b/package.json index e6224413..201705ab 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "flat-cache": "^6.1.1", "mongoose": "^8.12.1", "obscenity": "^0.4.5", - "ps-client": "^5.4.0", + "ps-client": "^6.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-json-tree": "^0.20.0", diff --git a/src/cache/showdown/index.ts b/src/cache/showdown/index.ts index 09d612c7..60acede8 100644 --- a/src/cache/showdown/index.ts +++ b/src/cache/showdown/index.ts @@ -1,6 +1,8 @@ import { FlatCache } from 'flat-cache'; +import { fetch as fetchPSDatacenters } from 'ps-client/tools'; import { ShowdownDataKeys } from '@/cache/showdown/types'; +import { applyPSDatacenters } from '@/ps/datacenters'; import { fsPath } from '@/utils/fsPath'; import type { ShowdownDataType } from '@/cache/showdown/types'; @@ -10,6 +12,7 @@ const RANDOMS_SETS_URL = export const ShowdownData: ShowdownDataType = { [ShowdownDataKeys.RandomSetsGen9]: {}, + [ShowdownDataKeys.PSDatacenters]: {}, }; const ShowdownMetadata: { @@ -28,25 +31,35 @@ const ShowdownMetadata: { return (await res.json()) as ShowdownDataType[ShowdownDataKeys.RandomSetsGen9]; }, }, + [ShowdownDataKeys.PSDatacenters]: { + key: ShowdownDataKeys.PSDatacenters, + cacheId: 'ps-datacenters.json', + update: () => fetchPSDatacenters(), + }, }; -const ShowdownFlatCaches = Object.fromEntries( - Object.values(ShowdownMetadata).map(({ key, cacheId }) => { - const flatCache = new FlatCache({ cacheId, cacheDir: fsPath('cache', 'flat-cache') }); - flatCache.load(cacheId); +function hydrateShowdownData(key: Key, data: ShowdownDataType[Key]) { + ShowdownData[key] = data; + if (key === ShowdownDataKeys.PSDatacenters) applyPSDatacenters(data as ShowdownDataType[ShowdownDataKeys.PSDatacenters]); +} + +const ShowdownFlatCaches = {} as Record; +for (const key of Object.keys(ShowdownMetadata) as ShowdownDataKeys[]) { + const { cacheId } = ShowdownMetadata[key]; + const flatCache = new FlatCache({ cacheId, cacheDir: fsPath('cache', 'flat-cache') }); + flatCache.load(cacheId); - const existing = flatCache.get('value'); - if (existing) ShowdownData[key] = existing; - return [key, flatCache]; - }) -) as Record; + const existing = flatCache.get('value'); + if (existing) hydrateShowdownData(key, existing); + ShowdownFlatCaches[key] = flatCache; +} export async function updateShowdownData(keys?: ShowdownDataKeys[]): Promise { const toUpdate = keys ?? (Object.keys(ShowdownMetadata) as ShowdownDataKeys[]); await Promise.all( toUpdate.map(async key => { const data = await ShowdownMetadata[key].update(); - ShowdownData[key] = data; + hydrateShowdownData(key, data); ShowdownFlatCaches[key].set('value', data); ShowdownFlatCaches[key].save(); }) diff --git a/src/cache/showdown/types.ts b/src/cache/showdown/types.ts index 1a5ad507..fc1cf449 100644 --- a/src/cache/showdown/types.ts +++ b/src/cache/showdown/types.ts @@ -1,5 +1,8 @@ +import type { Datacenters } from 'ps-client/data'; + export enum ShowdownDataKeys { RandomSetsGen9 = 'random-sets-gen-9', + PSDatacenters = 'ps-datacenters', } export type Gen9ShowdownSet = { @@ -9,6 +12,7 @@ export type Gen9ShowdownSet = { teraTypes: string[]; }; -export interface ShowdownDataType extends Record { +export type ShowdownDataType = { [ShowdownDataKeys.RandomSetsGen9]: Record; -} + [ShowdownDataKeys.PSDatacenters]: Partial; +}; diff --git a/src/database/games.ts b/src/database/games.ts index 72dd828d..29e64936 100644 --- a/src/database/games.ts +++ b/src/database/games.ts @@ -1,8 +1,8 @@ import { Temporal } from '@js-temporal/polyfill'; import mongoose, { type HydratedDocument } from 'mongoose'; -import { pokedex } from 'ps-client/data'; import { IS_ENABLED } from '@/enabled'; +import { pokedex } from '@/ps/datacenters'; import { ScrabbleMods } from '@/ps/games/scrabble/constants'; import { GamesList } from '@/ps/games/types'; import { TimeZone } from '@/ps/handlers/cron/constants'; diff --git a/src/eval.ts b/src/eval.ts index 9961d0f7..1367e330 100644 --- a/src/eval.ts +++ b/src/eval.ts @@ -2,10 +2,10 @@ import _fsSync, { promises as _fs } from 'fs'; import _path from 'path'; -import { pokedex } from 'ps-client/data'; import { inspect } from 'util'; import * as _cache from '@/cache'; +import { pokedex } from '@/ps/datacenters'; import _Sentinel from '@/sentinel'; import { ansiToHtml } from '@/utils/ansiToHtml'; import { cachebust as _cachebust } from '@/utils/cachebust'; diff --git a/src/index.ts b/src/index.ts index 8fc26e48..b9505fb0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,17 @@ import 'dotenv/config'; -import '@/globals'; -import { Logger } from '@/utils/logger'; -Logger.log('PartBot is starting up...'); +(async () => { + await import('@/cache/showdown'); + const { ensurePSDatacenters } = await import('@/ps/datacenters'); + await ensurePSDatacenters(); -import '@/discord'; -import '@/ps'; -import '@/web'; + await import('@/globals'); + const { Logger } = await import('@/utils/logger'); -import '@/sentinel'; + Logger.log('PartBot is starting up...'); + + await import('@/discord'); + await import('@/ps'); + await import('@/web'); + await import('@/sentinel'); +})(); diff --git a/src/ps/commands/filter.tsx b/src/ps/commands/filter.tsx index 43bb7b3a..6447b9d6 100644 --- a/src/ps/commands/filter.tsx +++ b/src/ps/commands/filter.tsx @@ -1,5 +1,4 @@ -import { abilities, items, moves, pokedex } from 'ps-client/data'; - +import { abilities, items, moves, pokedex } from '@/ps/datacenters'; import { ChatError } from '@/utils/chatError'; import { toId } from '@/utils/toId'; diff --git a/src/ps/commands/kunc.ts b/src/ps/commands/kunc.ts index f9cd7d80..8edf5fd3 100644 --- a/src/ps/commands/kunc.ts +++ b/src/ps/commands/kunc.ts @@ -1,9 +1,8 @@ -import { pokedex } from 'ps-client/data'; - import { PSKuncInProgress } from '@/cache'; import { ShowdownData } from '@/cache/showdown'; import { ShowdownDataKeys } from '@/cache/showdown/types'; import { prefix } from '@/config/ps'; +import { pokedex } from '@/ps/datacenters'; import { ChatError } from '@/utils/chatError'; import { debounce } from '@/utils/debounce'; import { DeferredPromise } from '@/utils/deferredPromise'; @@ -73,9 +72,7 @@ export const command: PSCommand = { if (solved.length > 0) { const guessers = solved.map(user => user.name).list($T); - message.reply( - $T('COMMANDS.KUNC.CORRECT_GUESSERS', { guessers, solution: matchingNames.list($T('GRAMMAR.OR')) }) - ); + message.reply($T('COMMANDS.KUNC.CORRECT_GUESSERS', { guessers, solution: matchingNames.list($T('GRAMMAR.OR')) })); return solved; } else { message.reply($T('COMMANDS.KUNC.NO_GUESSERS', { solution: matchingNames.list($T('GRAMMAR.OR')) })); diff --git a/src/ps/commands/modnote.tsx b/src/ps/commands/modnote.tsx index 628c0283..f82f7c52 100644 --- a/src/ps/commands/modnote.tsx +++ b/src/ps/commands/modnote.tsx @@ -1,6 +1,7 @@ -import { HSL, formatText } from 'ps-client/tools'; +import { formatText } from 'ps-client/tools'; import { isGlobalBot, prefix } from '@/config/ps'; +import { psHSL } from '@/ps/datacenters'; import { ChatError } from '@/utils/chatError'; import { Button, Form } from '@/utils/components/ps'; import { toId } from '@/utils/toId'; @@ -55,7 +56,7 @@ export const command: PSCommand = { if (!isStaff(userInRoom)) throw new ChatError($T('ACCESS_DENIED')); const nonGlobalPrefix = !isGlobalBot ? `/msgroom ${targetRoom.id},` : ''; - const [h, s, l] = HSL(message.author.id).hsl; + const [h, s, l] = psHSL(message.author.id).hsl; targetRoom.sendHTML(
diff --git a/src/ps/commands/namecolour.tsx b/src/ps/commands/namecolour.tsx index 08c33757..50032d0f 100644 --- a/src/ps/commands/namecolour.tsx +++ b/src/ps/commands/namecolour.tsx @@ -1,5 +1,4 @@ -import { Tools as ClientTools } from 'ps-client'; - +import { psHSL } from '@/ps/datacenters'; import { HslToRgb, HslToString, RgbToHex, RgbToString } from '@/utils/color'; import type { PSCommand } from '@/types/chat'; @@ -21,7 +20,7 @@ export const command: PSCommand = { categories: ['utility'], async run({ message, arg, broadcastHTML }) { const target = arg || message.author.name; - const data = ClientTools.HSL(target); + const data = psHSL(target); const current = convertedColors(data.hsl); const original = data.base ? convertedColors(data.base.hsl) : null; diff --git a/src/ps/datacenters.ts b/src/ps/datacenters.ts new file mode 100644 index 00000000..31ee55d8 --- /dev/null +++ b/src/ps/datacenters.ts @@ -0,0 +1,60 @@ +import { HSL as hslFromTools } from 'ps-client/tools'; + +import type { Datacenters } from 'ps-client/data'; + +export type { Ability, Item, Move, Species } from 'ps-client/data'; + +export const abilities: Datacenters['abilities'] = {}; +export const aliases: Datacenters['aliases'] = {}; +export const formatsData: Datacenters['formatsData'] = {}; +export const formats: Datacenters['formats'] = []; +export const items: Datacenters['items'] = {}; +export const learnsets: Datacenters['learnsets'] = {}; +export const moves: Datacenters['moves'] = {}; +export const pokedex: Datacenters['pokedex'] = {}; +export const typechart: Datacenters['typechart'] = {} as Datacenters['typechart']; +export const colors: Datacenters['colors'] = {}; + +const tables: Record, Record> = { + abilities, + aliases, + formatsData, + items, + learnsets, + moves, + pokedex, + typechart, +}; + +function inplaceReplace(target: Record, source: Record) { + for (const key of Object.keys(target)) delete target[key]; + Object.assign(target, source); +} + +export function applyPSDatacenters(data: Partial) { + for (const [key, value] of Object.entries(data) as [keyof Datacenters, Datacenters[keyof Datacenters]][]) { + if (key === 'formats') { + formats.length = 0; + formats.push(...(value as Datacenters['formats'])); + } else if (key === 'colors') { + inplaceReplace(colors, value as Record); + } else { + inplaceReplace(tables[key as keyof typeof tables], value as Record); + } + } +} + +export function datacentersLoaded() { + return Object.keys(pokedex).length > 0; +} + +export async function ensurePSDatacenters() { + if (datacentersLoaded()) return; + const { updateShowdownData } = await import('@/cache/showdown'); + const { ShowdownDataKeys } = await import('@/cache/showdown/types'); + await updateShowdownData([ShowdownDataKeys.PSDatacenters]); +} + +export function psHSL(name: string, opts?: { original?: boolean }) { + return hslFromTools(name, { ...opts, customColors: colors }); +} diff --git a/src/ps/games/scrabble/checker.ts b/src/ps/games/scrabble/checker.ts index 185d5f3b..a00987d2 100644 --- a/src/ps/games/scrabble/checker.ts +++ b/src/ps/games/scrabble/checker.ts @@ -1,5 +1,4 @@ -import { abilities, items, moves, pokedex } from 'ps-client/data'; - +import { abilities, items, moves, pokedex } from '@/ps/datacenters'; import { Dictionaries, ScrabbleMods } from '@/ps/games/scrabble/constants'; import { CRAZYMONS_SCORING, POKEMON_SCORING, ScrabbleModData } from '@/ps/games/scrabble/mods'; diff --git a/src/ps/games/snakesladders/index.ts b/src/ps/games/snakesladders/index.ts index 3b96b9dc..fb886ab1 100644 --- a/src/ps/games/snakesladders/index.ts +++ b/src/ps/games/snakesladders/index.ts @@ -1,5 +1,4 @@ -import { HSL } from 'ps-client/tools'; - +import { psHSL } from '@/ps/datacenters'; import { BaseGame } from '@/ps/games/game'; import { TOKEN_COLORS } from '@/ps/games/snakesladders/constants'; import { render } from '@/ps/games/snakesladders/render'; @@ -53,7 +52,7 @@ export class SnakesLadders extends BaseGame { onStart(): ActionResponse { const playerColors = Object.values(this.players).map<{ id: string; color: Hex }>(player => { - const [H, S, L] = HSL(player.id).hsl; + const [H, S, L] = psHSL(player.id).hsl; return { id: player.turn, color: HslToHex({ H, S, L, colorspace: 'hsla' }) }; }); diff --git a/src/ps/handlers/cron/hindi.ts b/src/ps/handlers/cron/hindi.ts index a1b6c711..b6a8dcaa 100644 --- a/src/ps/handlers/cron/hindi.ts +++ b/src/ps/handlers/cron/hindi.ts @@ -1,4 +1,3 @@ -import { pokedex } from 'ps-client/data'; import { uploadToPastie } from 'ps-client/tools'; import { PSCommands, PSRoomConfigs } from '@/cache'; @@ -6,6 +5,7 @@ import { queryPoints, resetPoints } from '@/database/points'; import { HINDI_LOGS } from '@/discord/constants/servers/hindi'; import { getChannel } from '@/discord/loaders/channels'; import { i18n } from '@/i18n'; +import { pokedex } from '@/ps/datacenters'; import { TimeZone } from '@/ps/handlers/cron/constants'; import { hasPoints } from '@/ps/handlers/cron/utils'; import { Logger } from '@/utils/logger'; diff --git a/src/sentinel/hotpatch.ts b/src/sentinel/hotpatch.ts index db235a3a..0a0f8d2e 100644 --- a/src/sentinel/hotpatch.ts +++ b/src/sentinel/hotpatch.ts @@ -1,9 +1,9 @@ import { promises as fs } from 'fs'; import path from 'path'; -import { update as updatePSData } from 'ps-client/tools'; import { PSRoomConfigs } from '@/cache'; import { updateShowdownData } from '@/cache/showdown'; +import { ShowdownDataKeys } from '@/cache/showdown/types'; import { fetchRoomConfigs } from '@/database/psrooms'; import PS from '@/ps'; import { registers } from '@/sentinel/registers'; @@ -71,8 +71,7 @@ export async function hotpatch(this: Sentinel, hotpatchType: HotpatchType, by: s } case 'data': { - await updatePSData(); - // TODO: cachebust + await updateShowdownData([ShowdownDataKeys.PSDatacenters]); break; } diff --git a/src/utils/components/username.tsx b/src/utils/components/username.tsx index 2c2d80e9..dadee983 100644 --- a/src/utils/components/username.tsx +++ b/src/utils/components/username.tsx @@ -1,4 +1,4 @@ -import { Tools as ClientTools } from 'ps-client'; +import { psHSL } from '@/ps/datacenters'; import type { ReactElement } from 'react'; @@ -13,7 +13,7 @@ export function Username({ clickable?: boolean; children?: string; }): ReactElement { - const namecolour = ClientTools.HSL(name); + const namecolour = psHSL(name); const [h, s, l] = useOriginalColor ? (namecolour.base?.hsl ?? namecolour.hsl) : namecolour.hsl; const nameEl = {children ?? name}; if (clickable) return {nameEl}; diff --git a/src/utils/queryMon.ts b/src/utils/queryMon.ts index f344a26f..1511dddc 100644 --- a/src/utils/queryMon.ts +++ b/src/utils/queryMon.ts @@ -1,6 +1,4 @@ -import { type Species, pokedex } from 'ps-client/data'; - - +import { type Species, pokedex } from '@/ps/datacenters'; import { toId } from '@/utils/toId'; export function queryMon(input: string): Species | null {