noot
This commit is contained in:
parent
6c26594bc6
commit
53f46934e8
Binary file not shown.
@ -1,10 +1,5 @@
|
||||
{
|
||||
"delete": true,
|
||||
"directory": [
|
||||
"./src/activities",
|
||||
"./src/workflows"
|
||||
],
|
||||
"exclude": [
|
||||
"types.ts"
|
||||
]
|
||||
"directory": ["./src/activities", "./src/workflows"],
|
||||
"exclude": ["types.ts"]
|
||||
}
|
||||
|
||||
41
ts/biome.json
Normal file
41
ts/biome.json
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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",
|
||||
|
||||
@ -1,17 +1,16 @@
|
||||
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"]])
|
||||
const resp = unionType([[1, '2']])
|
||||
if (resp instanceof ArkErrors) {
|
||||
const err = resp[0]
|
||||
console.log(err.data)
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
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)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
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: {
|
||||
@ -9,24 +9,19 @@ export const reply_to_interaction = async (props: {
|
||||
type: number
|
||||
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);
|
||||
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 })
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
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)
|
||||
@ -12,10 +12,10 @@ export async function update_all_guilds() {
|
||||
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)
|
||||
@ -32,7 +32,7 @@ export async function update_all_guilds() {
|
||||
}
|
||||
|
||||
export async function update_guild({
|
||||
guild_name
|
||||
guild_name,
|
||||
}: {
|
||||
guild_name: string
|
||||
}) {
|
||||
|
||||
@ -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<CreateMessageOptions & InteractionCallbackOptions> {
|
||||
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<CreateMessageOptions & InteractionCallbackOptions> {
|
||||
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<CreateMe
|
||||
on gi.uid = gm.guild_id
|
||||
where minecraft.user.server is not null
|
||||
and gm.guild_id = ${guild_id}
|
||||
`;
|
||||
`
|
||||
|
||||
const members = type({
|
||||
guild_name: "string",
|
||||
guild_prefix: "string",
|
||||
name: "string",
|
||||
rank: "string",
|
||||
contributed: "string",
|
||||
server: "string",
|
||||
}).array().assert(result);
|
||||
guild_name: 'string',
|
||||
guild_prefix: 'string',
|
||||
name: 'string',
|
||||
rank: 'string',
|
||||
contributed: 'string',
|
||||
server: 'string',
|
||||
})
|
||||
.array()
|
||||
.assert(result)
|
||||
|
||||
if (members.length == 0) {
|
||||
return {
|
||||
content: "😴 No guild members are currently online.",
|
||||
};
|
||||
content: '😴 No guild members are currently online.',
|
||||
}
|
||||
}
|
||||
|
||||
// Get 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
|
||||
|
||||
// Sort by contribution
|
||||
members.sort((a, b) => 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) => {
|
||||
const membersByServer = members.reduce(
|
||||
(acc, member) => {
|
||||
if (acc[member.server] == undefined) {
|
||||
acc[member.server] = [];
|
||||
acc[member.server] = []
|
||||
}
|
||||
acc[member.server].push(member);
|
||||
return acc;
|
||||
}, {} as Record<string, typeof members>);
|
||||
acc[member.server].push(member)
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, typeof members>
|
||||
)
|
||||
|
||||
// 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<CreateMessageOptions & InteractionCallbackOptions> {
|
||||
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<Cre
|
||||
inner join wynn.guild_info gi
|
||||
on gi.uid = gm.guild_id
|
||||
where gm.guild_id = ${guild_id}
|
||||
`;
|
||||
`
|
||||
|
||||
const members = type({
|
||||
guild_name: "string",
|
||||
guild_prefix: "string",
|
||||
name: "string",
|
||||
rank: "string",
|
||||
contributed: "string",
|
||||
}).array().assert(result);
|
||||
guild_name: 'string',
|
||||
guild_prefix: 'string',
|
||||
name: 'string',
|
||||
rank: 'string',
|
||||
contributed: 'string',
|
||||
})
|
||||
.array()
|
||||
.assert(result)
|
||||
|
||||
if (members.length === 0) {
|
||||
return {
|
||||
content: "No guild members found.",
|
||||
};
|
||||
content: 'No guild members found.',
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by contribution
|
||||
members.sort((a, b) => 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<Cre
|
||||
`👥 **Total Members:** \`${members.length}\``,
|
||||
`📊 **Average Contribution:** \`${avgContribution.toLocaleString()}\`\n`,
|
||||
`### Top Contributors`,
|
||||
"```",
|
||||
'```',
|
||||
leaderboardTable,
|
||||
"```",
|
||||
`*Showing top ${Math.min(members.length, 10)} of ${members.length} members*`
|
||||
].join("\n");
|
||||
'```',
|
||||
`*Showing top ${Math.min(members.length, 10)} of ${members.length} members*`,
|
||||
].join('\n')
|
||||
|
||||
return {
|
||||
content: output,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
* @file Automatically generated by barrelsby.
|
||||
*/
|
||||
|
||||
export * from "./database";
|
||||
export * from "./discord";
|
||||
export * from "./guild";
|
||||
export * from "./guild_messages";
|
||||
export * from "./leaderboards";
|
||||
export * from "./players";
|
||||
export * from './database'
|
||||
export * from './discord'
|
||||
export * from './guild'
|
||||
export * from './guild_messages'
|
||||
export * from './leaderboards'
|
||||
export * from './players'
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { c } from "#/di";
|
||||
import { WApi } from "#/lib/wynn/wapi";
|
||||
import { PG } from "#/services/pg";
|
||||
import { type } from "arktype";
|
||||
import { type } from 'arktype'
|
||||
import { c } from '#/di'
|
||||
import { WApi } from '#/lib/wynn/wapi'
|
||||
import { PG } from '#/services/pg'
|
||||
|
||||
export async function update_guild_levels() {
|
||||
const api = await c.getAsync(WApi)
|
||||
@ -10,13 +10,13 @@ export async function update_guild_levels() {
|
||||
throw new Error('Failed to get guild list from wapi')
|
||||
}
|
||||
const parsed = type({
|
||||
"[string]": {
|
||||
uuid: "string",
|
||||
name: "string",
|
||||
prefix: "string",
|
||||
xp: "number",
|
||||
level: "number",
|
||||
}
|
||||
'[string]': {
|
||||
uuid: 'string',
|
||||
name: 'string',
|
||||
prefix: 'string',
|
||||
xp: 'number',
|
||||
level: 'number',
|
||||
},
|
||||
}).assert(ans.data)
|
||||
const { sql } = await c.getAsync(PG)
|
||||
for (const [_, guild] of Object.entries(parsed)) {
|
||||
|
||||
@ -1,72 +1,73 @@
|
||||
import { c } from "#/di"
|
||||
import { WApi } from "#/lib/wynn/wapi"
|
||||
import { PG } from "#/services/pg"
|
||||
import { log } from "@temporalio/activity"
|
||||
import { type } from "arktype"
|
||||
import axios from "axios"
|
||||
import { log } from '@temporalio/activity'
|
||||
import { type } from 'arktype'
|
||||
import axios from 'axios'
|
||||
import { c } from '#/di'
|
||||
import { WApi } from '#/lib/wynn/wapi'
|
||||
import { PG } from '#/services/pg'
|
||||
|
||||
const playerSchemaFail = type({
|
||||
code: "string",
|
||||
message: "string",
|
||||
code: 'string',
|
||||
message: 'string',
|
||||
data: type({
|
||||
player: {
|
||||
meta: {
|
||||
cached_at: "number"
|
||||
cached_at: 'number',
|
||||
},
|
||||
username: 'string',
|
||||
id: 'string',
|
||||
raw_id: 'string',
|
||||
avatar: 'string',
|
||||
skin_texture: 'string',
|
||||
properties: [
|
||||
{
|
||||
name: 'string',
|
||||
value: 'string',
|
||||
signature: 'string',
|
||||
},
|
||||
],
|
||||
name_history: 'unknown[]',
|
||||
},
|
||||
username: "string",
|
||||
id: "string",
|
||||
raw_id: "string",
|
||||
avatar: "string",
|
||||
skin_texture: "string",
|
||||
properties: [{
|
||||
name: "string",
|
||||
value: "string",
|
||||
signature: "string"
|
||||
}],
|
||||
name_history: "unknown[]"
|
||||
}
|
||||
}),
|
||||
success: "false"
|
||||
success: 'false',
|
||||
})
|
||||
const playerSchemaSuccess = type({
|
||||
code: "string",
|
||||
message: "string",
|
||||
code: 'string',
|
||||
message: 'string',
|
||||
data: type({
|
||||
player: {
|
||||
meta: {
|
||||
cached_at: "number"
|
||||
cached_at: 'number',
|
||||
},
|
||||
username: 'string',
|
||||
id: 'string',
|
||||
raw_id: 'string',
|
||||
avatar: 'string',
|
||||
skin_texture: 'string',
|
||||
properties: [
|
||||
{
|
||||
name: 'string',
|
||||
value: 'string',
|
||||
signature: 'string',
|
||||
},
|
||||
],
|
||||
name_history: 'unknown[]',
|
||||
},
|
||||
username: "string",
|
||||
id: "string",
|
||||
raw_id: "string",
|
||||
avatar: "string",
|
||||
skin_texture: "string",
|
||||
properties: [{
|
||||
name: "string",
|
||||
value: "string",
|
||||
signature: "string"
|
||||
}],
|
||||
name_history: "unknown[]"
|
||||
}
|
||||
}),
|
||||
success: "true"
|
||||
success: 'true',
|
||||
})
|
||||
|
||||
const playerSchema = playerSchemaFail.or(playerSchemaSuccess)
|
||||
|
||||
|
||||
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)) {
|
||||
@ -78,13 +79,13 @@ export const scrape_online_players = async()=>{
|
||||
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) {
|
||||
log.warn(`failed to get uuid for ${playerName}`, {
|
||||
"payload": parsedPlayer,
|
||||
payload: parsedPlayer,
|
||||
})
|
||||
continue
|
||||
}
|
||||
@ -97,7 +98,7 @@ export const scrape_online_players = async()=>{
|
||||
`
|
||||
} catch (e) {
|
||||
log.warn(`failed to get uuid for ${playerName}`, {
|
||||
"err": e,
|
||||
err: e,
|
||||
})
|
||||
continue
|
||||
}
|
||||
@ -110,7 +111,7 @@ export const scrape_online_players = async()=>{
|
||||
await sql`update minecraft.user set server = ${server} where name = ${playerName}`
|
||||
} catch (e) {
|
||||
log.warn(`failed to update server for ${playerName}`, {
|
||||
"err": e,
|
||||
err: e,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
@ -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({
|
||||
const ingameauth = con.router(
|
||||
{
|
||||
challenge: {
|
||||
description: "generate a challenge for the client to solve",
|
||||
method: "GET",
|
||||
path: "/challenge",
|
||||
description: 'generate a challenge for the client to solve',
|
||||
method: 'GET',
|
||||
path: '/challenge',
|
||||
responses: {
|
||||
200: type({
|
||||
challenge: "string.uuid",
|
||||
challenge: 'string.uuid',
|
||||
}),
|
||||
},
|
||||
query: type({
|
||||
uuid: "string.uuid",
|
||||
uuid: 'string.uuid',
|
||||
}),
|
||||
},
|
||||
solve: {
|
||||
description: "attempt to solve the challenge and get the token for the challenge",
|
||||
method: "POST",
|
||||
path: "/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",
|
||||
challenge: 'string.uuid',
|
||||
uuid: 'string.uuid',
|
||||
}),
|
||||
responses: {
|
||||
200: type({
|
||||
success: "true",
|
||||
challenge: "string.uuid",
|
||||
uuid: "string.uuid",
|
||||
success: 'true',
|
||||
challenge: 'string.uuid',
|
||||
uuid: 'string.uuid',
|
||||
}),
|
||||
401: type({
|
||||
success: "false",
|
||||
reason: "string",
|
||||
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,
|
||||
})
|
||||
|
||||
|
||||
@ -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');
|
||||
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')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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: [{
|
||||
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: [{
|
||||
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: [{
|
||||
intervals: [
|
||||
{
|
||||
every: '1 hour',
|
||||
}]
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
scheduleId: "update-online-players",
|
||||
scheduleId: 'update-online-players',
|
||||
action: {
|
||||
type: 'startWorkflow',
|
||||
workflowType: workflowSyncOnline,
|
||||
@ -76,9 +79,11 @@ const schedules: ScheduleOptions[] = [
|
||||
overlap: ScheduleOverlapPolicy.SKIP,
|
||||
},
|
||||
spec: {
|
||||
intervals: [{
|
||||
intervals: [
|
||||
{
|
||||
every: '31 seconds',
|
||||
}]
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
@ -87,27 +92,26 @@ const addSchedules = async (c: Client) => {
|
||||
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) {
|
||||
await c.schedule.create(o)
|
||||
} else {
|
||||
throw e;
|
||||
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,
|
||||
@ -121,28 +125,23 @@ export class WorkerCommand extends Command {
|
||||
},
|
||||
bundlerOptions: {
|
||||
webpackConfigHook: (config) => {
|
||||
if(!config.resolve) config.resolve = {};
|
||||
if(!config.resolve.alias) config.resolve.alias = {};
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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<Sql>("T_PG")
|
||||
export const c = new Container()
|
||||
export const T_PG = new InjectionToken<Sql>('T_PG')
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
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({
|
||||
@ -10,13 +10,13 @@ const createBotWithToken = (token: string) => {
|
||||
token,
|
||||
})
|
||||
}
|
||||
export const Bot = new InjectionToken<BotType>("DISCORD_BOT")
|
||||
export const Bot = new InjectionToken<BotType>('DISCORD_BOT')
|
||||
c.bind({
|
||||
provide: Bot,
|
||||
useFactory: () => {
|
||||
let token = config.DISCORD_TOKEN
|
||||
const token = config.DISCORD_TOKEN
|
||||
if (!token) {
|
||||
throw new Error('no discord token found. bot cant start');
|
||||
throw new Error('no discord token found. bot cant start')
|
||||
}
|
||||
const bot = createBotWithToken(token)
|
||||
return bot
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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, Name> = Options extends readonly any[]
|
||||
? Options[number] extends infer O
|
||||
? O extends { name: Name }
|
||||
? O
|
||||
: never
|
||||
: never
|
||||
: never;
|
||||
type GetOption<Options, Name> = 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<Options extends readonly any[]> = {
|
||||
@ -32,11 +26,11 @@ export type ExtractArgs<Options extends readonly any[]> = {
|
||||
? OptionTypeMap[T]
|
||||
: OptionTypeMap[T] | undefined
|
||||
: never
|
||||
: never;
|
||||
};
|
||||
: never
|
||||
}
|
||||
|
||||
// Handler function type that accepts typed arguments
|
||||
type HandlerFunction<Args = {}> = (args: Args) => Promise<void> | void;
|
||||
type HandlerFunction<Args = {}> = (args: Args) => Promise<void> | void
|
||||
|
||||
// Get subcommand by name
|
||||
type GetSubcommand<Options, Name> = Options extends readonly any[]
|
||||
@ -45,21 +39,17 @@ type GetSubcommand<Options, Name> = Options extends readonly any[]
|
||||
? O
|
||||
: never
|
||||
: never
|
||||
: never;
|
||||
: never
|
||||
|
||||
// Check if all options are subcommands
|
||||
type HasOnlySubcommands<Options extends readonly any[]> =
|
||||
Options[number] extends { type: ApplicationCommandOptionTypes.SubCommand }
|
||||
? true
|
||||
: false;
|
||||
type HasOnlySubcommands<Options extends readonly any[]> = Options[number] extends { type: ApplicationCommandOptionTypes.SubCommand } ? true : false
|
||||
|
||||
// Extract subcommand names from options
|
||||
type SubcommandNames<Options extends readonly any[]> =
|
||||
Options[number] extends { name: infer N; type: ApplicationCommandOptionTypes.SubCommand }
|
||||
type SubcommandNames<Options extends readonly any[]> = Options[number] extends { name: infer N; type: ApplicationCommandOptionTypes.SubCommand }
|
||||
? N extends string
|
||||
? N
|
||||
: never
|
||||
: never;
|
||||
: never
|
||||
|
||||
// Type to extract subcommand handlers
|
||||
export type SubcommandHandlers<Options extends readonly any[]> = {
|
||||
@ -68,14 +58,10 @@ export type SubcommandHandlers<Options extends readonly any[]> = {
|
||||
? HandlerFunction<ExtractArgs<SubOpts>>
|
||||
: HandlerFunction<{}>
|
||||
: HandlerFunction<{}>
|
||||
};
|
||||
}
|
||||
|
||||
// Get command by name from array
|
||||
type GetCommand<Commands extends readonly any[], Name> = Commands[number] extends infer C
|
||||
? C extends { name: Name }
|
||||
? C
|
||||
: never
|
||||
: never;
|
||||
type GetCommand<Commands extends readonly any[], Name> = Commands[number] extends infer C ? (C extends { name: Name } ? C : never) : never
|
||||
|
||||
// Main type to extract command handlers from slash commands
|
||||
export type ExtractCommands<T extends readonly any[]> = {
|
||||
@ -86,76 +72,75 @@ export type ExtractCommands<T extends readonly any[]> = {
|
||||
: HandlerFunction<ExtractArgs<Options>>
|
||||
: HandlerFunction<{}>
|
||||
: HandlerFunction<{}>
|
||||
};
|
||||
}
|
||||
|
||||
// The actual command handler type based on SLASH_COMMANDS
|
||||
export type CommandHandlers = ExtractCommands<typeof SLASH_COMMANDS>;
|
||||
export type CommandHandlers = ExtractCommands<typeof SLASH_COMMANDS>
|
||||
|
||||
// Helper function to parse option values from interaction data
|
||||
function parseOptions(options?: DiscordInteractionDataOption[]): Record<string, any> {
|
||||
if (!options) return {};
|
||||
if (!options) return {}
|
||||
|
||||
const args: Record<string, any> = {};
|
||||
const args: Record<string, any> = {}
|
||||
|
||||
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<T extends readonly CreateApplicationCommand[]>(
|
||||
{handler, notFoundHandler}:{
|
||||
export function createCommandHandler<T extends readonly CreateApplicationCommand[]>({
|
||||
handler,
|
||||
notFoundHandler,
|
||||
}: {
|
||||
handler: ExtractCommands<T>
|
||||
notFoundHandler: HandlerFunction<{}>
|
||||
}) {
|
||||
return async (data: InteractionData): Promise<void> => {
|
||||
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<any>)(args);
|
||||
const args = parseOptions(subcommand.options)
|
||||
await (subHandler as HandlerFunction<any>)(args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
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 {
|
||||
export const events = () => {
|
||||
return {
|
||||
interactionCreate: async (interaction) => {
|
||||
if (interaction.acknowledged) {
|
||||
return
|
||||
@ -14,11 +15,12 @@ export const events = () => {return {
|
||||
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: [{
|
||||
args: [
|
||||
{
|
||||
ref: {
|
||||
id: interaction.id,
|
||||
token: interaction.token,
|
||||
@ -26,13 +28,14 @@ export const events = () => {return {
|
||||
acknowledged: interaction.acknowledged,
|
||||
},
|
||||
data: interaction.data,
|
||||
}],
|
||||
},
|
||||
],
|
||||
workflowId: `discord-interaction-${interaction.id}`,
|
||||
taskQueue: 'wynn-worker-ts',
|
||||
});
|
||||
})
|
||||
|
||||
// Wait for the workflow to complete
|
||||
await handle.result();
|
||||
await handle.result()
|
||||
|
||||
return
|
||||
},
|
||||
@ -50,5 +53,6 @@ export const events = () => {return {
|
||||
},
|
||||
],
|
||||
})
|
||||
},
|
||||
} as BotType['events']
|
||||
}
|
||||
} as BotType['events']}
|
||||
|
||||
@ -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[]
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import {BotType} from "#/discord"
|
||||
|
||||
import type { BotType } from '#/discord'
|
||||
|
||||
export type BotEventsType = BotType['events']
|
||||
export type InteractionHandler = NonNullable<BotType['events']['interactionCreate']>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
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,
|
||||
@ -39,29 +39,28 @@ export const createBotParameters = {
|
||||
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<CompleteDesiredProperties<ExtractedDesiredProperties>, DesiredPropertiesBehavior.RemoveKey>;
|
||||
|
||||
export type BotType = Bot<CompleteDesiredProperties<ExtractedDesiredProperties>, 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<NonNullable<BotType['events']['interactionCreate']>>[0]['data'];
|
||||
export type InteractionData = Parameters<NonNullable<BotType['events']['interactionCreate']>>[0]['data']
|
||||
|
||||
// Type for the complete interaction handling payload
|
||||
export interface InteractionCreatePayload {
|
||||
ref: InteractionRef;
|
||||
data: InteractionData;
|
||||
ref: InteractionRef
|
||||
data: InteractionData
|
||||
}
|
||||
|
||||
@ -1,9 +1,5 @@
|
||||
import { c } from "#/di";
|
||||
|
||||
|
||||
import { c } from '#/di'
|
||||
|
||||
export class EventMux {
|
||||
constructor() {
|
||||
constructor() {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
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<string> {
|
||||
const hash = xxhash128(data)
|
||||
return hash
|
||||
|
||||
@ -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(() => []);
|
||||
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}`);
|
||||
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]);
|
||||
this.columns[i].push(row[i])
|
||||
}
|
||||
}
|
||||
|
||||
build() {
|
||||
let out = ""
|
||||
let out = ''
|
||||
if (this.columns.length == 0) {
|
||||
return "";
|
||||
return ''
|
||||
}
|
||||
const columnWidths = this.columns.map(col => col.reduce((a, b) => Math.max(a, b.length+this.spacing), 0));
|
||||
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";
|
||||
if (i > 0) out += '\n'
|
||||
for (let j = 0; j < this.columns.length; j++) {
|
||||
out+= this.columns[j][i].padEnd(columnWidths[j]);
|
||||
out += this.columns[j][i].padEnd(columnWidths[j])
|
||||
}
|
||||
}
|
||||
return out;
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
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()
|
||||
}
|
||||
@ -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,235 +31,226 @@ 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)
|
||||
export const WApiV3Item = WapiV3ItemMaterial.or(WapiV3ItemWeapon)
|
||||
.or(WapiV3ItemArmour)
|
||||
.or(WapiV3ItemIngredient)
|
||||
.or(WapiV3ItemAccessory)
|
||||
@ -268,5 +259,5 @@ export const WApiV3Item = WapiV3ItemMaterial
|
||||
.or(WapiV3ItemTool)
|
||||
|
||||
export const WApiV3ItemDatabase = type({
|
||||
"[string]": WApiV3Item
|
||||
'[string]': WApiV3Item,
|
||||
})
|
||||
|
||||
@ -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([
|
||||
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([
|
||||
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,9 +108,7 @@ 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({
|
||||
@ -123,15 +120,7 @@ export const WapiV3ItemTool = z.object({
|
||||
|
||||
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(),
|
||||
@ -157,13 +146,12 @@ export const WapiV3ItemCharm = z.object({
|
||||
rarity: WynnItemRarity,
|
||||
})
|
||||
|
||||
export const WapiV3ItemAccessory = z.object({
|
||||
export const WapiV3ItemAccessory = z
|
||||
.object({
|
||||
internalName: z.string(),
|
||||
type: z.literal('accessory'),
|
||||
identified: z.boolean().optional(),
|
||||
accessoryType: z.enum([
|
||||
"ring","necklace","bracelet",
|
||||
]),
|
||||
accessoryType: z.enum(['ring', 'necklace', 'bracelet']),
|
||||
majorIds: z.record(z.string(), z.string()).optional(),
|
||||
restrictions: WynnItemRestrictions.optional(),
|
||||
dropMeta: WynnDropMeta.optional(),
|
||||
@ -174,23 +162,16 @@ export const WapiV3ItemAccessory = z.object({
|
||||
identifications: WynnIdentifications.optional(),
|
||||
base: WynnBaseStats.optional(),
|
||||
rarity: WynnItemRarity,
|
||||
}).strict()
|
||||
})
|
||||
.strict()
|
||||
|
||||
export const WapiV3ItemIngredient = z.object({
|
||||
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",
|
||||
])),
|
||||
skills: z.array(z.enum(['alchemism', 'armouring', 'cooking', 'jeweling', 'scribing', 'tailoring', 'weaponsmithing', 'woodworking'])),
|
||||
}),
|
||||
icon: WynnItemIcon,
|
||||
identifications: WynnIdentifications.optional(),
|
||||
@ -198,43 +179,58 @@ export const WapiV3ItemIngredient = z.object({
|
||||
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({
|
||||
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()
|
||||
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({
|
||||
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'])),
|
||||
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()
|
||||
})
|
||||
.strict()
|
||||
|
||||
export const WapiV3ItemWeapon = z.object({
|
||||
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"
|
||||
]),
|
||||
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(),
|
||||
@ -247,10 +243,11 @@ export const WapiV3ItemWeapon = z.object({
|
||||
identifications: WynnIdentifications.optional(),
|
||||
base: WynnBaseStats.optional(),
|
||||
rarity: WynnItemRarity,
|
||||
}).strict()
|
||||
})
|
||||
.strict()
|
||||
|
||||
|
||||
export const WapiV3ItemArmour = z.object({
|
||||
export const WapiV3ItemArmour = z
|
||||
.object({
|
||||
internalName: z.string(),
|
||||
type: z.literal('armour'),
|
||||
armourType: z.string(),
|
||||
@ -274,19 +271,25 @@ export const WapiV3ItemArmour = z.object({
|
||||
majorIds: z.record(z.string(), z.string()).optional(),
|
||||
powderSlots: z.number().optional(),
|
||||
lore: z.string().optional(),
|
||||
identifications: z.record(z.string(), z.union([
|
||||
identifications: z
|
||||
.record(
|
||||
z.string(),
|
||||
z.union([
|
||||
z.number(),
|
||||
z.object({
|
||||
min: z.number(),
|
||||
raw: z.number(),
|
||||
max: z.number(),
|
||||
})
|
||||
])).optional(),
|
||||
}),
|
||||
])
|
||||
)
|
||||
.optional(),
|
||||
base: WynnBaseStats.optional(),
|
||||
rarity: WynnItemRarity,
|
||||
}).strict()
|
||||
})
|
||||
.strict()
|
||||
|
||||
export const WApiV3Item = z.discriminatedUnion("type",[
|
||||
export const WApiV3Item = z.discriminatedUnion('type', [
|
||||
WapiV3ItemMaterial,
|
||||
WapiV3ItemWeapon,
|
||||
WapiV3ItemArmour,
|
||||
|
||||
@ -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,18 +13,14 @@ 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,
|
||||
@ -33,7 +28,7 @@ export class WApi {
|
||||
async find(key, currentRequest) {
|
||||
const value = await store.get({ key })
|
||||
if (!value) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
return JSON.parse(value)
|
||||
},
|
||||
@ -41,18 +36,13 @@ export class WApi {
|
||||
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)
|
||||
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!
|
||||
(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
|
||||
@ -70,20 +60,18 @@ export class WApi {
|
||||
async clear() {
|
||||
await store.clear({})
|
||||
},
|
||||
}),
|
||||
})
|
||||
});
|
||||
this.c = c;
|
||||
this.c = c
|
||||
}
|
||||
|
||||
async get(path: string, params?: any) {
|
||||
return this.c.get(path, {
|
||||
params,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { pino } from 'pino'
|
||||
|
||||
|
||||
export const logger = pino({
|
||||
transport: {
|
||||
target: 'pino-logfmt',
|
||||
@ -8,4 +7,4 @@ export const logger = pino({
|
||||
level: process.env.PINO_LOG_LEVEL || 'info',
|
||||
|
||||
redact: [], // prevent logging of sensitive data
|
||||
});
|
||||
})
|
||||
|
||||
@ -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])
|
||||
|
||||
@ -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,
|
||||
`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
|
||||
)
|
||||
}`,
|
||||
e as Error,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
@ -41,21 +31,21 @@ export class SuperJsonPayloadConverter implements PayloadConverterWithEncoding {
|
||||
format: encode('extended'),
|
||||
},
|
||||
data: encode(ejson),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public fromPayload<T>(content: Payload): T {
|
||||
return content.data ? superjson.parse<T>(decode(content.data)) : {} as T;
|
||||
return content.data ? superjson.parse<T>(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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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({
|
||||
defaultStore.useL2Layer(
|
||||
redisDriver({
|
||||
connection: new IORedis(config.REDIS_URL),
|
||||
prefix: 'wynn-bento',
|
||||
}))
|
||||
})
|
||||
)
|
||||
}
|
||||
const bento = new BentoCache({
|
||||
default: 'cache',
|
||||
stores: {
|
||||
cache: defaultStore,
|
||||
}
|
||||
},
|
||||
})
|
||||
return bento
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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,9 +13,9 @@ export class PG {
|
||||
const opts = {
|
||||
onnotice: () => {},
|
||||
}
|
||||
let db: Sql;
|
||||
let db: Sql
|
||||
if (config.PG_URL) {
|
||||
db = postgres(config.PG_URL, opts);
|
||||
db = postgres(config.PG_URL, opts)
|
||||
} else {
|
||||
db = postgres({
|
||||
host: config.PG_HOST,
|
||||
@ -30,4 +30,3 @@ export class PG {
|
||||
this.db = db
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
@ -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<typeof activities>({
|
||||
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<typeof SLASH_COMMANDS>({
|
||||
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,21 +76,19 @@ 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,
|
||||
) => {
|
||||
export const workflowHandleInteractionCreate = async (payload: InteractionCreatePayload) => {
|
||||
const { ref, data } = payload
|
||||
|
||||
if (ref.type === InteractionTypes.ApplicationCommand) {
|
||||
|
||||
@ -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<typeof activities>({
|
||||
const { formGuildInfoMessage, formGuildOnlineMessage, formGuildLeaderboardMessage, reply_to_interaction } = proxyActivities<typeof activities>({
|
||||
startToCloseTimeout: '30 seconds',
|
||||
});
|
||||
})
|
||||
|
||||
interface CommandPayload {
|
||||
ref: InteractionRef;
|
||||
ref: InteractionRef
|
||||
}
|
||||
|
||||
export async function handleCommandGuildInfo(payload: CommandPayload): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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,
|
||||
});
|
||||
})
|
||||
}
|
||||
@ -1,10 +1,9 @@
|
||||
|
||||
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<typeof activities>({
|
||||
startToCloseTimeout: '1 minute',
|
||||
});
|
||||
})
|
||||
|
||||
export const workflowSyncAllGuilds = async () => {
|
||||
await update_all_guilds()
|
||||
@ -16,9 +15,7 @@ export const workflowSyncGuildLeaderboardInfo = async() => {
|
||||
|
||||
export const workflowSyncGuilds = async () => {
|
||||
// TODO side effect
|
||||
const guildNames = [
|
||||
'less than three',
|
||||
]
|
||||
const guildNames = ['less than three']
|
||||
for (const guildName of guildNames) {
|
||||
// update the guild
|
||||
await update_guild({
|
||||
@ -26,4 +23,3 @@ export const workflowSyncGuilds = async() => {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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<typeof activities>({
|
||||
startToCloseTimeout: '1 minute',
|
||||
retry: {
|
||||
maximumAttempts: 1,
|
||||
}
|
||||
});
|
||||
},
|
||||
})
|
||||
|
||||
export const workflowSyncItemDatabase = async () => {
|
||||
const {found_new} = await update_wynn_items();
|
||||
|
||||
const { found_new } = await update_wynn_items()
|
||||
}
|
||||
|
||||
@ -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<typeof activities>({
|
||||
const { reply_to_interaction } = proxyActivities<typeof activities>({
|
||||
startToCloseTimeout: '30 seconds',
|
||||
});
|
||||
})
|
||||
|
||||
interface CommandPayload {
|
||||
ref: InteractionRef;
|
||||
ref: InteractionRef
|
||||
args: {
|
||||
player: string;
|
||||
};
|
||||
player: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleCommandPlayerLookup(payload: CommandPayload): Promise<void> {
|
||||
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,
|
||||
},
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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<typeof activities>({
|
||||
startToCloseTimeout: '1 minute',
|
||||
retry: {
|
||||
maximumAttempts: 1,
|
||||
}
|
||||
});
|
||||
},
|
||||
})
|
||||
|
||||
export const workflowSyncOnline = async () => {
|
||||
await scrape_online_players();
|
||||
await scrape_online_players()
|
||||
}
|
||||
|
||||
116
ts/yarn.lock
116
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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user