diff --git a/ts/.yarn/install-state.gz b/ts/.yarn/install-state.gz index 88999f3..95ad7f5 100644 Binary files a/ts/.yarn/install-state.gz and b/ts/.yarn/install-state.gz differ diff --git a/ts/barrelsby.json b/ts/barrelsby.json index b390548..7256314 100644 --- a/ts/barrelsby.json +++ b/ts/barrelsby.json @@ -1,10 +1,5 @@ { "delete": true, - "directory": [ - "./src/activities", - "./src/workflows" - ], - "exclude": [ - "types.ts" - ] + "directory": ["./src/activities", "./src/workflows"], + "exclude": ["types.ts"] } diff --git a/ts/biome.json b/ts/biome.json new file mode 100644 index 0000000..1ac6ae6 --- /dev/null +++ b/ts/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": ["**/dist/**", "**/node_modules/**"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 160, + "ignore": ["**/dist/**", "**/node_modules/**"] + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "es5", + "semicolons": "asNeeded", + "arrowParentheses": "always", + "bracketSameLine": false, + "quoteStyle": "single", + "attributePosition": "auto", + "bracketSpacing": true + } + } +} diff --git a/ts/package.json b/ts/package.json index fd99e75..92df4c3 100644 --- a/ts/package.json +++ b/ts/package.json @@ -10,6 +10,7 @@ "typecheck": "tsc --noEmit" }, "devDependencies": { + "@biomejs/biome": "1.9.4", "@types/node": "^22.13.4", "@types/object-hash": "^3", "@vitest/runner": "^3.2.3", @@ -29,8 +30,8 @@ "@temporalio/common": "^1.11.7", "@temporalio/worker": "^1.11.7", "@temporalio/workflow": "^1.11.7", - "@ts-rest/core": "https://gitpkg.vercel.app/aidant/ts-rest/libs/ts-rest/core?feat-standard-schema", - "@ts-rest/fastify": "https://gitpkg.vercel.app/aidant/ts-rest/libs/ts-rest/fastify?feat-standard-schema", + "@ts-rest/core": "^3.53.0-rc.1", + "@ts-rest/fastify": "^3.53.0-rc.1", "any-date-parser": "^2.0.3", "arktype": "2.1.1", "axios": "^1.7.9", diff --git a/ts/scratch/test.ts b/ts/scratch/test.ts index 7fc2eb8..13bb37f 100644 --- a/ts/scratch/test.ts +++ b/ts/scratch/test.ts @@ -1,18 +1,17 @@ -import {ArkErrors, type} from "arktype"; +import { ArkErrors, type } from 'arktype' - -const tupleType = type(["number","string"]) +const tupleType = type(['number', 'string']) const tupleArrayType = tupleType.array() const unionType = tupleType.or(tupleArrayType) // good -tupleType.assert([1,"2"]) +tupleType.assert([1, '2']) // good -tupleArrayType.assert([[1,"2"]]) +tupleArrayType.assert([[1, '2']]) // no good! -const resp = unionType([[1,"2"]]) -if(resp instanceof ArkErrors) { +const resp = unionType([[1, '2']]) +if (resp instanceof ArkErrors) { const err = resp[0] console.log(err.data) console.log(err.problem) diff --git a/ts/src/activities/database.ts b/ts/src/activities/database.ts index d2e4b87..1716e2a 100644 --- a/ts/src/activities/database.ts +++ b/ts/src/activities/database.ts @@ -1,40 +1,40 @@ -import stringify from 'json-stable-stringify'; -import { c } from "#/di"; -import { WApiV3ItemDatabase } from "#/lib/wynn/types"; -import { WApi } from "#/lib/wynn/wapi"; -import { PG } from "#/services/pg"; -import { ArkErrors } from "arktype"; -import { sha1Hash } from '#/lib/util/hashers'; -import { log } from '@temporalio/activity'; +import { log } from '@temporalio/activity' +import { ArkErrors } from 'arktype' +import stringify from 'json-stable-stringify' +import { c } from '#/di' +import { sha1Hash } from '#/lib/util/hashers' +import { WApiV3ItemDatabase } from '#/lib/wynn/types' +import { WApi } from '#/lib/wynn/wapi' +import { PG } from '#/services/pg' export async function update_wynn_items() { const api = await c.getAsync(WApi) - const ans = await api.get('/v3/item/database', {fullResult: ''}) - if(ans.status !== 200){ + const ans = await api.get('/v3/item/database', { fullResult: '' }) + if (ans.status !== 200) { throw new Error('Failed to get wynn items') } const parsed = WApiV3ItemDatabase(ans.data) - if(parsed instanceof ArkErrors){ + if (parsed instanceof ArkErrors) { throw parsed } - const {sql} = await c.getAsync(PG) + const { sql } = await c.getAsync(PG) // iterate over all items with their names const serializedData = stringify(parsed) - if(!serializedData){ + if (!serializedData) { throw new Error('Failed to serialize wynn items') } const dataHash = sha1Hash(serializedData) let found_new = false await sql.begin(async (sql) => { - const [{currenthash} = {}] = await sql`select value as currenthash from meta.hashes where key = 'wynn.items' limit 1` - if(currenthash === dataHash) { + const [{ currenthash } = {}] = await sql`select value as currenthash from meta.hashes where key = 'wynn.items' limit 1` + if (currenthash === dataHash) { return } found_new = true - log.info(`updating wynn with new hash`, {old: currenthash, new: dataHash}) - for(const [displayName, item] of Object.entries(parsed)){ + log.info(`updating wynn with new hash`, { old: currenthash, new: dataHash }) + for (const [displayName, item] of Object.entries(parsed)) { const json = stringify(item) - if(!json){ + if (!json) { throw new Error('Failed to serialize wynn item') } const itemHash = sha1Hash(json) diff --git a/ts/src/activities/discord.ts b/ts/src/activities/discord.ts index b1ad354..9875889 100644 --- a/ts/src/activities/discord.ts +++ b/ts/src/activities/discord.ts @@ -1,32 +1,27 @@ -import { Bot } from "#/discord/bot"; -import {c} from "#/di" -import { InteractionResponseTypes, InteractionCallbackOptions, InteractionCallbackData, InteractionTypes, MessageFlags } from "discordeno"; -import { InteractionRef } from "#/discord"; +import { type InteractionCallbackData, type InteractionCallbackOptions, InteractionResponseTypes, InteractionTypes, MessageFlags } from 'discordeno' +import { c } from '#/di' +import type { InteractionRef } from '#/discord' +import { Bot } from '#/discord/bot' // from https://github.com/discordeno/discordeno/blob/21.0.0/packages/bot/src/transformers/interaction.ts#L33 export const reply_to_interaction = async (props: { ref: InteractionRef type: number - options: InteractionCallbackOptions & {isPrivate?: boolean; content?: string} + options: InteractionCallbackOptions & { isPrivate?: boolean; content?: string } }) => { - const bot = await c.getAsync(Bot); + const bot = await c.getAsync(Bot) - const { - ref, - type, - options, - } = props; + const { ref, type, options } = props - let data: InteractionCallbackData = options; + const data: InteractionCallbackData = options if (options?.isPrivate) { - data.flags = MessageFlags.Ephemeral; + data.flags = MessageFlags.Ephemeral } - if(ref.acknowledged) { - return await bot.helpers.sendFollowupMessage(ref.token, data); + if (ref.acknowledged) { + return await bot.helpers.sendFollowupMessage(ref.token, data) } - return await bot.helpers.sendInteractionResponse(ref.id, ref.token, - { type, data }, { withResponse: options?.withResponse }) + return await bot.helpers.sendInteractionResponse(ref.id, ref.token, { type, data }, { withResponse: options?.withResponse }) } diff --git a/ts/src/activities/guild.ts b/ts/src/activities/guild.ts index 5daf91e..451c62c 100644 --- a/ts/src/activities/guild.ts +++ b/ts/src/activities/guild.ts @@ -1,25 +1,25 @@ -import { c } from "#/di"; -import { WapiV3GuildOverview } from "#/lib/wynn/types"; -import { WApi } from "#/lib/wynn/wapi"; -import { PG } from "#/services/pg"; -import { type } from "arktype"; -import {parseDate} from "chrono-node"; +import { type } from 'arktype' +import { parseDate } from 'chrono-node' +import { c } from '#/di' +import { WapiV3GuildOverview } from '#/lib/wynn/types' +import { WApi } from '#/lib/wynn/wapi' +import { PG } from '#/services/pg' export async function update_all_guilds() { const api = await c.getAsync(WApi) const ans = await api.get('/v3/guild/list/guild') - if(ans.status !== 200){ + if (ans.status !== 200) { throw new Error('Failed to get guild list from wapi') } const parsed = type({ - "[string]": { - uuid: "string", - prefix: "string", - } + '[string]': { + uuid: 'string', + prefix: 'string', + }, }).assert(ans.data) const { sql } = await c.getAsync(PG) - for(const [guild_name, guild] of Object.entries(parsed)){ + for (const [guild_name, guild] of Object.entries(parsed)) { await sql`insert into wynn.guild_info (uid, name, prefix) values @@ -32,13 +32,13 @@ export async function update_all_guilds() { } export async function update_guild({ - guild_name -}:{ - guild_name: string - }) { + guild_name, +}: { + guild_name: string +}) { const api = await c.getAsync(WApi) const ans = await api.get(`/v3/guild/${guild_name}`) - if(ans.status !== 200){ + if (ans.status !== 200) { throw new Error('Failed to get guild into from wapi') } const parsed = WapiV3GuildOverview.assert(ans.data) @@ -59,9 +59,9 @@ export async function update_guild({ wars = EXCLUDED.wars, created = EXCLUDED.created ` - const {total, ...rest} = parsed.members - for(const [rank_name, rank] of Object.entries(rest)){ - for(const [userName, member] of Object.entries(rank)) { + const { total, ...rest } = parsed.members + for (const [rank_name, rank] of Object.entries(rest)) { + for (const [userName, member] of Object.entries(rank)) { await sql`insert into wynn.guild_members (guild_id, member_id, rank, joined_at, contributed) values (${parsed.uuid}, ${member.uuid}, ${rank_name}, ${parseDate(member.joined)}, ${member.contributed}) diff --git a/ts/src/activities/guild_messages.ts b/ts/src/activities/guild_messages.ts index a78076a..315a9ea 100644 --- a/ts/src/activities/guild_messages.ts +++ b/ts/src/activities/guild_messages.ts @@ -1,13 +1,13 @@ -import { c } from "#/di"; -import { PG } from "#/services/pg"; -import { CreateMessageOptions, InteractionCallbackOptions } from "discordeno"; -import { type } from "arktype"; -import { TabWriter } from "#/lib/util/tabwriter"; -import { RANK_EMOJIS, getRankEmoji, formatNumber } from "#/lib/util/wynnfmt"; -import * as md from 'ts-markdown-builder'; +import { type } from 'arktype' +import type { CreateMessageOptions, InteractionCallbackOptions } from 'discordeno' +import * as md from 'ts-markdown-builder' +import { c } from '#/di' +import { TabWriter } from '#/lib/util/tabwriter' +import { RANK_EMOJIS, formatNumber, getRankEmoji } from '#/lib/util/wynnfmt' +import { PG } from '#/services/pg' export async function formGuildInfoMessage(guild_id: string): Promise { - const { sql } = await c.getAsync(PG); + const { sql } = await c.getAsync(PG) const result = await sql` with ranked as ( @@ -25,15 +25,15 @@ with ranked as ( ) select * from ranked where ranked.uid = ${guild_id} -`; +` if (result.length == 0) { return { - content: "No guild found.", - }; + content: 'No guild found.', + } } - const guild = result[0]; + const guild = result[0] const output = [ `# 🏰 Guild Information`, @@ -41,18 +41,18 @@ where ranked.uid = ${guild_id} `### 📊 Statistics`, `> **Level:** \`${guild.level}\``, `> **Total XP:** \`${formatNumber(guild.xp)}\``, - `> **XP Rank:** \`#${guild.xp_rank >= 1000 ? "1000+" : guild.xp_rank}\``, + `> **XP Rank:** \`#${guild.xp_rank >= 1000 ? '1000+' : guild.xp_rank}\``, `> **Territories:** \`${guild.territories}\``, `> **Wars:** \`${guild.wars.toLocaleString()}\``, - ].join("\n"); + ].join('\n') return { content: output, - }; + } } export async function formGuildOnlineMessage(guild_id: string): Promise { - const { sql } = await c.getAsync(PG); + const { sql } = await c.getAsync(PG) const result = await sql`select gi.name as guild_name, @@ -68,67 +68,73 @@ export async function formGuildOnlineMessage(guild_id: string): Promise Number(b.contributed) - Number(a.contributed)); + members.sort((a, b) => Number(b.contributed) - Number(a.contributed)) // Group members by server - const membersByServer = members.reduce((acc, member) => { - if (acc[member.server] == undefined) { - acc[member.server] = []; - } - acc[member.server].push(member); - return acc; - }, {} as Record); + const membersByServer = members.reduce( + (acc, member) => { + if (acc[member.server] == undefined) { + acc[member.server] = [] + } + acc[member.server].push(member) + return acc + }, + {} as Record + ) // Sort servers by player count - const sortedServers = Object.entries(membersByServer) - .sort(([, a], [, b]) => b.length - a.length); + const sortedServers = Object.entries(membersByServer).sort(([, a], [, b]) => b.length - a.length) // Build server sections const serverSections = sortedServers.map(([server, serverMembers]) => { - const memberList = serverMembers.map(m => { - const emoji = getRankEmoji(m.rank); - return `${emoji} ${m.name}`; - }).join(", "); + const memberList = serverMembers + .map((m) => { + const emoji = getRankEmoji(m.rank) + return `${emoji} ${m.name}` + }) + .join(', ') - return `### 🌐 ${server} (${serverMembers.length} player${serverMembers.length > 1 ? 's' : ''})\n> ${memberList}`; - }); + return `### 🌐 ${server} (${serverMembers.length} player${serverMembers.length > 1 ? 's' : ''})\n> ${memberList}` + }) const output = [ `# 🟢 Online Guild Members`, `**[${guildPrefix}] ${guildName}**\n`, `📊 **Total Online:** \`${members.length}\` members across \`${sortedServers.length}\` servers\n`, - ...serverSections - ].join("\n"); + ...serverSections, + ].join('\n') return { content: output, - }; + } } export async function formGuildLeaderboardMessage(guild_id: string): Promise { - const { sql } = await c.getAsync(PG); + const { sql } = await c.getAsync(PG) const result = await sql`select gi.name as guild_name, @@ -142,65 +148,59 @@ export async function formGuildLeaderboardMessage(guild_id: string): Promise Number(b.contributed) - Number(a.contributed)); - const topMembers = members.slice(0, 10); + members.sort((a, b) => Number(b.contributed) - Number(a.contributed)) + const topMembers = members.slice(0, 10) // Get guild info from first member (all have same guild info) - const guildName = members[0].guild_name; - const guildPrefix = members[0].guild_prefix; + const guildName = members[0].guild_name + const guildPrefix = members[0].guild_prefix // Calculate total guild XP - const totalXP = members.reduce((sum, m) => sum + Number(m.contributed), 0); + const totalXP = members.reduce((sum, m) => sum + Number(m.contributed), 0) // Build the leaderboard with proper alignment - const tw = new TabWriter(2); + const tw = new TabWriter(2) // Add header row - tw.add(["#", "Rank", "Player", "XP", "%"]); - tw.add(["───", "────────────", "────────────────", "──────────", "──────"]); // Separator line + tw.add(['#', 'Rank', 'Player', 'XP', '%']) + tw.add(['───', '────────────', '────────────────', '──────────', '──────']) // Separator line topMembers.forEach((member, index) => { - const position = index + 1; - const posStr = position === 1 ? "🥇" : position === 2 ? "🥈" : position === 3 ? "🥉" : `${position}.`; - const rankEmoji = getRankEmoji(member.rank); - const contribution = Number(member.contributed); - const percentage = ((contribution / totalXP) * 100).toFixed(1); + const position = index + 1 + const posStr = position === 1 ? '🥇' : position === 2 ? '🥈' : position === 3 ? '🥉' : `${position}.` + const rankEmoji = getRankEmoji(member.rank) + const contribution = Number(member.contributed) + const percentage = ((contribution / totalXP) * 100).toFixed(1) // Use formatNumber for consistent formatting - const contribFormatted = contribution >= 10_000 - ? formatNumber(contribution) - : contribution.toLocaleString(); + const contribFormatted = contribution >= 10_000 ? formatNumber(contribution) : contribution.toLocaleString() - tw.add([ - posStr, - `${rankEmoji} ${member.rank}`, - member.name, - contribFormatted, - `${percentage}%` - ]); - }); + tw.add([posStr, `${rankEmoji} ${member.rank}`, member.name, contribFormatted, `${percentage}%`]) + }) - const leaderboardTable = tw.build(); + const leaderboardTable = tw.build() // Create summary stats - const avgContribution = Math.floor(totalXP / members.length); + const avgContribution = Math.floor(totalXP / members.length) const output = [ `# 📊 Guild XP Leaderboard`, @@ -209,13 +209,13 @@ export async function formGuildLeaderboardMessage(guild_id: string): Promise{ - +export const scrape_online_players = async () => { const api = await c.getAsync(WApi) const raw = await api.get('/v3/player') const onlineList = type({ - total: "number", + total: 'number', players: { - "[string]": "string | null", - } + '[string]': 'string | null', + }, }).assert(raw.data) - const { sql } = await c.getAsync(PG) - for(const [playerName, server] of Object.entries(onlineList.players)){ + for (const [playerName, server] of Object.entries(onlineList.players)) { // we do this optimistically without a tx, because temporal will probably handle // the race, and the worst case is we do extra requests. const ans = await sql`select uid from minecraft.user where name = ${playerName} limit 1` - if(ans.length === 0){ + if (ans.length === 0) { // the user doesn't exist, so we need to grab their uuid try { const resp = await axios.get(`https://playerdb.co/api/player/minecraft/${playerName}`, { headers: { - "User-Agent": "lil-robot-guy (a@tuxpa.in)", - } + 'User-Agent': 'lil-robot-guy (a@tuxpa.in)', + }, }) const parsedPlayer = playerSchema.assert(resp.data) - if(!parsedPlayer.success){ + if (!parsedPlayer.success) { log.warn(`failed to get uuid for ${playerName}`, { - "payload": parsedPlayer, + payload: parsedPlayer, }) continue } @@ -95,22 +96,22 @@ export const scrape_online_players = async()=>{ name = EXCLUDED.name, server = EXCLUDED.server ` - }catch(e) { + } catch (e) { log.warn(`failed to get uuid for ${playerName}`, { - "err": e, + err: e, }) continue } } } - await sql.begin(async (sql)=>{ + await sql.begin(async (sql) => { await sql`update minecraft.user set server = null` - for(const [playerName, server] of Object.entries(onlineList.players)){ + for (const [playerName, server] of Object.entries(onlineList.players)) { try { await sql`update minecraft.user set server = ${server} where name = ${playerName}` - }catch(e) { + } catch (e) { log.warn(`failed to update server for ${playerName}`, { - "err": e, + err: e, }) continue } diff --git a/ts/src/apiserver/contract.ts b/ts/src/apiserver/contract.ts index 2db598b..b255ae7 100644 --- a/ts/src/apiserver/contract.ts +++ b/ts/src/apiserver/contract.ts @@ -1,49 +1,54 @@ -import { initContract } from "@ts-rest/core/src"; -import { type } from "arktype"; +import { initContract } from '@ts-rest/core' +import { type } from 'arktype' -const con = initContract(); +const con = initContract() -const ingameauth = con.router({ - challenge: { - description: "generate a challenge for the client to solve", - method: "GET", - path: "/challenge", - responses: { - 200: type({ - challenge: "string.uuid", +const ingameauth = con.router( + { + challenge: { + description: 'generate a challenge for the client to solve', + method: 'GET', + path: '/challenge', + responses: { + 200: type({ + challenge: 'string.uuid', + }), + }, + query: type({ + uuid: 'string.uuid', }), }, - query: type({ - uuid: "string.uuid", - }), + solve: { + description: 'attempt to solve the challenge and get the token for the challenge', + method: 'POST', + path: '/solve', + body: type({ + challenge: 'string.uuid', + uuid: 'string.uuid', + }), + responses: { + 200: type({ + success: 'true', + challenge: 'string.uuid', + uuid: 'string.uuid', + }), + 401: type({ + success: 'false', + reason: 'string', + }), + }, + }, }, - solve: { - description: "attempt to solve the challenge and get the token for the challenge", - method: "POST", - path: "/solve", - body: type({ - challenge: "string.uuid", - uuid: "string.uuid", - }), - responses: { - 200: type({ - success: "true", - challenge: "string.uuid", - uuid: "string.uuid", - }), - 401: type({ - success: "false", - reason: "string", - }), - }, - } -}, {pathPrefix: "/ingame"}) + { pathPrefix: '/ingame' } +) -export const api = con.router({ - "ingameauth": ingameauth, -}, {pathPrefix: "/api/v1"}) +export const api = con.router( + { + ingameauth: ingameauth, + }, + { pathPrefix: '/api/v1' } +) export const contract = con.router({ - api: api + api: api, }) - diff --git a/ts/src/cmd/bot.ts b/ts/src/cmd/bot.ts index 85190fc..aeea6ca 100644 --- a/ts/src/cmd/bot.ts +++ b/ts/src/cmd/bot.ts @@ -1,36 +1,28 @@ -import { Command } from 'clipanion'; - +import { Command } from 'clipanion' // di -import "#/services/pg" -import { DISCORD_GUILD_ID } from '#/constants'; -import { Bot } from '#/discord/bot'; -import { events } from '#/discord/botevent/handler'; -import { SLASH_COMMANDS } from '#/discord/botevent/slash_commands'; -import { c } from '#/di'; -import { config } from '#/config'; - +import '#/services/pg' +import { config } from '#/config' +import { DISCORD_GUILD_ID } from '#/constants' +import { c } from '#/di' +import { Bot } from '#/discord/bot' +import { events } from '#/discord/botevent/handler' +import { SLASH_COMMANDS } from '#/discord/botevent/slash_commands' export class BotCommand extends Command { - static paths = [['bot']]; + static paths = [['bot']] async execute() { - if(!config.DISCORD_TOKEN) { - throw new Error('no discord token found. bot cant start'); + if (!config.DISCORD_TOKEN) { + throw new Error('no discord token found. bot cant start') } const bot = await c.getAsync(Bot) bot.events = events() - console.log('registring slash commands'); + console.log('registring slash commands') await bot.rest.upsertGuildApplicationCommands(DISCORD_GUILD_ID, SLASH_COMMANDS).catch(console.error) - await bot.rest.upsertGuildApplicationCommands("547828454972850196", SLASH_COMMANDS).catch(console.error) + await bot.rest.upsertGuildApplicationCommands('547828454972850196', SLASH_COMMANDS).catch(console.error) - - console.log('connecting bot to gateway'); - await bot.start(); - console.log('bot connected'); + console.log('connecting bot to gateway') + await bot.start() + console.log('bot connected') } } - - - - - diff --git a/ts/src/cmd/worker.ts b/ts/src/cmd/worker.ts index dbb4ddd..f53c787 100644 --- a/ts/src/cmd/worker.ts +++ b/ts/src/cmd/worker.ts @@ -1,24 +1,21 @@ -import { Command } from 'clipanion'; +import { Command } from 'clipanion' -import { c } from '#/di'; +import { c } from '#/di' // di -import "#/services/temporal" -import { NativeConnection, Worker } from '@temporalio/worker'; -import * as activities from '../activities'; -import path from 'path'; -import { Client, ScheduleNotFoundError, ScheduleOptions, ScheduleOverlapPolicy } from '@temporalio/client'; -import { workflowSyncAllGuilds, workflowSyncGuilds, workflowSyncOnline, workflowSyncGuildLeaderboardInfo } from '#/workflows'; -import { PG } from '#/services/pg'; - - -import { config } from '#/config'; - +import '#/services/temporal' +import path from 'path' +import { Client, ScheduleNotFoundError, type ScheduleOptions, ScheduleOverlapPolicy } from '@temporalio/client' +import { NativeConnection, Worker } from '@temporalio/worker' +import { PG } from '#/services/pg' +import { workflowSyncAllGuilds, workflowSyncGuildLeaderboardInfo, workflowSyncGuilds, workflowSyncOnline } from '#/workflows' +import * as activities from '../activities' +import { config } from '#/config' const schedules: ScheduleOptions[] = [ { - scheduleId: "update-guild-players", + scheduleId: 'update-guild-players', action: { type: 'startWorkflow', workflowType: workflowSyncGuilds, @@ -28,13 +25,15 @@ const schedules: ScheduleOptions[] = [ overlap: ScheduleOverlapPolicy.SKIP, }, spec: { - intervals: [{ - every: '15 minutes', - }] + intervals: [ + { + every: '15 minutes', + }, + ], }, }, { - scheduleId: "update_guild_leaderboards", + scheduleId: 'update_guild_leaderboards', action: { type: 'startWorkflow', workflowType: workflowSyncGuildLeaderboardInfo, @@ -44,13 +43,15 @@ const schedules: ScheduleOptions[] = [ overlap: ScheduleOverlapPolicy.SKIP, }, spec: { - intervals: [{ - every: '5 minutes', - }] + intervals: [ + { + every: '5 minutes', + }, + ], }, }, { - scheduleId: "update-all-guilds", + scheduleId: 'update-all-guilds', action: { type: 'startWorkflow', workflowType: workflowSyncAllGuilds, @@ -60,13 +61,15 @@ const schedules: ScheduleOptions[] = [ overlap: ScheduleOverlapPolicy.SKIP, }, spec: { - intervals: [{ - every: '1 hour', - }] + intervals: [ + { + every: '1 hour', + }, + ], }, }, { - scheduleId: "update-online-players", + scheduleId: 'update-online-players', action: { type: 'startWorkflow', workflowType: workflowSyncOnline, @@ -76,38 +79,39 @@ const schedules: ScheduleOptions[] = [ overlap: ScheduleOverlapPolicy.SKIP, }, spec: { - intervals: [{ - every: '31 seconds', - }] + intervals: [ + { + every: '31 seconds', + }, + ], }, }, ] const addSchedules = async (c: Client) => { - for(const o of schedules) { + for (const o of schedules) { const handle = c.schedule.getHandle(o.scheduleId) try { - const desc = await handle.describe(); + const desc = await handle.describe() console.log(desc) - }catch(e: any){ - if(e instanceof ScheduleNotFoundError) { + } catch (e: any) { + if (e instanceof ScheduleNotFoundError) { await c.schedule.create(o) - }else { - throw e; + } else { + throw e } } } } - export class WorkerCommand extends Command { - static paths = [['worker']]; + static paths = [['worker']] async execute() { - const { db } = await c.getAsync(PG); + const { db } = await c.getAsync(PG) - const client = await c.getAsync(Client); + const client = await c.getAsync(Client) // schedules - await addSchedules(client); + await addSchedules(client) const connection = await NativeConnection.connect({ address: config.TEMPORAL_HOSTPORT, @@ -116,33 +120,28 @@ export class WorkerCommand extends Command { connection, namespace: config.TEMPORAL_NAMESPACE, workflowsPath: require.resolve('../workflows'), - dataConverter: { + dataConverter: { payloadConverterPath: require.resolve('../payload_converter'), }, bundlerOptions: { - webpackConfigHook: (config)=>{ - if(!config.resolve) config.resolve = {}; - if(!config.resolve.alias) config.resolve.alias = {}; + webpackConfigHook: (config) => { + if (!config.resolve) config.resolve = {} + if (!config.resolve.alias) config.resolve.alias = {} config.resolve!.alias = { - "#":path.resolve(process.cwd(),'src/'), + '#': path.resolve(process.cwd(), 'src/'), ...config.resolve!.alias, } - return config; - }}, + return config + }, + }, taskQueue: 'wynn-worker-ts', stickyQueueScheduleToStartTimeout: 5 * 1000, - activities - }); - await worker.run(); + activities, + }) + await worker.run() - - console.log("worked.run exited"); - await db.end(); - await connection.close(); + console.log('worked.run exited') + await db.end() + await connection.close() } } - - - - - diff --git a/ts/src/config/index.ts b/ts/src/config/index.ts index b1862c1..3257675 100644 --- a/ts/src/config/index.ts +++ b/ts/src/config/index.ts @@ -1,7 +1,7 @@ -import { z } from 'zod'; -import { parseEnv} from 'znv'; -import {config as dotenvConfig} from 'dotenv'; -dotenvConfig(); +import { config as dotenvConfig } from 'dotenv' +import { parseEnv } from 'znv' +import { z } from 'zod' +dotenvConfig() const schemaConfig = { DISCORD_TOKEN: z.string().optional(), @@ -17,9 +17,8 @@ const schemaConfig = { PG_PORT: z.number().int().optional(), PG_SSLMODE: z.string().optional(), - WAPI_URL: z.string().default("https://api.wynncraft.com/"), + WAPI_URL: z.string().default('https://api.wynncraft.com/'), REDIS_URL: z.string().optional(), -}; - +} export const config = parseEnv(process.env, schemaConfig) diff --git a/ts/src/constants/index.ts b/ts/src/constants/index.ts index d8ea932..76c07bb 100644 --- a/ts/src/constants/index.ts +++ b/ts/src/constants/index.ts @@ -1,3 +1,3 @@ -export const DISCORD_GUILD_ID = "1340213134949875835"; -export const WYNN_GUILD_NAME = "less than three" -export const WYNN_GUILD_ID = "2b717c60-ae61-4073-9d4f-c9c4583afed5"; +export const DISCORD_GUILD_ID = '1340213134949875835' +export const WYNN_GUILD_NAME = 'less than three' +export const WYNN_GUILD_ID = '2b717c60-ae61-4073-9d4f-c9c4583afed5' diff --git a/ts/src/di/index.ts b/ts/src/di/index.ts index 69acd46..2dfbeea 100644 --- a/ts/src/di/index.ts +++ b/ts/src/di/index.ts @@ -1,5 +1,5 @@ -import { Container, InjectionToken } from "@needle-di/core"; -import { Sql } from "postgres"; +import { Container, InjectionToken } from '@needle-di/core' +import type { Sql } from 'postgres' -export const c = new Container(); -export const T_PG = new InjectionToken("T_PG") +export const c = new Container() +export const T_PG = new InjectionToken('T_PG') diff --git a/ts/src/discord/bot.ts b/ts/src/discord/bot.ts index 130f230..aaffa7a 100644 --- a/ts/src/discord/bot.ts +++ b/ts/src/discord/bot.ts @@ -1,24 +1,24 @@ -import { config } from "#/config"; -import { c } from "#/di"; -import { InjectionToken } from "@needle-di/core"; -import { createBot, } from "discordeno"; -import { BotType, createBotParameters } from "./index"; +import { InjectionToken } from '@needle-di/core' +import { createBot } from 'discordeno' +import { config } from '#/config' +import { c } from '#/di' +import { type BotType, createBotParameters } from './index' const createBotWithToken = (token: string) => { - return createBot({ - ...createBotParameters, - token, - }) + return createBot({ + ...createBotParameters, + token, + }) } -export const Bot = new InjectionToken("DISCORD_BOT") +export const Bot = new InjectionToken('DISCORD_BOT') c.bind({ - provide: Bot, - useFactory: () => { - let token = config.DISCORD_TOKEN - if(!token) { - throw new Error('no discord token found. bot cant start'); - } - const bot = createBotWithToken(token) - return bot - }, + provide: Bot, + useFactory: () => { + const token = config.DISCORD_TOKEN + if (!token) { + throw new Error('no discord token found. bot cant start') + } + const bot = createBotWithToken(token) + return bot + }, }) diff --git a/ts/src/discord/botevent/command_parser.spec.ts b/ts/src/discord/botevent/command_parser.spec.ts index e8cf821..5f41162 100644 --- a/ts/src/discord/botevent/command_parser.spec.ts +++ b/ts/src/discord/botevent/command_parser.spec.ts @@ -1,7 +1,7 @@ -import { describe, it, expect, vi } from 'vitest' -import { ApplicationCommandOptionTypes, ApplicationCommandTypes, CreateApplicationCommand } from '@discordeno/types' -import { createCommandHandler, ExtractCommands } from './command_parser' +import { ApplicationCommandOptionTypes, ApplicationCommandTypes, type CreateApplicationCommand } from '@discordeno/types' +import { describe, expect, it, vi } from 'vitest' import type { InteractionData } from '..' +import { type ExtractCommands, createCommandHandler } from './command_parser' // Test command definitions const TEST_COMMANDS = [ @@ -83,9 +83,7 @@ describe('createCommandHandler', () => { const interactionData: InteractionData = { name: 'simple', - options: [ - { name: 'message', type: ApplicationCommandOptionTypes.String, value: 'Hello world' }, - ], + options: [{ name: 'message', type: ApplicationCommandOptionTypes.String, value: 'Hello world' }], } await handler(interactionData) @@ -425,4 +423,4 @@ describe('ExtractCommands type utility', () => { expect(handlers).toBeDefined() }) -}) \ No newline at end of file +}) diff --git a/ts/src/discord/botevent/command_parser.ts b/ts/src/discord/botevent/command_parser.ts index 61f7765..c7d468c 100644 --- a/ts/src/discord/botevent/command_parser.ts +++ b/ts/src/discord/botevent/command_parser.ts @@ -1,28 +1,22 @@ -import { ApplicationCommandOptionTypes, CreateApplicationCommand, DiscordInteractionDataOption} from "@discordeno/types"; -import { SLASH_COMMANDS } from "./slash_commands"; -import { InteractionData } from ".."; +import { ApplicationCommandOptionTypes, type CreateApplicationCommand, type DiscordInteractionDataOption } from '@discordeno/types' +import type { InteractionData } from '..' +import type { SLASH_COMMANDS } from './slash_commands' // Map option types to their TypeScript types type OptionTypeMap = { - [ApplicationCommandOptionTypes.String]: string; - [ApplicationCommandOptionTypes.Integer]: number; - [ApplicationCommandOptionTypes.Boolean]: boolean; - [ApplicationCommandOptionTypes.User]: string; // user ID - [ApplicationCommandOptionTypes.Channel]: string; // channel ID - [ApplicationCommandOptionTypes.Role]: string; // role ID - [ApplicationCommandOptionTypes.Number]: number; - [ApplicationCommandOptionTypes.Mentionable]: string; // ID - [ApplicationCommandOptionTypes.Attachment]: string; // attachment ID -}; + [ApplicationCommandOptionTypes.String]: string + [ApplicationCommandOptionTypes.Integer]: number + [ApplicationCommandOptionTypes.Boolean]: boolean + [ApplicationCommandOptionTypes.User]: string // user ID + [ApplicationCommandOptionTypes.Channel]: string // channel ID + [ApplicationCommandOptionTypes.Role]: string // role ID + [ApplicationCommandOptionTypes.Number]: number + [ApplicationCommandOptionTypes.Mentionable]: string // ID + [ApplicationCommandOptionTypes.Attachment]: string // attachment ID +} // Helper type to get option by name -type GetOption = Options extends readonly any[] - ? Options[number] extends infer O - ? O extends { name: Name } - ? O - : never - : never - : never; +type GetOption = Options extends readonly any[] ? (Options[number] extends infer O ? (O extends { name: Name } ? O : never) : never) : never // Extract the argument types from command options export type ExtractArgs = { @@ -32,11 +26,11 @@ export type ExtractArgs = { ? OptionTypeMap[T] : OptionTypeMap[T] | undefined : never - : never; -}; + : never +} // Handler function type that accepts typed arguments -type HandlerFunction = (args: Args) => Promise | void; +type HandlerFunction = (args: Args) => Promise | void // Get subcommand by name type GetSubcommand = Options extends readonly any[] @@ -45,21 +39,17 @@ type GetSubcommand = Options extends readonly any[] ? O : never : never - : never; + : never // Check if all options are subcommands -type HasOnlySubcommands = - Options[number] extends { type: ApplicationCommandOptionTypes.SubCommand } - ? true - : false; +type HasOnlySubcommands = Options[number] extends { type: ApplicationCommandOptionTypes.SubCommand } ? true : false // Extract subcommand names from options -type SubcommandNames = - Options[number] extends { name: infer N; type: ApplicationCommandOptionTypes.SubCommand } - ? N extends string - ? N - : never - : never; +type SubcommandNames = Options[number] extends { name: infer N; type: ApplicationCommandOptionTypes.SubCommand } + ? N extends string + ? N + : never + : never // Type to extract subcommand handlers export type SubcommandHandlers = { @@ -68,14 +58,10 @@ export type SubcommandHandlers = { ? HandlerFunction> : HandlerFunction<{}> : HandlerFunction<{}> -}; +} // Get command by name from array -type GetCommand = Commands[number] extends infer C - ? C extends { name: Name } - ? C - : never - : never; +type GetCommand = Commands[number] extends infer C ? (C extends { name: Name } ? C : never) : never // Main type to extract command handlers from slash commands export type ExtractCommands = { @@ -86,76 +72,75 @@ export type ExtractCommands = { : HandlerFunction> : HandlerFunction<{}> : HandlerFunction<{}> -}; +} // The actual command handler type based on SLASH_COMMANDS -export type CommandHandlers = ExtractCommands; +export type CommandHandlers = ExtractCommands // Helper function to parse option values from interaction data function parseOptions(options?: DiscordInteractionDataOption[]): Record { - if (!options) return {}; + if (!options) return {} - const args: Record = {}; + const args: Record = {} for (const option of options) { - if (option.type === ApplicationCommandOptionTypes.SubCommand || - option.type === ApplicationCommandOptionTypes.SubCommandGroup) { - continue; + if (option.type === ApplicationCommandOptionTypes.SubCommand || option.type === ApplicationCommandOptionTypes.SubCommandGroup) { + continue } - args[option.name] = option.value; + args[option.name] = option.value } - return args; + return args } // Helper function to create command handlers with type safety -export function createCommandHandler( - {handler, notFoundHandler}:{ +export function createCommandHandler({ + handler, + notFoundHandler, +}: { handler: ExtractCommands notFoundHandler: HandlerFunction<{}> }) { return async (data: InteractionData): Promise => { if (!data || !data.name) { - await notFoundHandler({}); - return; + await notFoundHandler({}) + return } - const commandName = data.name as keyof typeof handler; - const commandHandler = handler[commandName]; + const commandName = data.name as keyof typeof handler + const commandHandler = handler[commandName] if (!commandHandler) { - await notFoundHandler({}); - return; + await notFoundHandler({}) + return } // Check if it's a direct command or has subcommands if (typeof commandHandler === 'function') { // Parse arguments from top-level options - const args = parseOptions(data.options); - await commandHandler(args); + const args = parseOptions(data.options) + await commandHandler(args) } else { // Handle subcommands const subcommand = data.options?.find( - opt => opt.type === ApplicationCommandOptionTypes.SubCommand || - opt.type === ApplicationCommandOptionTypes.SubCommandGroup - ); + (opt) => opt.type === ApplicationCommandOptionTypes.SubCommand || opt.type === ApplicationCommandOptionTypes.SubCommandGroup + ) if (!subcommand) { - await notFoundHandler({}); - return; + await notFoundHandler({}) + return } - const subHandler = commandHandler[subcommand.name as keyof typeof commandHandler]; + const subHandler = commandHandler[subcommand.name as keyof typeof commandHandler] if (!subHandler || typeof subHandler !== 'function') { - await notFoundHandler({}); - return; + await notFoundHandler({}) + return } // Parse arguments from subcommand options - const args = parseOptions(subcommand.options); - await (subHandler as HandlerFunction)(args); + const args = parseOptions(subcommand.options) + await (subHandler as HandlerFunction)(args) } } } - diff --git a/ts/src/discord/botevent/handler.ts b/ts/src/discord/botevent/handler.ts index 200cf42..0629652 100644 --- a/ts/src/discord/botevent/handler.ts +++ b/ts/src/discord/botevent/handler.ts @@ -1,54 +1,58 @@ -import { Bot } from "#/discord/bot" -import { ActivityTypes, InteractionTypes } from "discordeno" -import { c } from "#/di" -import { Client } from "@temporalio/client" -import { workflowHandleInteractionCreate } from "#/workflows" -import { BotType } from "#/discord" +import { Client } from '@temporalio/client' +import { ActivityTypes, InteractionTypes } from 'discordeno' +import { c } from '#/di' +import type { BotType } from '#/discord' +import { Bot } from '#/discord/bot' +import { workflowHandleInteractionCreate } from '#/workflows' -export const events = () => {return { - interactionCreate: async (interaction) => { - if(interaction.acknowledged) { - return - } - if(interaction.type !== InteractionTypes.ApplicationCommand) { - return - } +export const events = () => { + return { + interactionCreate: async (interaction) => { + if (interaction.acknowledged) { + return + } + if (interaction.type !== InteractionTypes.ApplicationCommand) { + return + } - const temporalClient = await c.getAsync(Client); + const temporalClient = await c.getAsync(Client) - // Start the workflow to handle the interaction - const handle = await temporalClient.workflow.start(workflowHandleInteractionCreate, { - args: [{ - ref: { - id: interaction.id, - token: interaction.token, - type: interaction.type, - acknowledged: interaction.acknowledged, - }, - data: interaction.data, - }], - workflowId: `discord-interaction-${interaction.id}`, - taskQueue: 'wynn-worker-ts', - }); - - // Wait for the workflow to complete - await handle.result(); - - return - }, - ready: async ({shardId}) => { - const bot = await c.getAsync(Bot) - await bot.gateway.editShardStatus(shardId, { - status: 'online', - activities: [ - { - name: 'im frog', - type: ActivityTypes.Playing, - timestamps: { - start: Date.now(), + // Start the workflow to handle the interaction + const handle = await temporalClient.workflow.start(workflowHandleInteractionCreate, { + args: [ + { + ref: { + id: interaction.id, + token: interaction.token, + type: interaction.type, + acknowledged: interaction.acknowledged, + }, + data: interaction.data, }, - }, - ], - }) - } -} as BotType['events']} + ], + workflowId: `discord-interaction-${interaction.id}`, + taskQueue: 'wynn-worker-ts', + }) + + // Wait for the workflow to complete + await handle.result() + + return + }, + ready: async ({ shardId }) => { + const bot = await c.getAsync(Bot) + await bot.gateway.editShardStatus(shardId, { + status: 'online', + activities: [ + { + name: 'im frog', + type: ActivityTypes.Playing, + timestamps: { + start: Date.now(), + }, + }, + ], + }) + }, + } as BotType['events'] +} diff --git a/ts/src/discord/botevent/slash_commands.ts b/ts/src/discord/botevent/slash_commands.ts index c7b8068..96a3742 100644 --- a/ts/src/discord/botevent/slash_commands.ts +++ b/ts/src/discord/botevent/slash_commands.ts @@ -1,60 +1,59 @@ -import { ApplicationCommandOptionTypes, ApplicationCommandTypes, CreateApplicationCommand } from "@discordeno/types" +import { ApplicationCommandOptionTypes, ApplicationCommandTypes, type CreateApplicationCommand } from '@discordeno/types' export const SLASH_COMMANDS = [ { name: `guild`, - description: "guild commands", + description: 'guild commands', type: ApplicationCommandTypes.ChatInput, options: [ { - name: "leaderboard", - description: "view the current leaderboard", + name: 'leaderboard', + description: 'view the current leaderboard', type: ApplicationCommandOptionTypes.SubCommand, }, { - name: "info", - description: "view guild information", + name: 'info', + description: 'view guild information', type: ApplicationCommandOptionTypes.SubCommand, }, { - name: "online", - description: "show online players", + name: 'online', + description: 'show online players', type: ApplicationCommandOptionTypes.SubCommand, }, ], }, { - name: "admin", - description: "admin commands", + name: 'admin', + description: 'admin commands', type: ApplicationCommandTypes.ChatInput, - defaultMemberPermissions: [ - "ADMINISTRATOR", - ], + defaultMemberPermissions: ['ADMINISTRATOR'], options: [ { - name: "set_wynn_guild", - description: "set the default wynncraft guild for the server", + name: 'set_wynn_guild', + description: 'set the default wynncraft guild for the server', type: ApplicationCommandOptionTypes.SubCommand, }, ], - },{ - name: "player", - description: "player commands", + }, + { + name: 'player', + description: 'player commands', type: ApplicationCommandTypes.ChatInput, options: [ { - name: "lookup", - description: "view player information", + name: 'lookup', + description: 'view player information', type: ApplicationCommandOptionTypes.SubCommand, options: [ { - name: "player", - description: "player name", + name: 'player', + description: 'player name', type: ApplicationCommandOptionTypes.String, required: true, }, ], - } + }, ], - } + }, ] as const satisfies CreateApplicationCommand[] diff --git a/ts/src/discord/botevent/types.ts b/ts/src/discord/botevent/types.ts index 572b9c9..66714e7 100644 --- a/ts/src/discord/botevent/types.ts +++ b/ts/src/discord/botevent/types.ts @@ -1,5 +1,4 @@ -import {BotType} from "#/discord" - +import type { BotType } from '#/discord' export type BotEventsType = BotType['events'] export type InteractionHandler = NonNullable diff --git a/ts/src/discord/index.ts b/ts/src/discord/index.ts index e84a324..58c3d2d 100644 --- a/ts/src/discord/index.ts +++ b/ts/src/discord/index.ts @@ -1,67 +1,66 @@ -import { Intents, InteractionTypes } from "@discordeno/types"; -import type { Bot, DesiredPropertiesBehavior, CompleteDesiredProperties } from "discordeno"; +import { Intents, type InteractionTypes } from '@discordeno/types' +import type { Bot, CompleteDesiredProperties, DesiredPropertiesBehavior } from 'discordeno' export const intents = [ - Intents.GuildModeration , - Intents.GuildWebhooks , - Intents.GuildExpressions , - Intents.GuildScheduledEvents , - Intents.GuildMessagePolls , - Intents.GuildIntegrations , - Intents.GuildInvites , - Intents.GuildMessageReactions , - Intents.GuildPresences , - Intents.DirectMessages , - Intents.DirectMessageReactions , - Intents.GuildMembers , - Intents.Guilds , - Intents.GuildInvites , - Intents.GuildMessages, + Intents.GuildModeration, + Intents.GuildWebhooks, + Intents.GuildExpressions, + Intents.GuildScheduledEvents, + Intents.GuildMessagePolls, + Intents.GuildIntegrations, + Intents.GuildInvites, + Intents.GuildMessageReactions, + Intents.GuildPresences, + Intents.DirectMessages, + Intents.DirectMessageReactions, + Intents.GuildMembers, + Intents.Guilds, + Intents.GuildInvites, + Intents.GuildMessages, ] as const export const createBotParameters = { - intents: intents.reduce((acc, curr) => acc | curr, Intents.Guilds), - desiredProperties: { - interaction: { - id: true, - data: true, - type: true, - token: true, - message: true, - channelId: true, - channel: true, - guildId: true, - guild: true, - user: true, - member: true, - }, - message: { - id: true, - member: true, - guildId: true, - }, - } + intents: intents.reduce((acc, curr) => acc | curr, Intents.Guilds), + desiredProperties: { + interaction: { + id: true, + data: true, + type: true, + token: true, + message: true, + channelId: true, + channel: true, + guildId: true, + guild: true, + user: true, + member: true, + }, + message: { + id: true, + member: true, + guildId: true, + }, + }, } as const // Extract the type of desired properties from our parameters -type ExtractedDesiredProperties = typeof createBotParameters.desiredProperties; +type ExtractedDesiredProperties = typeof createBotParameters.desiredProperties // The BotType uses the CompleteDesiredProperties helper to fill in the missing properties -export type BotType = Bot, DesiredPropertiesBehavior.RemoveKey>; - +export type BotType = Bot, DesiredPropertiesBehavior.RemoveKey> // Type for the interaction reference passed to workflows/activities export interface InteractionRef { - id: bigint; - token: string; - type: InteractionTypes; - acknowledged?: boolean; + id: bigint + token: string + type: InteractionTypes + acknowledged?: boolean } // Type for the interaction data payload -export type InteractionData = Parameters>[0]['data']; +export type InteractionData = Parameters>[0]['data'] // Type for the complete interaction handling payload export interface InteractionCreatePayload { - ref: InteractionRef; - data: InteractionData; + ref: InteractionRef + data: InteractionData } diff --git a/ts/src/discord/mux/index.ts b/ts/src/discord/mux/index.ts index 4a980a0..ba68d60 100644 --- a/ts/src/discord/mux/index.ts +++ b/ts/src/discord/mux/index.ts @@ -1,9 +1,5 @@ -import { c } from "#/di"; - - +import { c } from '#/di' export class EventMux { - constructor() { - } + constructor() {} } - diff --git a/ts/src/lib/util/hashers.ts b/ts/src/lib/util/hashers.ts index f7ff748..2be1e44 100644 --- a/ts/src/lib/util/hashers.ts +++ b/ts/src/lib/util/hashers.ts @@ -1,14 +1,13 @@ -import crypto from "node:crypto"; -import {IDataType, xxhash128} from "hash-wasm"; +import crypto from 'node:crypto' +import { type IDataType, xxhash128 } from 'hash-wasm' export function sha1Hash(data: crypto.BinaryLike) { - const hash = crypto.createHash('sha1'); - hash.update(data); - return hash.digest('hex'); + const hash = crypto.createHash('sha1') + hash.update(data) + return hash.digest('hex') } - -export async function fastHashFileV1(data: IDataType):Promise { +export async function fastHashFileV1(data: IDataType): Promise { const hash = xxhash128(data) return hash } diff --git a/ts/src/lib/util/tabwriter.ts b/ts/src/lib/util/tabwriter.ts index 597aa37..86b97db 100644 --- a/ts/src/lib/util/tabwriter.ts +++ b/ts/src/lib/util/tabwriter.ts @@ -1,39 +1,34 @@ - - export class TabWriter { + columns: string[][] - columns: string[][]; - - constructor( - private readonly spacing: number = 2 - ) { + constructor(private readonly spacing: number = 2) { this.columns = [] } add(row: string[]) { - if(this.columns.length == 0) { - this.columns = new Array(row.length).fill(0).map(() => []); + if (this.columns.length == 0) { + this.columns = new Array(row.length).fill(0).map(() => []) } - if(row.length != this.columns.length) { - throw new Error(`Row length ${row.length} does not match columns length ${this.columns.length}`); + if (row.length != this.columns.length) { + throw new Error(`Row length ${row.length} does not match columns length ${this.columns.length}`) } - for(let i = 0; i < row.length; i++) { - this.columns[i].push(row[i]); + for (let i = 0; i < row.length; i++) { + this.columns[i].push(row[i]) } } build() { - let out = "" - if(this.columns.length == 0) { - return ""; + let out = '' + if (this.columns.length == 0) { + return '' } - const columnWidths = this.columns.map(col => col.reduce((a, b) => Math.max(a, b.length+this.spacing), 0)); - for(let i = 0; i < this.columns[0].length; i++) { - if (i > 0) out += "\n"; - for(let j = 0; j < this.columns.length; j++) { - out+= this.columns[j][i].padEnd(columnWidths[j]); + const columnWidths = this.columns.map((col) => col.reduce((a, b) => Math.max(a, b.length + this.spacing), 0)) + for (let i = 0; i < this.columns[0].length; i++) { + if (i > 0) out += '\n' + for (let j = 0; j < this.columns.length; j++) { + out += this.columns[j][i].padEnd(columnWidths[j]) } } - return out; + return out } } diff --git a/ts/src/lib/util/wynnfmt.ts b/ts/src/lib/util/wynnfmt.ts index ea769fc..3f037b6 100644 --- a/ts/src/lib/util/wynnfmt.ts +++ b/ts/src/lib/util/wynnfmt.ts @@ -6,13 +6,13 @@ * Mapping of Wynncraft guild ranks to their corresponding emojis */ export const RANK_EMOJIS = { - "OWNER": "👑", - "CHIEF": "⭐", - "STRATEGIST": "🎯", - "CAPTAIN": "⚔️", - "RECRUITER": "📢", - "RECRUIT": "🌱", -} as const; + OWNER: '👑', + CHIEF: '⭐', + STRATEGIST: '🎯', + CAPTAIN: '⚔️', + RECRUITER: '📢', + RECRUIT: '🌱', +} as const /** * Get the emoji for a given guild rank @@ -20,7 +20,7 @@ export const RANK_EMOJIS = { * @returns The corresponding emoji or a default bullet point */ export function getRankEmoji(rank: string): string { - return RANK_EMOJIS[rank as keyof typeof RANK_EMOJIS] || "•"; + return RANK_EMOJIS[rank as keyof typeof RANK_EMOJIS] || '•' } /** @@ -29,7 +29,7 @@ export function getRankEmoji(rank: string): string { * @returns Formatted string with appropriate suffix */ export function formatNumber(num: number): string { - if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1)}M`; - if (num >= 1_000) return `${(num / 1_000).toFixed(0)}K`; - return num.toLocaleString(); -} \ No newline at end of file + if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1)}M` + if (num >= 1_000) return `${(num / 1_000).toFixed(0)}K` + return num.toLocaleString() +} diff --git a/ts/src/lib/wynn/types.ts b/ts/src/lib/wynn/types.ts index 9746288..7ac35a3 100644 --- a/ts/src/lib/wynn/types.ts +++ b/ts/src/lib/wynn/types.ts @@ -1,29 +1,29 @@ -import { type } from "arktype" +import { type } from 'arktype' export const WynnGuildOverviewMember = type({ - uuid: "string", - online: "boolean", - server: "null | string", - contributed: "number", - contributionRank: "number", - joined: "string" + uuid: 'string', + online: 'boolean', + server: 'null | string', + contributed: 'number', + contributionRank: 'number', + joined: 'string', }) const WapiV3GuildMembers = type({ - "[string]": WynnGuildOverviewMember + '[string]': WynnGuildOverviewMember, }) export const WapiV3GuildOverview = type({ - uuid: "string", - name: "string", - prefix: "string", - level: "number", - xpPercent: "number", - territories: "number", - wars: "number", - created: "string", + uuid: 'string', + name: 'string', + prefix: 'string', + level: 'number', + xpPercent: 'number', + territories: 'number', + wars: 'number', + created: 'string', members: { - total: "number", + total: 'number', owner: WapiV3GuildMembers, chief: WapiV3GuildMembers, strategist: WapiV3GuildMembers, @@ -31,242 +31,233 @@ export const WapiV3GuildOverview = type({ recruiter: WapiV3GuildMembers, recruit: WapiV3GuildMembers, }, - online: "number", + online: 'number', banner: { - base: "string", - tier: "number", - structure: "string", - layers: type({ colour: "string", pattern: "string" }).array(), + base: 'string', + tier: 'number', + structure: 'string', + layers: type({ colour: 'string', pattern: 'string' }).array(), }, seasonRanks: { - "[string]": { - rating: "number", - finalTerritories: "number" - } - } + '[string]': { + rating: 'number', + finalTerritories: 'number', + }, + }, }) -const WynnItemRarity = type.enumerated("common", "fabled", "legendary", "mythic", "rare", "set", "unique") +const WynnItemRarity = type.enumerated('common', 'fabled', 'legendary', 'mythic', 'rare', 'set', 'unique') -const WynnSkills = type.enumerated( - "alchemism", - "armouring", - "cooking", - "jeweling", - "scribing", - "tailoring", - "weaponsmithing", - "woodworking", -) +const WynnSkills = type.enumerated('alchemism', 'armouring', 'cooking', 'jeweling', 'scribing', 'tailoring', 'weaponsmithing', 'woodworking') -const WynnDropMeta = type("object") +const WynnDropMeta = type('object') -const WynnDropRestriction = type.enumerated("normal", "never", "dungeon", "lootchest") +const WynnDropRestriction = type.enumerated('normal', 'never', 'dungeon', 'lootchest') -const WynnItemRestrictions = type.enumerated("untradable", "quest item") +const WynnItemRestrictions = type.enumerated('untradable', 'quest item') const WynnEquipRequirements = type({ - level: "number", - "classRequirement?": "string", - "intelligence?": "number", - "strength?": "number", - "dexterity?": "number", - "defence?": "number", - "agility?": "number", - "skills?": WynnSkills.array(), + level: 'number', + 'classRequirement?': 'string', + 'intelligence?': 'number', + 'strength?': 'number', + 'dexterity?': 'number', + 'defence?': 'number', + 'agility?': 'number', + 'skills?': WynnSkills.array(), }) const WynnBaseStats = type({ - "[string]": type("number").or({ - min: "number", - raw: "number", - max: "number", - }) + '[string]': type('number').or({ + min: 'number', + raw: 'number', + max: 'number', + }), }) - const WynnIdentifications = type({ - "[string]": type("number").or({ - min: "number", - raw: "number", - max: "number", - }) + '[string]': type('number').or({ + min: 'number', + raw: 'number', + max: 'number', + }), }) const WynnItemIcon = type({ - format: "string", - value: "unknown" + format: 'string', + value: 'unknown', }) export const WapiV3ItemTool = type({ - internalName: "string", + internalName: 'string', type: '"tool"', - toolType: type.enumerated("axe", "pickaxe", "rod", "scythe"), - "identified?": "boolean", - gatheringSpeed: "number", + toolType: type.enumerated('axe', 'pickaxe', 'rod', 'scythe'), + 'identified?': 'boolean', + gatheringSpeed: 'number', requirements: { - level: "number", + level: 'number', }, icon: WynnItemIcon, - rarity: WynnItemRarity + rarity: WynnItemRarity, }) export const WapiV3ItemTome = type({ - internalName: "string", - tomeType: type.enumerated( - "guild_tome", - "marathon_tome", - "mysticism_tome", - "weapon_tome", - "armour_tome", - "expertise_tome", - "lootrun_tome", - ), - raidReward: "boolean", + internalName: 'string', + tomeType: type.enumerated('guild_tome', 'marathon_tome', 'mysticism_tome', 'weapon_tome', 'armour_tome', 'expertise_tome', 'lootrun_tome'), + raidReward: 'boolean', type: '"tome"', - "restrictions?": WynnItemRestrictions, - "dropMeta?": WynnDropMeta, + 'restrictions?': WynnItemRestrictions, + 'dropMeta?': WynnDropMeta, dropRestriction: WynnDropRestriction, requirements: WynnEquipRequirements, - "lore?": "string", + 'lore?': 'string', icon: WynnItemIcon, - "base?": WynnBaseStats, - rarity: WynnItemRarity + 'base?': WynnBaseStats, + rarity: WynnItemRarity, }) export const WapiV3ItemCharm = type({ - internalName: "string", + internalName: 'string', type: '"charm"', - "restrictions?": WynnItemRestrictions, - "dropMeta?": WynnDropMeta, + 'restrictions?': WynnItemRestrictions, + 'dropMeta?': WynnDropMeta, dropRestriction: WynnDropRestriction, requirements: WynnEquipRequirements, - "lore?": "string", + 'lore?': 'string', icon: WynnItemIcon, base: WynnBaseStats, - rarity: WynnItemRarity + rarity: WynnItemRarity, }) export const WapiV3ItemAccessory = type({ - internalName: "string", + internalName: 'string', type: '"accessory"', - "identified?": "boolean", - accessoryType: type.enumerated("ring", "necklace", "bracelet"), - "majorIds?": { - "[string]": "string" + 'identified?': 'boolean', + accessoryType: type.enumerated('ring', 'necklace', 'bracelet'), + 'majorIds?': { + '[string]': 'string', }, - "restrictions?": WynnItemRestrictions, - "dropMeta?": WynnDropMeta, + 'restrictions?': WynnItemRestrictions, + 'dropMeta?': WynnDropMeta, dropRestriction: WynnDropRestriction, requirements: WynnEquipRequirements, - "lore?": "string", + 'lore?': 'string', icon: WynnItemIcon, - "identifications?": WynnIdentifications, - "base?": WynnBaseStats, - rarity: WynnItemRarity + 'identifications?': WynnIdentifications, + 'base?': WynnBaseStats, + rarity: WynnItemRarity, }) export const WapiV3ItemIngredient = type({ - internalName: "string", + internalName: 'string', type: '"ingredient"', requirements: { - level: "number", + level: 'number', skills: WynnSkills.array(), }, icon: WynnItemIcon, - "identifications?": WynnIdentifications, - tier: "number", + 'identifications?': WynnIdentifications, + tier: 'number', consumableOnlyIDs: { - "[string]": "number" + '[string]': 'number', }, ingredientPositionModifiers: { - "[string]": "number" + '[string]': 'number', }, itemOnlyIDs: { - "[string]": "number" + '[string]': 'number', }, - "droppedBy?": type({ - name: "string", - coords: type("boolean | null") - .or(type("number[] == 4")) - .or(type("number[] == 4").array()) - }).array() + 'droppedBy?': type({ + name: 'string', + coords: type('boolean | null').or(type('number[] == 4')).or(type('number[] == 4').array()), + }).array(), }) export const WapiV3ItemMaterial = type({ - internalName: "string", + internalName: 'string', type: '"material"', - identified: "boolean", + identified: 'boolean', requirements: { - level: "number", + level: 'number', }, - craftable: type.enumerated( - "potions", "food", "scrolls", - "helmets", "chestplates", "rings", "bracelets", - "necklaces", "boots", "leggings", "bows", "wands", "spears", - "daggers", "chestplates", "helmets" - ).array(), + craftable: type + .enumerated( + 'potions', + 'food', + 'scrolls', + 'helmets', + 'chestplates', + 'rings', + 'bracelets', + 'necklaces', + 'boots', + 'leggings', + 'bows', + 'wands', + 'spears', + 'daggers', + 'chestplates', + 'helmets' + ) + .array(), icon: WynnItemIcon, - tier: "number" + tier: 'number', }) export const WapiV3ItemWeapon = type({ - internalName: "string", + internalName: 'string', type: '"weapon"', - "identified?": "boolean", - "allowCraftsman?": "boolean", - weaponType: type.enumerated("bow", "relik", "wand", "dagger", "spear"), - attackSpeed: type.enumerated( - "super_slow", "very_slow", "slow", "normal", "fast", "very_fast", "super_fast" - ), - "powderSlots?": "number", - "averageDps?": "number", - "restrictions?": WynnItemRestrictions, - "dropMeta?": WynnDropMeta, - "dropRestriction?": WynnDropRestriction, + 'identified?': 'boolean', + 'allowCraftsman?': 'boolean', + weaponType: type.enumerated('bow', 'relik', 'wand', 'dagger', 'spear'), + attackSpeed: type.enumerated('super_slow', 'very_slow', 'slow', 'normal', 'fast', 'very_fast', 'super_fast'), + 'powderSlots?': 'number', + 'averageDps?': 'number', + 'restrictions?': WynnItemRestrictions, + 'dropMeta?': WynnDropMeta, + 'dropRestriction?': WynnDropRestriction, requirements: WynnEquipRequirements, - "majorIds?": { - "[string]": "string" + 'majorIds?': { + '[string]': 'string', }, - "lore?": "string", + 'lore?': 'string', icon: WynnItemIcon, - "identifications?": WynnIdentifications, - "base?": WynnBaseStats, - rarity: WynnItemRarity + 'identifications?': WynnIdentifications, + 'base?': WynnBaseStats, + rarity: WynnItemRarity, }) export const WapiV3ItemArmour = type({ - internalName: "string", + internalName: 'string', type: '"armour"', - armourType: "string", - "armourMaterial?": "string", - "armourColor?": "string", - "identified?": "boolean", - "allowCraftsman?": "boolean", - "restrictions?": WynnItemRestrictions, + armourType: 'string', + 'armourMaterial?': 'string', + 'armourColor?': 'string', + 'identified?': 'boolean', + 'allowCraftsman?': 'boolean', + 'restrictions?': WynnItemRestrictions, dropRestriction: WynnDropRestriction, - "dropMeta?": WynnDropMeta, - "icon?": WynnItemIcon, + 'dropMeta?': WynnDropMeta, + 'icon?': WynnItemIcon, requirements: WynnEquipRequirements, - "majorIds?": { - "[string]": "string" + 'majorIds?': { + '[string]': 'string', }, - "powderSlots?": "number", - "lore?": "string", - "identifications?": WynnIdentifications, - "base?": WynnBaseStats, - rarity: WynnItemRarity + 'powderSlots?': 'number', + 'lore?': 'string', + 'identifications?': WynnIdentifications, + 'base?': WynnBaseStats, + rarity: WynnItemRarity, }) -export const WApiV3Item = WapiV3ItemMaterial -.or(WapiV3ItemWeapon) -.or(WapiV3ItemArmour) -.or(WapiV3ItemIngredient) -.or(WapiV3ItemAccessory) -.or(WapiV3ItemCharm) -.or(WapiV3ItemTome) -.or(WapiV3ItemTool) +export const WApiV3Item = WapiV3ItemMaterial.or(WapiV3ItemWeapon) + .or(WapiV3ItemArmour) + .or(WapiV3ItemIngredient) + .or(WapiV3ItemAccessory) + .or(WapiV3ItemCharm) + .or(WapiV3ItemTome) + .or(WapiV3ItemTool) export const WApiV3ItemDatabase = type({ - "[string]": WApiV3Item + '[string]': WApiV3Item, }) diff --git a/ts/src/lib/wynn/types.zod.ts b/ts/src/lib/wynn/types.zod.ts index 6f8fbdd..07082c5 100644 --- a/ts/src/lib/wynn/types.zod.ts +++ b/ts/src/lib/wynn/types.zod.ts @@ -6,10 +6,9 @@ export const WynnGuildOverviewMember = z.object({ server: z.null(), contributed: z.number(), contributionRank: z.number(), - joined: z.string() + joined: z.string(), }) - const WapiV3GuildMembers = z.record(z.string(), WynnGuildOverviewMember) export const WapiV3GuildOverview = z.object({ @@ -35,22 +34,16 @@ export const WapiV3GuildOverview = z.object({ base: z.string(), tier: z.number(), structure: z.string(), - layers: z.array(z.object({ colour: z.string(), pattern: z.string() })) + layers: z.array(z.object({ colour: z.string(), pattern: z.string() })), }), - seasonRanks: z.record(z.string(),z.object({ rating: z.number(), finalTerritories: z.number() })) + seasonRanks: z.record(z.string(), z.object({ rating: z.number(), finalTerritories: z.number() })), }) - - -const WynnItemRarity = z.enum([ - "common","fabled","legendary","mythic","rare","set","unique", -]) +const WynnItemRarity = z.enum(['common', 'fabled', 'legendary', 'mythic', 'rare', 'set', 'unique']) const WynnDropMeta = z.any() -const WynnDropRestriction = z.enum(["normal","never","dungeon", "lootchest"]) +const WynnDropRestriction = z.enum(['normal', 'never', 'dungeon', 'lootchest']) -const WynnItemRestrictions = z.enum([ - "untradable", "quest item", -]) +const WynnItemRestrictions = z.enum(['untradable', 'quest item']) const WynnEquipRequirements = z.object({ level: z.number(), @@ -62,23 +55,29 @@ const WynnEquipRequirements = z.object({ agility: z.number().optional(), }) -const WynnBaseStats = z.record(z.string(),z.union([ - z.number(), - z.object({ - min: z.number(), - raw: z.number(), - max: z.number(), - }) -])) +const WynnBaseStats = z.record( + z.string(), + z.union([ + z.number(), + z.object({ + min: z.number(), + raw: z.number(), + max: z.number(), + }), + ]) +) -const WynnIdentifications = z.record(z.string(), z.union([ - z.number(), - z.object({ - min: z.number(), - raw: z.number(), - max: z.number(), - }) -])) +const WynnIdentifications = z.record( + z.string(), + z.union([ + z.number(), + z.object({ + min: z.number(), + raw: z.number(), + max: z.number(), + }), + ]) +) const WynnItemIcon = z.object({ format: z.string(), @@ -109,29 +108,19 @@ const WynnItemIcon = z.object({ export const WapiV3ItemTool = z.object({ internalName: z.string(), type: z.literal('tool'), - toolType: z.enum([ - "axe","pickaxe","rod","scythe", - ]), + toolType: z.enum(['axe', 'pickaxe', 'rod', 'scythe']), identified: z.boolean().optional(), gatheringSpeed: z.number(), requirements: z.object({ level: z.number(), }), icon: WynnItemIcon, - rarity:WynnItemRarity, + rarity: WynnItemRarity, }) export const WapiV3ItemTome = z.object({ internalName: z.string(), - tomeType: z.enum([ - "guild_tome", - "marathon_tome", - "mysticism_tome", - "weapon_tome", - "armour_tome", - "expertise_tome", - "lootrun_tome", - ]), + tomeType: z.enum(['guild_tome', 'marathon_tome', 'mysticism_tome', 'weapon_tome', 'armour_tome', 'expertise_tome', 'lootrun_tome']), raidReward: z.boolean(), type: z.literal('tome'), restrictions: WynnItemRestrictions.optional(), @@ -141,7 +130,7 @@ export const WapiV3ItemTome = z.object({ lore: z.string().optional(), icon: WynnItemIcon, base: WynnBaseStats.optional(), - rarity:WynnItemRarity, + rarity: WynnItemRarity, }) export const WapiV3ItemCharm = z.object({ @@ -154,139 +143,153 @@ export const WapiV3ItemCharm = z.object({ lore: z.string().optional(), icon: WynnItemIcon, base: WynnBaseStats, - rarity:WynnItemRarity, + rarity: WynnItemRarity, }) -export const WapiV3ItemAccessory = z.object({ - internalName: z.string(), - type: z.literal('accessory'), - identified: z.boolean().optional(), - accessoryType: z.enum([ - "ring","necklace","bracelet", - ]), - majorIds: z.record(z.string(), z.string()).optional(), - restrictions: WynnItemRestrictions.optional(), - dropMeta: WynnDropMeta.optional(), - dropRestriction: WynnDropRestriction, - requirements:WynnEquipRequirements, - lore: z.string().optional(), - icon: WynnItemIcon, - identifications: WynnIdentifications.optional(), - base: WynnBaseStats.optional(), - rarity:WynnItemRarity, -}).strict() +export const WapiV3ItemAccessory = z + .object({ + internalName: z.string(), + type: z.literal('accessory'), + identified: z.boolean().optional(), + accessoryType: z.enum(['ring', 'necklace', 'bracelet']), + majorIds: z.record(z.string(), z.string()).optional(), + restrictions: WynnItemRestrictions.optional(), + dropMeta: WynnDropMeta.optional(), + dropRestriction: WynnDropRestriction, + requirements: WynnEquipRequirements, + lore: z.string().optional(), + icon: WynnItemIcon, + identifications: WynnIdentifications.optional(), + base: WynnBaseStats.optional(), + rarity: WynnItemRarity, + }) + .strict() -export const WapiV3ItemIngredient = z.object({ - internalName: z.string(), - type: z.literal('ingredient'), - requirements: z.object({ - level: z.number(), - skills: z.array(z.enum([ - "alchemism", - "armouring", - "cooking", - "jeweling", - "scribing", - "tailoring", - "weaponsmithing", - "woodworking", - ])), - }), - icon: WynnItemIcon, - identifications: WynnIdentifications.optional(), - tier: z.number(), - consumableOnlyIDs: z.record(z.string(), z.number()), - ingredientPositionModifiers: z.record(z.string(), z.number()), - itemOnlyIDs: z.record(z.string(), z.number()), - droppedBy: z.array(z.object({ - name: z.string(), - coords: z.union([ - z.boolean(), - z.array(z.number()).length(4), - z.array(z.array(z.number()).length(4)), - ]).nullable() - })).optional() -}).strict() +export const WapiV3ItemIngredient = z + .object({ + internalName: z.string(), + type: z.literal('ingredient'), + requirements: z.object({ + level: z.number(), + skills: z.array(z.enum(['alchemism', 'armouring', 'cooking', 'jeweling', 'scribing', 'tailoring', 'weaponsmithing', 'woodworking'])), + }), + icon: WynnItemIcon, + identifications: WynnIdentifications.optional(), + tier: z.number(), + consumableOnlyIDs: z.record(z.string(), z.number()), + ingredientPositionModifiers: z.record(z.string(), z.number()), + itemOnlyIDs: z.record(z.string(), z.number()), + droppedBy: z + .array( + z.object({ + name: z.string(), + coords: z.union([z.boolean(), z.array(z.number()).length(4), z.array(z.array(z.number()).length(4))]).nullable(), + }) + ) + .optional(), + }) + .strict() -export const WapiV3ItemMaterial = z.object({ - internalName: z.string(), - type: z.literal('material'), - identified: z.boolean(), - requirements: z.object({ - level: z.number(), - }), - craftable: z.array(z.enum([ - 'potions','food','scrolls', - 'helmets','chestplates','rings','bracelets', - 'necklaces','boots','leggings','bows','wands','spears', - 'daggers','chestplates','helmets'])), - icon: WynnItemIcon, - tier: z.number(), -}).strict() +export const WapiV3ItemMaterial = z + .object({ + internalName: z.string(), + type: z.literal('material'), + identified: z.boolean(), + requirements: z.object({ + level: z.number(), + }), + craftable: z.array( + z.enum([ + 'potions', + 'food', + 'scrolls', + 'helmets', + 'chestplates', + 'rings', + 'bracelets', + 'necklaces', + 'boots', + 'leggings', + 'bows', + 'wands', + 'spears', + 'daggers', + 'chestplates', + 'helmets', + ]) + ), + icon: WynnItemIcon, + tier: z.number(), + }) + .strict() -export const WapiV3ItemWeapon = z.object({ - internalName: z.string(), - type: z.literal('weapon'), - identified: z.boolean().optional(), - allowCraftsman: z.boolean().optional(), - weaponType: z.enum([ - "bow","relik","wand","dagger","spear" - ]), - attackSpeed: z.enum([ - "super_slow", "very_slow", "slow","normal","fast", "very_fast","super_fast" - ]), - powderSlots: z.number().optional(), - averageDps: z.number().optional(), - restrictions: WynnItemRestrictions.optional(), - dropMeta: WynnDropMeta.optional(), - dropRestriction: WynnDropRestriction.optional(), - requirements: WynnEquipRequirements, - majorIds: z.record(z.string(), z.string()).optional(), - lore: z.string().optional(), - icon: WynnItemIcon, - identifications: WynnIdentifications.optional(), - base: WynnBaseStats.optional(), - rarity:WynnItemRarity, -}).strict() +export const WapiV3ItemWeapon = z + .object({ + internalName: z.string(), + type: z.literal('weapon'), + identified: z.boolean().optional(), + allowCraftsman: z.boolean().optional(), + weaponType: z.enum(['bow', 'relik', 'wand', 'dagger', 'spear']), + attackSpeed: z.enum(['super_slow', 'very_slow', 'slow', 'normal', 'fast', 'very_fast', 'super_fast']), + powderSlots: z.number().optional(), + averageDps: z.number().optional(), + restrictions: WynnItemRestrictions.optional(), + dropMeta: WynnDropMeta.optional(), + dropRestriction: WynnDropRestriction.optional(), + requirements: WynnEquipRequirements, + majorIds: z.record(z.string(), z.string()).optional(), + lore: z.string().optional(), + icon: WynnItemIcon, + identifications: WynnIdentifications.optional(), + base: WynnBaseStats.optional(), + rarity: WynnItemRarity, + }) + .strict() +export const WapiV3ItemArmour = z + .object({ + internalName: z.string(), + type: z.literal('armour'), + armourType: z.string(), + armourMaterial: z.string().optional(), + armourColor: z.string().optional(), + identified: z.boolean().optional(), + allowCraftsman: z.boolean().optional(), + restrictions: WynnItemRestrictions.optional(), + dropRestriction: WynnDropRestriction, + dropMeta: WynnDropMeta.optional(), + icon: WynnItemIcon.optional(), + requirements: z.object({ + level: z.number(), + classRequirement: z.string().optional(), + intelligence: z.number().optional(), + strength: z.number().optional(), + dexterity: z.number().optional(), + defence: z.number().optional(), + agility: z.number().optional(), + }), + majorIds: z.record(z.string(), z.string()).optional(), + powderSlots: z.number().optional(), + lore: z.string().optional(), + identifications: z + .record( + z.string(), + z.union([ + z.number(), + z.object({ + min: z.number(), + raw: z.number(), + max: z.number(), + }), + ]) + ) + .optional(), + base: WynnBaseStats.optional(), + rarity: WynnItemRarity, + }) + .strict() -export const WapiV3ItemArmour = z.object({ - internalName: z.string(), - type: z.literal('armour'), - armourType: z.string(), - armourMaterial: z.string().optional(), - armourColor: z.string().optional(), - identified: z.boolean().optional(), - allowCraftsman: z.boolean().optional(), - restrictions: WynnItemRestrictions.optional(), - dropRestriction: WynnDropRestriction, - dropMeta: WynnDropMeta.optional(), - icon: WynnItemIcon.optional(), - requirements: z.object({ - level: z.number(), - classRequirement: z.string().optional(), - intelligence: z.number().optional(), - strength: z.number().optional(), - dexterity: z.number().optional(), - defence: z.number().optional(), - agility: z.number().optional(), - }), - majorIds: z.record(z.string(), z.string()).optional(), - powderSlots: z.number().optional(), - lore: z.string().optional(), - identifications: z.record(z.string(), z.union([ - z.number(), - z.object({ - min: z.number(), - raw: z.number(), - max: z.number(), - }) - ])).optional(), - base: WynnBaseStats.optional(), - rarity:WynnItemRarity, -}).strict() - -export const WApiV3Item = z.discriminatedUnion("type",[ +export const WApiV3Item = z.discriminatedUnion('type', [ WapiV3ItemMaterial, WapiV3ItemWeapon, WapiV3ItemArmour, @@ -296,4 +299,4 @@ export const WApiV3Item = z.discriminatedUnion("type",[ WapiV3ItemTome, WapiV3ItemTool, ]) -export const WApiV3ItemDatabase= z.record(z.string(), WApiV3Item) +export const WApiV3ItemDatabase = z.record(z.string(), WApiV3Item) diff --git a/ts/src/lib/wynn/wapi.ts b/ts/src/lib/wynn/wapi.ts index 7c57d07..8b2c8fb 100644 --- a/ts/src/lib/wynn/wapi.ts +++ b/ts/src/lib/wynn/wapi.ts @@ -1,12 +1,11 @@ -import { config } from "#/config"; -import { inject, injectable } from "@needle-di/core"; -import axios, { AxiosInstance } from "axios"; -import { buildStorage, canStale, setupCache } from 'axios-cache-interceptor'; -import { BentoCache } from "bentocache"; - -import "#/services/bento"; -import { logger } from "#/logger"; +import { inject, injectable } from '@needle-di/core' +import axios, { type AxiosInstance } from 'axios' +import { buildStorage, canStale, setupCache } from 'axios-cache-interceptor' +import { BentoCache } from 'bentocache' +import { config } from '#/config' +import '#/services/bento' +import { logger } from '#/logger' @injectable() export class WApi { @@ -14,51 +13,42 @@ export class WApi { private readonly log = logger.child({ module: 'wapi' }) - constructor( - private readonly bento = inject(BentoCache) - ) { + constructor(private readonly bento = inject(BentoCache)) { const c = axios.create({ baseURL: config.WAPI_URL, headers: { - "User-Agent": "lil-robot-guy (a@tuxpa.in)", + 'User-Agent': 'lil-robot-guy (a@tuxpa.in)', }, }) const store = this.bento.namespace('wapi-cache') - - const self = this setupCache(c, { interpretHeader: true, ttl: 5000, storage: buildStorage({ async find(key, currentRequest) { - const value = await store.get({key}) - if(!value) { - return; + const value = await store.get({ key }) + if (!value) { + return } return JSON.parse(value) }, async remove(key, req) { - await store.delete({key}) + await store.delete({ key }) }, async set(key, value, req) { - let expireTime = value.state === 'loading' - ? Date.now() + - (req?.cache && typeof req.cache.ttl === 'number' - ? req.cache.ttl - : - 3000) - : // When a stale state has a determined value to expire, we can use it. - // Or if the cached value cannot enter in stale state. - (value.state === 'stale' && value.ttl) || - (value.state === 'cached' && !canStale(value)) - ? - value.createdAt + value.ttl! - : // otherwise, we can't determine when it should expire, so we keep - // it indefinitely. - undefined + const expireTime = + value.state === 'loading' + ? Date.now() + (req?.cache && typeof req.cache.ttl === 'number' ? req.cache.ttl : 3000) + : // When a stale state has a determined value to expire, we can use it. + // Or if the cached value cannot enter in stale state. + (value.state === 'stale' && value.ttl) || (value.state === 'cached' && !canStale(value)) + ? value.createdAt + value.ttl! + : // otherwise, we can't determine when it should expire, so we keep + // it indefinitely. + undefined let ttl: number | undefined - if(expireTime) { + if (expireTime) { ttl = expireTime - Date.now() } await store.set({ @@ -70,20 +60,18 @@ export class WApi { async clear() { await store.clear({}) }, - }) - }); - this.c = c; + }), + }) + this.c = c } - async get(path:string, params?: any) { + async get(path: string, params?: any) { return this.c.get(path, { params, headers: { - 'Accept': 'application/json', + Accept: 'application/json', 'Content-Type': 'application/json', - } + }, }) } - - } diff --git a/ts/src/logger/index.ts b/ts/src/logger/index.ts index 4fcd7eb..25860e8 100644 --- a/ts/src/logger/index.ts +++ b/ts/src/logger/index.ts @@ -1,11 +1,10 @@ -import {pino} from 'pino' +import { pino } from 'pino' - -export const logger = pino({ +export const logger = pino({ transport: { target: 'pino-logfmt', }, level: process.env.PINO_LOG_LEVEL || 'info', redact: [], // prevent logging of sensitive data -}); +}) diff --git a/ts/src/main.ts b/ts/src/main.ts index e4e03e0..23ef70e 100644 --- a/ts/src/main.ts +++ b/ts/src/main.ts @@ -1,10 +1,6 @@ -import { runExit } from "clipanion"; +import { runExit } from 'clipanion' -import { WorkerCommand } from "#/cmd/worker"; -import { BotCommand } from "#/cmd/bot"; +import { BotCommand } from '#/cmd/bot' +import { WorkerCommand } from '#/cmd/worker' - -runExit([ - WorkerCommand, - BotCommand, -]) +runExit([WorkerCommand, BotCommand]) diff --git a/ts/src/payload_converter/adapter.ts b/ts/src/payload_converter/adapter.ts index d02f173..5324c85 100644 --- a/ts/src/payload_converter/adapter.ts +++ b/ts/src/payload_converter/adapter.ts @@ -1,37 +1,27 @@ -import { - EncodingType, - METADATA_ENCODING_KEY, - Payload, - PayloadConverterError, - PayloadConverterWithEncoding, -} from '@temporalio/common'; -import { decode, encode } from '@temporalio/common/lib/encoding'; -import { errorMessage } from '@temporalio/common/lib/type-helpers'; -import superjson from 'superjson'; - - +import { type EncodingType, METADATA_ENCODING_KEY, type Payload, PayloadConverterError, type PayloadConverterWithEncoding } from '@temporalio/common' +import { decode, encode } from '@temporalio/common/lib/encoding' +import { errorMessage } from '@temporalio/common/lib/type-helpers' +import superjson from 'superjson' /** * Converts between values and [superjson](https://github.com/flightcontrolhq/superjson) Payloads. */ export class SuperJsonPayloadConverter implements PayloadConverterWithEncoding { // Use 'json/plain' so that Payloads are displayed in the UI - public encodingType = 'json/plain' as EncodingType; + public encodingType = 'json/plain' as EncodingType public toPayload(value: unknown): Payload | undefined { - if (value === undefined) return undefined; - let ejson; + if (value === undefined) return undefined + let ejson try { - ejson = superjson.stringify(value); + ejson = superjson.stringify(value) } catch (e) { throw new UnsupportedSuperJsonTypeError( - `Can't run superjson.stringify on this value: ${value}. Either convert it (or its properties) to superjson-serializable values (see https://docs.meteor.com/api/ejson.html ), or create a custom data converter. superjson.stringify error message: ${ - errorMessage( - e, - ) - }`, - e as Error, - ); + `Can't run superjson.stringify on this value: ${value}. Either convert it (or its properties) to superjson-serializable values (see https://docs.meteor.com/api/ejson.html ), or create a custom data converter. superjson.stringify error message: ${errorMessage( + e + )}`, + e as Error + ) } return { @@ -41,21 +31,21 @@ export class SuperJsonPayloadConverter implements PayloadConverterWithEncoding { format: encode('extended'), }, data: encode(ejson), - }; + } } public fromPayload(content: Payload): T { - return content.data ? superjson.parse(decode(content.data)) : {} as T; + return content.data ? superjson.parse(decode(content.data)) : ({} as T) } } export class UnsupportedSuperJsonTypeError extends PayloadConverterError { - public readonly name: string = 'UnsupportedJsonTypeError'; + public readonly name: string = 'UnsupportedJsonTypeError' constructor( message: string | undefined, - public readonly cause?: Error, + public readonly cause?: Error ) { - super(message ?? undefined); + super(message ?? undefined) } } diff --git a/ts/src/payload_converter/index.ts b/ts/src/payload_converter/index.ts index 2a24a1f..d1e0edb 100644 --- a/ts/src/payload_converter/index.ts +++ b/ts/src/payload_converter/index.ts @@ -1,10 +1,4 @@ -import { - CompositePayloadConverter, - UndefinedPayloadConverter, -} from '@temporalio/common'; -import { SuperJsonPayloadConverter } from './adapter'; +import { CompositePayloadConverter, UndefinedPayloadConverter } from '@temporalio/common' +import { SuperJsonPayloadConverter } from './adapter' -export const payloadConverter = new CompositePayloadConverter( - new UndefinedPayloadConverter(), - new SuperJsonPayloadConverter(), -); +export const payloadConverter = new CompositePayloadConverter(new UndefinedPayloadConverter(), new SuperJsonPayloadConverter()) diff --git a/ts/src/services/bento/index.ts b/ts/src/services/bento/index.ts index bb25e9e..d3c590a 100644 --- a/ts/src/services/bento/index.ts +++ b/ts/src/services/bento/index.ts @@ -1,31 +1,29 @@ -import { config } from '#/config' -import { c } from '#/di' import { BentoCache, bentostore } from 'bentocache' import { memoryDriver } from 'bentocache/drivers/memory' import { redisDriver } from 'bentocache/drivers/redis' import IORedis from 'ioredis' +import { config } from '#/config' +import { c } from '#/di' c.bind({ provide: BentoCache, useFactory: () => { - const defaultStore = bentostore() defaultStore.useL1Layer(memoryDriver({ maxSize: '32mb' })) - if(config.REDIS_URL) { - defaultStore.useL2Layer(redisDriver({ - connection: new IORedis(config.REDIS_URL), - prefix: 'wynn-bento', - })) + if (config.REDIS_URL) { + defaultStore.useL2Layer( + redisDriver({ + connection: new IORedis(config.REDIS_URL), + prefix: 'wynn-bento', + }) + ) } const bento = new BentoCache({ default: 'cache', stores: { cache: defaultStore, - } + }, }) return bento - } + }, }) - - - diff --git a/ts/src/services/pg/index.ts b/ts/src/services/pg/index.ts index 678c227..e326805 100644 --- a/ts/src/services/pg/index.ts +++ b/ts/src/services/pg/index.ts @@ -1,10 +1,10 @@ -import { config } from "#/config"; -import { injectable } from "@needle-di/core"; -import postgres, { Sql } from "postgres"; +import { injectable } from '@needle-di/core' +import postgres, { type Sql } from 'postgres' +import { config } from '#/config' @injectable() export class PG { - readonly db: Sql; + readonly db: Sql get sql() { return this.db } @@ -13,10 +13,10 @@ export class PG { const opts = { onnotice: () => {}, } - let db: Sql; - if(config.PG_URL) { - db = postgres(config.PG_URL, opts); - }else { + let db: Sql + if (config.PG_URL) { + db = postgres(config.PG_URL, opts) + } else { db = postgres({ host: config.PG_HOST, port: config.PG_PORT, @@ -30,4 +30,3 @@ export class PG { this.db = db } } - diff --git a/ts/src/services/temporal/index.ts b/ts/src/services/temporal/index.ts index bb62c70..565e567 100644 --- a/ts/src/services/temporal/index.ts +++ b/ts/src/services/temporal/index.ts @@ -1,6 +1,6 @@ -import { config } from "#/config"; -import { c } from "#/di"; -import { Client, Connection} from '@temporalio/client'; +import { Client, Connection } from '@temporalio/client' +import { config } from '#/config' +import { c } from '#/di' c.bind({ provide: Client, @@ -15,11 +15,11 @@ c.bind({ dataConverter: { payloadConverterPath: require.resolve('../../payload_converter'), }, - }); + }) process.on('exit', () => { - console.log('closing temporal client'); - client.connection.close(); - }); + console.log('closing temporal client') + client.connection.close() + }) return client }, -}); +}) diff --git a/ts/src/workflows/discord.ts b/ts/src/workflows/discord.ts index b43e5d2..59b80fe 100644 --- a/ts/src/workflows/discord.ts +++ b/ts/src/workflows/discord.ts @@ -1,21 +1,19 @@ -import { proxyActivities, startChild, workflowInfo } from '@temporalio/workflow'; -import type * as activities from '#/activities'; -import { InteractionTypes } from '@discordeno/types'; -import { handleCommandGuildInfo, handleCommandGuildOnline, handleCommandGuildLeaderboard } from './guild_messages'; -import { handleCommandPlayerLookup } from './player_messages'; -import { createCommandHandler } from '#/discord/botevent/command_parser'; -import { SLASH_COMMANDS } from '#/discord/botevent/slash_commands'; -import { InteractionCreatePayload} from '#/discord'; +import { InteractionTypes } from '@discordeno/types' +import { proxyActivities, startChild, workflowInfo } from '@temporalio/workflow' +import type * as activities from '#/activities' +import type { InteractionCreatePayload } from '#/discord' +import { createCommandHandler } from '#/discord/botevent/command_parser' +import type { SLASH_COMMANDS } from '#/discord/botevent/slash_commands' +import { handleCommandGuildInfo, handleCommandGuildLeaderboard, handleCommandGuildOnline } from './guild_messages' +import { handleCommandPlayerLookup } from './player_messages' const { reply_to_interaction } = proxyActivities({ startToCloseTimeout: '1 minute', -}); +}) // Define command handlers with type safety -const workflowHandleApplicationCommand = async ( - payload: InteractionCreatePayload, -) => { - const { ref, data } = payload; +const workflowHandleApplicationCommand = async (payload: InteractionCreatePayload) => { + const { ref, data } = payload const notFoundHandler = async (content: string) => { await reply_to_interaction({ @@ -24,52 +22,52 @@ const workflowHandleApplicationCommand = async ( options: { content: content, isPrivate: true, - } - }); + }, + }) } if (!data || !data.name) { - await notFoundHandler(`Invalid command data`); + await notFoundHandler(`Invalid command data`) return } const commandHandler = createCommandHandler({ notFoundHandler: async () => { - await notFoundHandler(`command not found`); + await notFoundHandler(`command not found`) }, handler: { player: { lookup: async (args) => { - const { workflowId } = workflowInfo(); + const { workflowId } = workflowInfo() const handle = await startChild(handleCommandPlayerLookup, { args: [{ ref, args }], workflowId: `${workflowId}-player-lookup`, - }); - await handle.result(); + }) + await handle.result() }, }, guild: { info: async (args) => { - const { workflowId } = workflowInfo(); + const { workflowId } = workflowInfo() const handle = await startChild(handleCommandGuildInfo, { args: [{ ref }], workflowId: `${workflowId}-guild-info`, - }); - await handle.result(); + }) + await handle.result() }, online: async (args) => { - const { workflowId } = workflowInfo(); + const { workflowId } = workflowInfo() const handle = await startChild(handleCommandGuildOnline, { args: [{ ref }], workflowId: `${workflowId}-guild-online`, - }); - await handle.result(); + }) + await handle.result() }, leaderboard: async (args) => { - const { workflowId } = workflowInfo(); + const { workflowId } = workflowInfo() const handle = await startChild(handleCommandGuildLeaderboard, { args: [{ ref }], workflowId: `${workflowId}-guild-leaderboard`, - }); - await handle.result(); + }) + await handle.result() }, }, admin: { @@ -78,24 +76,22 @@ const workflowHandleApplicationCommand = async ( ref, type: 4, options: { - content: "Not implemented yet", + content: 'Not implemented yet', isPrivate: true, - } - }); + }, + }) }, }, - } - }); + }, + }) - await commandHandler(data); + await commandHandler(data) } -export const workflowHandleInteractionCreate = async ( - payload: InteractionCreatePayload, -) => { - const {ref, data} = payload +export const workflowHandleInteractionCreate = async (payload: InteractionCreatePayload) => { + const { ref, data } = payload - if(ref.type === InteractionTypes.ApplicationCommand) { + if (ref.type === InteractionTypes.ApplicationCommand) { await workflowHandleApplicationCommand(payload) } } diff --git a/ts/src/workflows/guild_messages.ts b/ts/src/workflows/guild_messages.ts index 1c5f6de..fb1bd95 100644 --- a/ts/src/workflows/guild_messages.ts +++ b/ts/src/workflows/guild_messages.ts @@ -1,47 +1,42 @@ -import { proxyActivities } from "@temporalio/workflow"; -import type * as activities from "#/activities"; -import { WYNN_GUILD_ID } from "#/constants"; -import { InteractionRef } from "#/discord"; +import { proxyActivities } from '@temporalio/workflow' +import type * as activities from '#/activities' +import { WYNN_GUILD_ID } from '#/constants' +import type { InteractionRef } from '#/discord' -const { - formGuildInfoMessage, - formGuildOnlineMessage, - formGuildLeaderboardMessage, - reply_to_interaction -} = proxyActivities({ +const { formGuildInfoMessage, formGuildOnlineMessage, formGuildLeaderboardMessage, reply_to_interaction } = proxyActivities({ startToCloseTimeout: '30 seconds', -}); +}) interface CommandPayload { - ref: InteractionRef; + ref: InteractionRef } export async function handleCommandGuildInfo(payload: CommandPayload): Promise { - const { ref } = payload; - const msg = await formGuildInfoMessage(WYNN_GUILD_ID); + const { ref } = payload + const msg = await formGuildInfoMessage(WYNN_GUILD_ID) await reply_to_interaction({ ref, type: 4, options: msg, - }); + }) } export async function handleCommandGuildOnline(payload: CommandPayload): Promise { - const { ref } = payload; - const msg = await formGuildOnlineMessage(WYNN_GUILD_ID); + const { ref } = payload + const msg = await formGuildOnlineMessage(WYNN_GUILD_ID) await reply_to_interaction({ ref, type: 4, options: msg, - }); + }) } export async function handleCommandGuildLeaderboard(payload: CommandPayload): Promise { - const { ref } = payload; - const msg = await formGuildLeaderboardMessage(WYNN_GUILD_ID); + const { ref } = payload + const msg = await formGuildLeaderboardMessage(WYNN_GUILD_ID) await reply_to_interaction({ ref, type: 4, options: msg, - }); -} \ No newline at end of file + }) +} diff --git a/ts/src/workflows/guilds.ts b/ts/src/workflows/guilds.ts index 3c7d528..ae22639 100644 --- a/ts/src/workflows/guilds.ts +++ b/ts/src/workflows/guilds.ts @@ -1,29 +1,25 @@ - -import { proxyActivities } from '@temporalio/workflow'; -import type * as activities from '#/activities'; +import { proxyActivities } from '@temporalio/workflow' +import type * as activities from '#/activities' const { update_guild, update_all_guilds, update_guild_levels } = proxyActivities({ startToCloseTimeout: '1 minute', -}); +}) -export const workflowSyncAllGuilds = async() => { +export const workflowSyncAllGuilds = async () => { await update_all_guilds() } -export const workflowSyncGuildLeaderboardInfo = async() => { +export const workflowSyncGuildLeaderboardInfo = async () => { await update_guild_levels() } -export const workflowSyncGuilds = async() => { +export const workflowSyncGuilds = async () => { // TODO side effect - const guildNames = [ - 'less than three', - ] - for(const guildName of guildNames) { + const guildNames = ['less than three'] + for (const guildName of guildNames) { // update the guild await update_guild({ guild_name: guildName, }) } } - diff --git a/ts/src/workflows/index.ts b/ts/src/workflows/index.ts index 91e4a1d..667f03d 100644 --- a/ts/src/workflows/index.ts +++ b/ts/src/workflows/index.ts @@ -2,9 +2,9 @@ * @file Automatically generated by barrelsby. */ -export * from "./discord"; -export * from "./guild_messages"; -export * from "./guilds"; -export * from "./items"; -export * from "./player_messages"; -export * from "./players"; +export * from './discord' +export * from './guild_messages' +export * from './guilds' +export * from './items' +export * from './player_messages' +export * from './players' diff --git a/ts/src/workflows/items.ts b/ts/src/workflows/items.ts index e3fa312..1827dd3 100644 --- a/ts/src/workflows/items.ts +++ b/ts/src/workflows/items.ts @@ -1,15 +1,13 @@ - -import { proxyActivities } from '@temporalio/workflow'; -import type * as activities from '#/activities'; +import { proxyActivities } from '@temporalio/workflow' +import type * as activities from '#/activities' const { update_wynn_items } = proxyActivities({ startToCloseTimeout: '1 minute', retry: { maximumAttempts: 1, - } -}); - -export const workflowSyncItemDatabase = async() => { - const {found_new} = await update_wynn_items(); + }, +}) +export const workflowSyncItemDatabase = async () => { + const { found_new } = await update_wynn_items() } diff --git a/ts/src/workflows/player_messages.ts b/ts/src/workflows/player_messages.ts index 413bc4b..48a80ee 100644 --- a/ts/src/workflows/player_messages.ts +++ b/ts/src/workflows/player_messages.ts @@ -1,23 +1,21 @@ -import { proxyActivities } from "@temporalio/workflow"; -import type * as activities from "#/activities"; -import { InteractionRef } from "#/discord"; +import { proxyActivities } from '@temporalio/workflow' +import type * as activities from '#/activities' +import type { InteractionRef } from '#/discord' -const { - reply_to_interaction -} = proxyActivities({ +const { reply_to_interaction } = proxyActivities({ startToCloseTimeout: '30 seconds', -}); +}) interface CommandPayload { - ref: InteractionRef; + ref: InteractionRef args: { - player: string; - }; + player: string + } } export async function handleCommandPlayerLookup(payload: CommandPayload): Promise { - const { ref, args } = payload; - const playerName = args.player; + const { ref, args } = payload + const playerName = args.player try { // For now, we'll send a simple response @@ -29,7 +27,7 @@ export async function handleCommandPlayerLookup(payload: CommandPayload): Promis content: `Looking up player: **${playerName}**\n\n*Player lookup functionality coming soon!*`, isPrivate: false, }, - }); + }) } catch (error) { await reply_to_interaction({ ref, @@ -38,6 +36,6 @@ export async function handleCommandPlayerLookup(payload: CommandPayload): Promis content: `Error looking up player: ${playerName}`, isPrivate: true, }, - }); + }) } -} \ No newline at end of file +} diff --git a/ts/src/workflows/players.ts b/ts/src/workflows/players.ts index 76fc405..860fa6c 100644 --- a/ts/src/workflows/players.ts +++ b/ts/src/workflows/players.ts @@ -1,14 +1,13 @@ - -import { proxyActivities } from '@temporalio/workflow'; -import type * as activities from '#/activities'; +import { proxyActivities } from '@temporalio/workflow' +import type * as activities from '#/activities' const { scrape_online_players } = proxyActivities({ startToCloseTimeout: '1 minute', retry: { maximumAttempts: 1, - } -}); + }, +}) -export const workflowSyncOnline = async() => { - await scrape_online_players(); +export const workflowSyncOnline = async () => { + await scrape_online_players() } diff --git a/ts/yarn.lock b/ts/yarn.lock index a0c7144..9b451d5 100644 --- a/ts/yarn.lock +++ b/ts/yarn.lock @@ -21,6 +21,97 @@ __metadata: languageName: node linkType: hard +"@biomejs/biome@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/biome@npm:1.9.4" + dependencies: + "@biomejs/cli-darwin-arm64": "npm:1.9.4" + "@biomejs/cli-darwin-x64": "npm:1.9.4" + "@biomejs/cli-linux-arm64": "npm:1.9.4" + "@biomejs/cli-linux-arm64-musl": "npm:1.9.4" + "@biomejs/cli-linux-x64": "npm:1.9.4" + "@biomejs/cli-linux-x64-musl": "npm:1.9.4" + "@biomejs/cli-win32-arm64": "npm:1.9.4" + "@biomejs/cli-win32-x64": "npm:1.9.4" + dependenciesMeta: + "@biomejs/cli-darwin-arm64": + optional: true + "@biomejs/cli-darwin-x64": + optional: true + "@biomejs/cli-linux-arm64": + optional: true + "@biomejs/cli-linux-arm64-musl": + optional: true + "@biomejs/cli-linux-x64": + optional: true + "@biomejs/cli-linux-x64-musl": + optional: true + "@biomejs/cli-win32-arm64": + optional: true + "@biomejs/cli-win32-x64": + optional: true + bin: + biome: bin/biome + checksum: 10c0/b5655c5aed9a6fffe24f7d04f15ba4444389d0e891c9ed9106fab7388ac9b4be63185852cc2a937b22940dac3e550b71032a4afd306925cfea436c33e5646b3e + languageName: node + linkType: hard + +"@biomejs/cli-darwin-arm64@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/cli-darwin-arm64@npm:1.9.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@biomejs/cli-darwin-x64@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/cli-darwin-x64@npm:1.9.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@biomejs/cli-linux-arm64-musl@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/cli-linux-arm64-musl@npm:1.9.4" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@biomejs/cli-linux-arm64@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/cli-linux-arm64@npm:1.9.4" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@biomejs/cli-linux-x64-musl@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/cli-linux-x64-musl@npm:1.9.4" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@biomejs/cli-linux-x64@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/cli-linux-x64@npm:1.9.4" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@biomejs/cli-win32-arm64@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/cli-win32-arm64@npm:1.9.4" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@biomejs/cli-win32-x64@npm:1.9.4": + version: 1.9.4 + resolution: "@biomejs/cli-win32-x64@npm:1.9.4" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@boringnode/bus@npm:^0.7.1": version: 0.7.1 resolution: "@boringnode/bus@npm:0.7.1" @@ -1168,27 +1259,25 @@ __metadata: languageName: node linkType: hard -"@ts-rest/core@https://gitpkg.vercel.app/aidant/ts-rest/libs/ts-rest/core?feat-standard-schema": - version: 3.52.0 - resolution: "@ts-rest/core@https://gitpkg.vercel.app/aidant/ts-rest/libs/ts-rest/core?feat-standard-schema" +"@ts-rest/core@npm:^3.53.0-rc.1": + version: 3.53.0-rc.1 + resolution: "@ts-rest/core@npm:3.53.0-rc.1" peerDependencies: "@types/node": ^18.18.7 || >=20.8.4 - zod: ^3.24.0 peerDependenciesMeta: "@types/node": optional: true - zod: - optional: true - checksum: 10c0/fba55ca7d1a5161d3ec1af850c9c98d34efd1b4373ad8ca4108737c2d75fa11ef033da6e9cb657cd5078465c09ff6761c029053f9c59022075b8d3e128f298a5 + checksum: 10c0/a1a8c304f797da016ad968878a47c75fb0dcb7811c6f8e2ae81a3f9f3dedba30c5b5c8b14668437c136c9eca9b361e7048117478330bd449a2fbbc53f84f73cb languageName: node linkType: hard -"@ts-rest/fastify@https://gitpkg.vercel.app/aidant/ts-rest/libs/ts-rest/fastify?feat-standard-schema": - version: 3.52.0 - resolution: "@ts-rest/fastify@https://gitpkg.vercel.app/aidant/ts-rest/libs/ts-rest/fastify?feat-standard-schema" +"@ts-rest/fastify@npm:^3.53.0-rc.1": + version: 3.53.0-rc.1 + resolution: "@ts-rest/fastify@npm:3.53.0-rc.1" peerDependencies: + "@ts-rest/core": 3.53.0-rc.1 fastify: ^4.0.0 - checksum: 10c0/8e8a31fda5a49c4fc976962df29129a24ad3c9c4896af38a6deb95bf60e36943c29bef56ff601bb63cab883d845095c7793ae266e92876cf74a7a05d238ba00f + checksum: 10c0/5d935903743e457873036dc943538b8603081e1d4a1f28bb7b79b0f1a6cee8ddcd4aef839318f5f00c7b24f38491e68977aa8ef41b2665f4702d47eeba0cee9e languageName: node linkType: hard @@ -1799,6 +1888,7 @@ __metadata: version: 0.0.0-use.local resolution: "backend@workspace:." dependencies: + "@biomejs/biome": "npm:1.9.4" "@discordeno/types": "npm:^21.0.0" "@needle-di/core": "npm:^0.10.1" "@temporalio/activity": "npm:^1.11.7" @@ -1806,8 +1896,8 @@ __metadata: "@temporalio/common": "npm:^1.11.7" "@temporalio/worker": "npm:^1.11.7" "@temporalio/workflow": "npm:^1.11.7" - "@ts-rest/core": "https://gitpkg.vercel.app/aidant/ts-rest/libs/ts-rest/core?feat-standard-schema" - "@ts-rest/fastify": "https://gitpkg.vercel.app/aidant/ts-rest/libs/ts-rest/fastify?feat-standard-schema" + "@ts-rest/core": "npm:^3.53.0-rc.1" + "@ts-rest/fastify": "npm:^3.53.0-rc.1" "@types/node": "npm:^22.13.4" "@types/object-hash": "npm:^3" "@vitest/runner": "npm:^3.2.3"