noot
This commit is contained in:
parent
6c26594bc6
commit
53f46934e8
Binary file not shown.
@ -1,10 +1,5 @@
|
|||||||
{
|
{
|
||||||
"delete": true,
|
"delete": true,
|
||||||
"directory": [
|
"directory": ["./src/activities", "./src/workflows"],
|
||||||
"./src/activities",
|
"exclude": ["types.ts"]
|
||||||
"./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"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "1.9.4",
|
||||||
"@types/node": "^22.13.4",
|
"@types/node": "^22.13.4",
|
||||||
"@types/object-hash": "^3",
|
"@types/object-hash": "^3",
|
||||||
"@vitest/runner": "^3.2.3",
|
"@vitest/runner": "^3.2.3",
|
||||||
@ -29,8 +30,8 @@
|
|||||||
"@temporalio/common": "^1.11.7",
|
"@temporalio/common": "^1.11.7",
|
||||||
"@temporalio/worker": "^1.11.7",
|
"@temporalio/worker": "^1.11.7",
|
||||||
"@temporalio/workflow": "^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/core": "^3.53.0-rc.1",
|
||||||
"@ts-rest/fastify": "https://gitpkg.vercel.app/aidant/ts-rest/libs/ts-rest/fastify?feat-standard-schema",
|
"@ts-rest/fastify": "^3.53.0-rc.1",
|
||||||
"any-date-parser": "^2.0.3",
|
"any-date-parser": "^2.0.3",
|
||||||
"arktype": "2.1.1",
|
"arktype": "2.1.1",
|
||||||
"axios": "^1.7.9",
|
"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 tupleArrayType = tupleType.array()
|
||||||
const unionType = tupleType.or(tupleArrayType)
|
const unionType = tupleType.or(tupleArrayType)
|
||||||
|
|
||||||
// good
|
// good
|
||||||
tupleType.assert([1,"2"])
|
tupleType.assert([1, '2'])
|
||||||
// good
|
// good
|
||||||
tupleArrayType.assert([[1,"2"]])
|
tupleArrayType.assert([[1, '2']])
|
||||||
|
|
||||||
// no good!
|
// no good!
|
||||||
const resp = unionType([[1,"2"]])
|
const resp = unionType([[1, '2']])
|
||||||
if (resp instanceof ArkErrors) {
|
if (resp instanceof ArkErrors) {
|
||||||
const err = resp[0]
|
const err = resp[0]
|
||||||
console.log(err.data)
|
console.log(err.data)
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import stringify from 'json-stable-stringify';
|
import { log } from '@temporalio/activity'
|
||||||
import { c } from "#/di";
|
import { ArkErrors } from 'arktype'
|
||||||
import { WApiV3ItemDatabase } from "#/lib/wynn/types";
|
import stringify from 'json-stable-stringify'
|
||||||
import { WApi } from "#/lib/wynn/wapi";
|
import { c } from '#/di'
|
||||||
import { PG } from "#/services/pg";
|
import { sha1Hash } from '#/lib/util/hashers'
|
||||||
import { ArkErrors } from "arktype";
|
import { WApiV3ItemDatabase } from '#/lib/wynn/types'
|
||||||
import { sha1Hash } from '#/lib/util/hashers';
|
import { WApi } from '#/lib/wynn/wapi'
|
||||||
import { log } from '@temporalio/activity';
|
import { PG } from '#/services/pg'
|
||||||
|
|
||||||
export async function update_wynn_items() {
|
export async function update_wynn_items() {
|
||||||
const api = await c.getAsync(WApi)
|
const api = await c.getAsync(WApi)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Bot } from "#/discord/bot";
|
import { type InteractionCallbackData, type InteractionCallbackOptions, InteractionResponseTypes, InteractionTypes, MessageFlags } from 'discordeno'
|
||||||
import {c} from "#/di"
|
import { c } from '#/di'
|
||||||
import { InteractionResponseTypes, InteractionCallbackOptions, InteractionCallbackData, InteractionTypes, MessageFlags } from "discordeno";
|
import type { InteractionRef } from '#/discord'
|
||||||
import { 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
|
// from https://github.com/discordeno/discordeno/blob/21.0.0/packages/bot/src/transformers/interaction.ts#L33
|
||||||
export const reply_to_interaction = async (props: {
|
export const reply_to_interaction = async (props: {
|
||||||
@ -9,24 +9,19 @@ export const reply_to_interaction = async (props: {
|
|||||||
type: number
|
type: number
|
||||||
options: InteractionCallbackOptions & { isPrivate?: boolean; content?: string }
|
options: InteractionCallbackOptions & { isPrivate?: boolean; content?: string }
|
||||||
}) => {
|
}) => {
|
||||||
const bot = await c.getAsync(Bot);
|
const bot = await c.getAsync(Bot)
|
||||||
|
|
||||||
const {
|
const { ref, type, options } = props
|
||||||
ref,
|
|
||||||
type,
|
|
||||||
options,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
let data: InteractionCallbackData = options;
|
const data: InteractionCallbackData = options
|
||||||
|
|
||||||
if (options?.isPrivate) {
|
if (options?.isPrivate) {
|
||||||
data.flags = MessageFlags.Ephemeral;
|
data.flags = MessageFlags.Ephemeral
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ref.acknowledged) {
|
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,
|
return await bot.helpers.sendInteractionResponse(ref.id, ref.token, { type, data }, { withResponse: options?.withResponse })
|
||||||
{ type, data }, { withResponse: options?.withResponse })
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { c } from "#/di";
|
import { type } from 'arktype'
|
||||||
import { WapiV3GuildOverview } from "#/lib/wynn/types";
|
import { parseDate } from 'chrono-node'
|
||||||
import { WApi } from "#/lib/wynn/wapi";
|
import { c } from '#/di'
|
||||||
import { PG } from "#/services/pg";
|
import { WapiV3GuildOverview } from '#/lib/wynn/types'
|
||||||
import { type } from "arktype";
|
import { WApi } from '#/lib/wynn/wapi'
|
||||||
import {parseDate} from "chrono-node";
|
import { PG } from '#/services/pg'
|
||||||
|
|
||||||
export async function update_all_guilds() {
|
export async function update_all_guilds() {
|
||||||
const api = await c.getAsync(WApi)
|
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')
|
throw new Error('Failed to get guild list from wapi')
|
||||||
}
|
}
|
||||||
const parsed = type({
|
const parsed = type({
|
||||||
"[string]": {
|
'[string]': {
|
||||||
uuid: "string",
|
uuid: 'string',
|
||||||
prefix: "string",
|
prefix: 'string',
|
||||||
}
|
},
|
||||||
}).assert(ans.data)
|
}).assert(ans.data)
|
||||||
|
|
||||||
const { sql } = await c.getAsync(PG)
|
const { sql } = await c.getAsync(PG)
|
||||||
@ -32,7 +32,7 @@ export async function update_all_guilds() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function update_guild({
|
export async function update_guild({
|
||||||
guild_name
|
guild_name,
|
||||||
}: {
|
}: {
|
||||||
guild_name: string
|
guild_name: string
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { c } from "#/di";
|
import { type } from 'arktype'
|
||||||
import { PG } from "#/services/pg";
|
import type { CreateMessageOptions, InteractionCallbackOptions } from 'discordeno'
|
||||||
import { CreateMessageOptions, InteractionCallbackOptions } from "discordeno";
|
import * as md from 'ts-markdown-builder'
|
||||||
import { type } from "arktype";
|
import { c } from '#/di'
|
||||||
import { TabWriter } from "#/lib/util/tabwriter";
|
import { TabWriter } from '#/lib/util/tabwriter'
|
||||||
import { RANK_EMOJIS, getRankEmoji, formatNumber } from "#/lib/util/wynnfmt";
|
import { RANK_EMOJIS, formatNumber, getRankEmoji } from '#/lib/util/wynnfmt'
|
||||||
import * as md from 'ts-markdown-builder';
|
import { PG } from '#/services/pg'
|
||||||
|
|
||||||
export async function formGuildInfoMessage(guild_id: string): Promise<CreateMessageOptions & InteractionCallbackOptions> {
|
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`
|
const result = await sql`
|
||||||
with ranked as (
|
with ranked as (
|
||||||
@ -25,15 +25,15 @@ with ranked as (
|
|||||||
)
|
)
|
||||||
select * from ranked
|
select * from ranked
|
||||||
where ranked.uid = ${guild_id}
|
where ranked.uid = ${guild_id}
|
||||||
`;
|
`
|
||||||
|
|
||||||
if (result.length == 0) {
|
if (result.length == 0) {
|
||||||
return {
|
return {
|
||||||
content: "No guild found.",
|
content: 'No guild found.',
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const guild = result[0];
|
const guild = result[0]
|
||||||
|
|
||||||
const output = [
|
const output = [
|
||||||
`# 🏰 Guild Information`,
|
`# 🏰 Guild Information`,
|
||||||
@ -41,18 +41,18 @@ where ranked.uid = ${guild_id}
|
|||||||
`### 📊 Statistics`,
|
`### 📊 Statistics`,
|
||||||
`> **Level:** \`${guild.level}\``,
|
`> **Level:** \`${guild.level}\``,
|
||||||
`> **Total XP:** \`${formatNumber(guild.xp)}\``,
|
`> **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}\``,
|
`> **Territories:** \`${guild.territories}\``,
|
||||||
`> **Wars:** \`${guild.wars.toLocaleString()}\``,
|
`> **Wars:** \`${guild.wars.toLocaleString()}\``,
|
||||||
].join("\n");
|
].join('\n')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: output,
|
content: output,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function formGuildOnlineMessage(guild_id: string): Promise<CreateMessageOptions & InteractionCallbackOptions> {
|
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
|
const result = await sql`select
|
||||||
gi.name as guild_name,
|
gi.name as guild_name,
|
||||||
@ -68,67 +68,73 @@ export async function formGuildOnlineMessage(guild_id: string): Promise<CreateMe
|
|||||||
on gi.uid = gm.guild_id
|
on gi.uid = gm.guild_id
|
||||||
where minecraft.user.server is not null
|
where minecraft.user.server is not null
|
||||||
and gm.guild_id = ${guild_id}
|
and gm.guild_id = ${guild_id}
|
||||||
`;
|
`
|
||||||
|
|
||||||
const members = type({
|
const members = type({
|
||||||
guild_name: "string",
|
guild_name: 'string',
|
||||||
guild_prefix: "string",
|
guild_prefix: 'string',
|
||||||
name: "string",
|
name: 'string',
|
||||||
rank: "string",
|
rank: 'string',
|
||||||
contributed: "string",
|
contributed: 'string',
|
||||||
server: "string",
|
server: 'string',
|
||||||
}).array().assert(result);
|
})
|
||||||
|
.array()
|
||||||
|
.assert(result)
|
||||||
|
|
||||||
if (members.length == 0) {
|
if (members.length == 0) {
|
||||||
return {
|
return {
|
||||||
content: "😴 No guild members are currently online.",
|
content: '😴 No guild members are currently online.',
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get guild info
|
// Get guild info
|
||||||
const guildName = members[0].guild_name;
|
const guildName = members[0].guild_name
|
||||||
const guildPrefix = members[0].guild_prefix;
|
const guildPrefix = members[0].guild_prefix
|
||||||
|
|
||||||
// Sort by contribution
|
// 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
|
// Group members by server
|
||||||
const membersByServer = members.reduce((acc, member) => {
|
const membersByServer = members.reduce(
|
||||||
|
(acc, member) => {
|
||||||
if (acc[member.server] == undefined) {
|
if (acc[member.server] == undefined) {
|
||||||
acc[member.server] = [];
|
acc[member.server] = []
|
||||||
}
|
}
|
||||||
acc[member.server].push(member);
|
acc[member.server].push(member)
|
||||||
return acc;
|
return acc
|
||||||
}, {} as Record<string, typeof members>);
|
},
|
||||||
|
{} as Record<string, typeof members>
|
||||||
|
)
|
||||||
|
|
||||||
// Sort servers by player count
|
// Sort servers by player count
|
||||||
const sortedServers = Object.entries(membersByServer)
|
const sortedServers = Object.entries(membersByServer).sort(([, a], [, b]) => b.length - a.length)
|
||||||
.sort(([, a], [, b]) => b.length - a.length);
|
|
||||||
|
|
||||||
// Build server sections
|
// Build server sections
|
||||||
const serverSections = sortedServers.map(([server, serverMembers]) => {
|
const serverSections = sortedServers.map(([server, serverMembers]) => {
|
||||||
const memberList = serverMembers.map(m => {
|
const memberList = serverMembers
|
||||||
const emoji = getRankEmoji(m.rank);
|
.map((m) => {
|
||||||
return `${emoji} ${m.name}`;
|
const emoji = getRankEmoji(m.rank)
|
||||||
}).join(", ");
|
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 = [
|
const output = [
|
||||||
`# 🟢 Online Guild Members`,
|
`# 🟢 Online Guild Members`,
|
||||||
`**[${guildPrefix}] ${guildName}**\n`,
|
`**[${guildPrefix}] ${guildName}**\n`,
|
||||||
`📊 **Total Online:** \`${members.length}\` members across \`${sortedServers.length}\` servers\n`,
|
`📊 **Total Online:** \`${members.length}\` members across \`${sortedServers.length}\` servers\n`,
|
||||||
...serverSections
|
...serverSections,
|
||||||
].join("\n");
|
].join('\n')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: output,
|
content: output,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function formGuildLeaderboardMessage(guild_id: string): Promise<CreateMessageOptions & InteractionCallbackOptions> {
|
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
|
const result = await sql`select
|
||||||
gi.name as guild_name,
|
gi.name as guild_name,
|
||||||
@ -142,65 +148,59 @@ export async function formGuildLeaderboardMessage(guild_id: string): Promise<Cre
|
|||||||
inner join wynn.guild_info gi
|
inner join wynn.guild_info gi
|
||||||
on gi.uid = gm.guild_id
|
on gi.uid = gm.guild_id
|
||||||
where gm.guild_id = ${guild_id}
|
where gm.guild_id = ${guild_id}
|
||||||
`;
|
`
|
||||||
|
|
||||||
const members = type({
|
const members = type({
|
||||||
guild_name: "string",
|
guild_name: 'string',
|
||||||
guild_prefix: "string",
|
guild_prefix: 'string',
|
||||||
name: "string",
|
name: 'string',
|
||||||
rank: "string",
|
rank: 'string',
|
||||||
contributed: "string",
|
contributed: 'string',
|
||||||
}).array().assert(result);
|
})
|
||||||
|
.array()
|
||||||
|
.assert(result)
|
||||||
|
|
||||||
if (members.length === 0) {
|
if (members.length === 0) {
|
||||||
return {
|
return {
|
||||||
content: "No guild members found.",
|
content: 'No guild members found.',
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by contribution
|
// Sort by contribution
|
||||||
members.sort((a, b) => Number(b.contributed) - Number(a.contributed));
|
members.sort((a, b) => Number(b.contributed) - Number(a.contributed))
|
||||||
const topMembers = members.slice(0, 10);
|
const topMembers = members.slice(0, 10)
|
||||||
|
|
||||||
// Get guild info from first member (all have same guild info)
|
// Get guild info from first member (all have same guild info)
|
||||||
const guildName = members[0].guild_name;
|
const guildName = members[0].guild_name
|
||||||
const guildPrefix = members[0].guild_prefix;
|
const guildPrefix = members[0].guild_prefix
|
||||||
|
|
||||||
// Calculate total guild XP
|
// 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
|
// Build the leaderboard with proper alignment
|
||||||
const tw = new TabWriter(2);
|
const tw = new TabWriter(2)
|
||||||
|
|
||||||
// Add header row
|
// Add header row
|
||||||
tw.add(["#", "Rank", "Player", "XP", "%"]);
|
tw.add(['#', 'Rank', 'Player', 'XP', '%'])
|
||||||
tw.add(["───", "────────────", "────────────────", "──────────", "──────"]); // Separator line
|
tw.add(['───', '────────────', '────────────────', '──────────', '──────']) // Separator line
|
||||||
|
|
||||||
topMembers.forEach((member, index) => {
|
topMembers.forEach((member, index) => {
|
||||||
const position = index + 1;
|
const position = index + 1
|
||||||
const posStr = position === 1 ? "🥇" : position === 2 ? "🥈" : position === 3 ? "🥉" : `${position}.`;
|
const posStr = position === 1 ? '🥇' : position === 2 ? '🥈' : position === 3 ? '🥉' : `${position}.`
|
||||||
const rankEmoji = getRankEmoji(member.rank);
|
const rankEmoji = getRankEmoji(member.rank)
|
||||||
const contribution = Number(member.contributed);
|
const contribution = Number(member.contributed)
|
||||||
const percentage = ((contribution / totalXP) * 100).toFixed(1);
|
const percentage = ((contribution / totalXP) * 100).toFixed(1)
|
||||||
|
|
||||||
// Use formatNumber for consistent formatting
|
// Use formatNumber for consistent formatting
|
||||||
const contribFormatted = contribution >= 10_000
|
const contribFormatted = contribution >= 10_000 ? formatNumber(contribution) : contribution.toLocaleString()
|
||||||
? formatNumber(contribution)
|
|
||||||
: contribution.toLocaleString();
|
|
||||||
|
|
||||||
tw.add([
|
tw.add([posStr, `${rankEmoji} ${member.rank}`, member.name, contribFormatted, `${percentage}%`])
|
||||||
posStr,
|
})
|
||||||
`${rankEmoji} ${member.rank}`,
|
|
||||||
member.name,
|
|
||||||
contribFormatted,
|
|
||||||
`${percentage}%`
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
const leaderboardTable = tw.build();
|
const leaderboardTable = tw.build()
|
||||||
|
|
||||||
// Create summary stats
|
// Create summary stats
|
||||||
const avgContribution = Math.floor(totalXP / members.length);
|
const avgContribution = Math.floor(totalXP / members.length)
|
||||||
|
|
||||||
const output = [
|
const output = [
|
||||||
`# 📊 Guild XP Leaderboard`,
|
`# 📊 Guild XP Leaderboard`,
|
||||||
@ -209,13 +209,13 @@ export async function formGuildLeaderboardMessage(guild_id: string): Promise<Cre
|
|||||||
`👥 **Total Members:** \`${members.length}\``,
|
`👥 **Total Members:** \`${members.length}\``,
|
||||||
`📊 **Average Contribution:** \`${avgContribution.toLocaleString()}\`\n`,
|
`📊 **Average Contribution:** \`${avgContribution.toLocaleString()}\`\n`,
|
||||||
`### Top Contributors`,
|
`### Top Contributors`,
|
||||||
"```",
|
'```',
|
||||||
leaderboardTable,
|
leaderboardTable,
|
||||||
"```",
|
'```',
|
||||||
`*Showing top ${Math.min(members.length, 10)} of ${members.length} members*`
|
`*Showing top ${Math.min(members.length, 10)} of ${members.length} members*`,
|
||||||
].join("\n");
|
].join('\n')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: output,
|
content: output,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
* @file Automatically generated by barrelsby.
|
* @file Automatically generated by barrelsby.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from "./database";
|
export * from './database'
|
||||||
export * from "./discord";
|
export * from './discord'
|
||||||
export * from "./guild";
|
export * from './guild'
|
||||||
export * from "./guild_messages";
|
export * from './guild_messages'
|
||||||
export * from "./leaderboards";
|
export * from './leaderboards'
|
||||||
export * from "./players";
|
export * from './players'
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { c } from "#/di";
|
import { type } from 'arktype'
|
||||||
import { WApi } from "#/lib/wynn/wapi";
|
import { c } from '#/di'
|
||||||
import { PG } from "#/services/pg";
|
import { WApi } from '#/lib/wynn/wapi'
|
||||||
import { type } from "arktype";
|
import { PG } from '#/services/pg'
|
||||||
|
|
||||||
export async function update_guild_levels() {
|
export async function update_guild_levels() {
|
||||||
const api = await c.getAsync(WApi)
|
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')
|
throw new Error('Failed to get guild list from wapi')
|
||||||
}
|
}
|
||||||
const parsed = type({
|
const parsed = type({
|
||||||
"[string]": {
|
'[string]': {
|
||||||
uuid: "string",
|
uuid: 'string',
|
||||||
name: "string",
|
name: 'string',
|
||||||
prefix: "string",
|
prefix: 'string',
|
||||||
xp: "number",
|
xp: 'number',
|
||||||
level: "number",
|
level: 'number',
|
||||||
}
|
},
|
||||||
}).assert(ans.data)
|
}).assert(ans.data)
|
||||||
const { sql } = await c.getAsync(PG)
|
const { sql } = await c.getAsync(PG)
|
||||||
for (const [_, guild] of Object.entries(parsed)) {
|
for (const [_, guild] of Object.entries(parsed)) {
|
||||||
|
|||||||
@ -1,72 +1,73 @@
|
|||||||
import { c } from "#/di"
|
import { log } from '@temporalio/activity'
|
||||||
import { WApi } from "#/lib/wynn/wapi"
|
import { type } from 'arktype'
|
||||||
import { PG } from "#/services/pg"
|
import axios from 'axios'
|
||||||
import { log } from "@temporalio/activity"
|
import { c } from '#/di'
|
||||||
import { type } from "arktype"
|
import { WApi } from '#/lib/wynn/wapi'
|
||||||
import axios from "axios"
|
import { PG } from '#/services/pg'
|
||||||
|
|
||||||
const playerSchemaFail = type({
|
const playerSchemaFail = type({
|
||||||
code: "string",
|
code: 'string',
|
||||||
message: "string",
|
message: 'string',
|
||||||
data: type({
|
data: type({
|
||||||
player: {
|
player: {
|
||||||
meta: {
|
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({
|
const playerSchemaSuccess = type({
|
||||||
code: "string",
|
code: 'string',
|
||||||
message: "string",
|
message: 'string',
|
||||||
data: type({
|
data: type({
|
||||||
player: {
|
player: {
|
||||||
meta: {
|
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)
|
const playerSchema = playerSchemaFail.or(playerSchemaSuccess)
|
||||||
|
|
||||||
|
|
||||||
export const scrape_online_players = async () => {
|
export const scrape_online_players = async () => {
|
||||||
|
|
||||||
const api = await c.getAsync(WApi)
|
const api = await c.getAsync(WApi)
|
||||||
const raw = await api.get('/v3/player')
|
const raw = await api.get('/v3/player')
|
||||||
const onlineList = type({
|
const onlineList = type({
|
||||||
total: "number",
|
total: 'number',
|
||||||
players: {
|
players: {
|
||||||
"[string]": "string | null",
|
'[string]': 'string | null',
|
||||||
}
|
},
|
||||||
}).assert(raw.data)
|
}).assert(raw.data)
|
||||||
|
|
||||||
|
|
||||||
const { sql } = await c.getAsync(PG)
|
const { sql } = await c.getAsync(PG)
|
||||||
|
|
||||||
for (const [playerName, server] of Object.entries(onlineList.players)) {
|
for (const [playerName, server] of Object.entries(onlineList.players)) {
|
||||||
@ -78,13 +79,13 @@ export const scrape_online_players = async()=>{
|
|||||||
try {
|
try {
|
||||||
const resp = await axios.get(`https://playerdb.co/api/player/minecraft/${playerName}`, {
|
const resp = await axios.get(`https://playerdb.co/api/player/minecraft/${playerName}`, {
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent": "lil-robot-guy (a@tuxpa.in)",
|
'User-Agent': 'lil-robot-guy (a@tuxpa.in)',
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
const parsedPlayer = playerSchema.assert(resp.data)
|
const parsedPlayer = playerSchema.assert(resp.data)
|
||||||
if (!parsedPlayer.success) {
|
if (!parsedPlayer.success) {
|
||||||
log.warn(`failed to get uuid for ${playerName}`, {
|
log.warn(`failed to get uuid for ${playerName}`, {
|
||||||
"payload": parsedPlayer,
|
payload: parsedPlayer,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -97,7 +98,7 @@ export const scrape_online_players = async()=>{
|
|||||||
`
|
`
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.warn(`failed to get uuid for ${playerName}`, {
|
log.warn(`failed to get uuid for ${playerName}`, {
|
||||||
"err": e,
|
err: e,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -110,7 +111,7 @@ export const scrape_online_players = async()=>{
|
|||||||
await sql`update minecraft.user set server = ${server} where name = ${playerName}`
|
await sql`update minecraft.user set server = ${server} where name = ${playerName}`
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.warn(`failed to update server for ${playerName}`, {
|
log.warn(`failed to update server for ${playerName}`, {
|
||||||
"err": e,
|
err: e,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,49 +1,54 @@
|
|||||||
import { initContract } from "@ts-rest/core/src";
|
import { initContract } from '@ts-rest/core'
|
||||||
import { type } from "arktype";
|
import { type } from 'arktype'
|
||||||
|
|
||||||
const con = initContract();
|
const con = initContract()
|
||||||
|
|
||||||
const ingameauth = con.router({
|
const ingameauth = con.router(
|
||||||
|
{
|
||||||
challenge: {
|
challenge: {
|
||||||
description: "generate a challenge for the client to solve",
|
description: 'generate a challenge for the client to solve',
|
||||||
method: "GET",
|
method: 'GET',
|
||||||
path: "/challenge",
|
path: '/challenge',
|
||||||
responses: {
|
responses: {
|
||||||
200: type({
|
200: type({
|
||||||
challenge: "string.uuid",
|
challenge: 'string.uuid',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
query: type({
|
query: type({
|
||||||
uuid: "string.uuid",
|
uuid: 'string.uuid',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
solve: {
|
solve: {
|
||||||
description: "attempt to solve the challenge and get the token for the challenge",
|
description: 'attempt to solve the challenge and get the token for the challenge',
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
path: "/solve",
|
path: '/solve',
|
||||||
body: type({
|
body: type({
|
||||||
challenge: "string.uuid",
|
challenge: 'string.uuid',
|
||||||
uuid: "string.uuid",
|
uuid: 'string.uuid',
|
||||||
}),
|
}),
|
||||||
responses: {
|
responses: {
|
||||||
200: type({
|
200: type({
|
||||||
success: "true",
|
success: 'true',
|
||||||
challenge: "string.uuid",
|
challenge: 'string.uuid',
|
||||||
uuid: "string.uuid",
|
uuid: 'string.uuid',
|
||||||
}),
|
}),
|
||||||
401: type({
|
401: type({
|
||||||
success: "false",
|
success: 'false',
|
||||||
reason: "string",
|
reason: 'string',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}, {pathPrefix: "/ingame"})
|
},
|
||||||
|
{ pathPrefix: '/ingame' }
|
||||||
|
)
|
||||||
|
|
||||||
export const api = con.router({
|
export const api = con.router(
|
||||||
"ingameauth": ingameauth,
|
{
|
||||||
}, {pathPrefix: "/api/v1"})
|
ingameauth: ingameauth,
|
||||||
|
},
|
||||||
|
{ pathPrefix: '/api/v1' }
|
||||||
|
)
|
||||||
|
|
||||||
export const contract = con.router({
|
export const contract = con.router({
|
||||||
api: api
|
api: api,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,36 +1,28 @@
|
|||||||
import { Command } from 'clipanion';
|
import { Command } from 'clipanion'
|
||||||
|
|
||||||
|
|
||||||
// di
|
// di
|
||||||
import "#/services/pg"
|
import '#/services/pg'
|
||||||
import { DISCORD_GUILD_ID } from '#/constants';
|
import { config } from '#/config'
|
||||||
import { Bot } from '#/discord/bot';
|
import { DISCORD_GUILD_ID } from '#/constants'
|
||||||
import { events } from '#/discord/botevent/handler';
|
import { c } from '#/di'
|
||||||
import { SLASH_COMMANDS } from '#/discord/botevent/slash_commands';
|
import { Bot } from '#/discord/bot'
|
||||||
import { c } from '#/di';
|
import { events } from '#/discord/botevent/handler'
|
||||||
import { config } from '#/config';
|
import { SLASH_COMMANDS } from '#/discord/botevent/slash_commands'
|
||||||
|
|
||||||
|
|
||||||
export class BotCommand extends Command {
|
export class BotCommand extends Command {
|
||||||
static paths = [['bot']];
|
static paths = [['bot']]
|
||||||
async execute() {
|
async execute() {
|
||||||
if (!config.DISCORD_TOKEN) {
|
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)
|
const bot = await c.getAsync(Bot)
|
||||||
bot.events = events()
|
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(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')
|
||||||
console.log('connecting bot to gateway');
|
await bot.start()
|
||||||
await bot.start();
|
console.log('bot connected')
|
||||||
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
|
// di
|
||||||
import "#/services/temporal"
|
import '#/services/temporal'
|
||||||
import { NativeConnection, Worker } from '@temporalio/worker';
|
import path from 'path'
|
||||||
import * as activities from '../activities';
|
import { Client, ScheduleNotFoundError, type ScheduleOptions, ScheduleOverlapPolicy } from '@temporalio/client'
|
||||||
import path from 'path';
|
import { NativeConnection, Worker } from '@temporalio/worker'
|
||||||
import { Client, ScheduleNotFoundError, ScheduleOptions, ScheduleOverlapPolicy } from '@temporalio/client';
|
import { PG } from '#/services/pg'
|
||||||
import { workflowSyncAllGuilds, workflowSyncGuilds, workflowSyncOnline, workflowSyncGuildLeaderboardInfo } from '#/workflows';
|
import { workflowSyncAllGuilds, workflowSyncGuildLeaderboardInfo, workflowSyncGuilds, workflowSyncOnline } from '#/workflows'
|
||||||
import { PG } from '#/services/pg';
|
import * as activities from '../activities'
|
||||||
|
|
||||||
|
|
||||||
import { config } from '#/config';
|
|
||||||
|
|
||||||
|
|
||||||
|
import { config } from '#/config'
|
||||||
|
|
||||||
const schedules: ScheduleOptions[] = [
|
const schedules: ScheduleOptions[] = [
|
||||||
{
|
{
|
||||||
scheduleId: "update-guild-players",
|
scheduleId: 'update-guild-players',
|
||||||
action: {
|
action: {
|
||||||
type: 'startWorkflow',
|
type: 'startWorkflow',
|
||||||
workflowType: workflowSyncGuilds,
|
workflowType: workflowSyncGuilds,
|
||||||
@ -28,13 +25,15 @@ const schedules: ScheduleOptions[] = [
|
|||||||
overlap: ScheduleOverlapPolicy.SKIP,
|
overlap: ScheduleOverlapPolicy.SKIP,
|
||||||
},
|
},
|
||||||
spec: {
|
spec: {
|
||||||
intervals: [{
|
intervals: [
|
||||||
|
{
|
||||||
every: '15 minutes',
|
every: '15 minutes',
|
||||||
}]
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scheduleId: "update_guild_leaderboards",
|
scheduleId: 'update_guild_leaderboards',
|
||||||
action: {
|
action: {
|
||||||
type: 'startWorkflow',
|
type: 'startWorkflow',
|
||||||
workflowType: workflowSyncGuildLeaderboardInfo,
|
workflowType: workflowSyncGuildLeaderboardInfo,
|
||||||
@ -44,13 +43,15 @@ const schedules: ScheduleOptions[] = [
|
|||||||
overlap: ScheduleOverlapPolicy.SKIP,
|
overlap: ScheduleOverlapPolicy.SKIP,
|
||||||
},
|
},
|
||||||
spec: {
|
spec: {
|
||||||
intervals: [{
|
intervals: [
|
||||||
|
{
|
||||||
every: '5 minutes',
|
every: '5 minutes',
|
||||||
}]
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scheduleId: "update-all-guilds",
|
scheduleId: 'update-all-guilds',
|
||||||
action: {
|
action: {
|
||||||
type: 'startWorkflow',
|
type: 'startWorkflow',
|
||||||
workflowType: workflowSyncAllGuilds,
|
workflowType: workflowSyncAllGuilds,
|
||||||
@ -60,13 +61,15 @@ const schedules: ScheduleOptions[] = [
|
|||||||
overlap: ScheduleOverlapPolicy.SKIP,
|
overlap: ScheduleOverlapPolicy.SKIP,
|
||||||
},
|
},
|
||||||
spec: {
|
spec: {
|
||||||
intervals: [{
|
intervals: [
|
||||||
|
{
|
||||||
every: '1 hour',
|
every: '1 hour',
|
||||||
}]
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scheduleId: "update-online-players",
|
scheduleId: 'update-online-players',
|
||||||
action: {
|
action: {
|
||||||
type: 'startWorkflow',
|
type: 'startWorkflow',
|
||||||
workflowType: workflowSyncOnline,
|
workflowType: workflowSyncOnline,
|
||||||
@ -76,9 +79,11 @@ const schedules: ScheduleOptions[] = [
|
|||||||
overlap: ScheduleOverlapPolicy.SKIP,
|
overlap: ScheduleOverlapPolicy.SKIP,
|
||||||
},
|
},
|
||||||
spec: {
|
spec: {
|
||||||
intervals: [{
|
intervals: [
|
||||||
|
{
|
||||||
every: '31 seconds',
|
every: '31 seconds',
|
||||||
}]
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -87,27 +92,26 @@ const addSchedules = async (c: Client) => {
|
|||||||
for (const o of schedules) {
|
for (const o of schedules) {
|
||||||
const handle = c.schedule.getHandle(o.scheduleId)
|
const handle = c.schedule.getHandle(o.scheduleId)
|
||||||
try {
|
try {
|
||||||
const desc = await handle.describe();
|
const desc = await handle.describe()
|
||||||
console.log(desc)
|
console.log(desc)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e instanceof ScheduleNotFoundError) {
|
if (e instanceof ScheduleNotFoundError) {
|
||||||
await c.schedule.create(o)
|
await c.schedule.create(o)
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class WorkerCommand extends Command {
|
export class WorkerCommand extends Command {
|
||||||
static paths = [['worker']];
|
static paths = [['worker']]
|
||||||
async execute() {
|
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
|
// schedules
|
||||||
await addSchedules(client);
|
await addSchedules(client)
|
||||||
|
|
||||||
const connection = await NativeConnection.connect({
|
const connection = await NativeConnection.connect({
|
||||||
address: config.TEMPORAL_HOSTPORT,
|
address: config.TEMPORAL_HOSTPORT,
|
||||||
@ -121,28 +125,23 @@ export class WorkerCommand extends Command {
|
|||||||
},
|
},
|
||||||
bundlerOptions: {
|
bundlerOptions: {
|
||||||
webpackConfigHook: (config) => {
|
webpackConfigHook: (config) => {
|
||||||
if(!config.resolve) config.resolve = {};
|
if (!config.resolve) config.resolve = {}
|
||||||
if(!config.resolve.alias) config.resolve.alias = {};
|
if (!config.resolve.alias) config.resolve.alias = {}
|
||||||
config.resolve!.alias = {
|
config.resolve!.alias = {
|
||||||
"#":path.resolve(process.cwd(),'src/'),
|
'#': path.resolve(process.cwd(), 'src/'),
|
||||||
...config.resolve!.alias,
|
...config.resolve!.alias,
|
||||||
}
|
}
|
||||||
return config;
|
return config
|
||||||
}},
|
},
|
||||||
|
},
|
||||||
taskQueue: 'wynn-worker-ts',
|
taskQueue: 'wynn-worker-ts',
|
||||||
stickyQueueScheduleToStartTimeout: 5 * 1000,
|
stickyQueueScheduleToStartTimeout: 5 * 1000,
|
||||||
activities
|
activities,
|
||||||
});
|
})
|
||||||
await worker.run();
|
await worker.run()
|
||||||
|
|
||||||
|
console.log('worked.run exited')
|
||||||
console.log("worked.run exited");
|
await db.end()
|
||||||
await db.end();
|
await connection.close()
|
||||||
await connection.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { z } from 'zod';
|
import { config as dotenvConfig } from 'dotenv'
|
||||||
import { parseEnv} from 'znv';
|
import { parseEnv } from 'znv'
|
||||||
import {config as dotenvConfig} from 'dotenv';
|
import { z } from 'zod'
|
||||||
dotenvConfig();
|
dotenvConfig()
|
||||||
|
|
||||||
const schemaConfig = {
|
const schemaConfig = {
|
||||||
DISCORD_TOKEN: z.string().optional(),
|
DISCORD_TOKEN: z.string().optional(),
|
||||||
@ -17,9 +17,8 @@ const schemaConfig = {
|
|||||||
PG_PORT: z.number().int().optional(),
|
PG_PORT: z.number().int().optional(),
|
||||||
PG_SSLMODE: z.string().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(),
|
REDIS_URL: z.string().optional(),
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
export const config = parseEnv(process.env, schemaConfig)
|
export const config = parseEnv(process.env, schemaConfig)
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
export const DISCORD_GUILD_ID = "1340213134949875835";
|
export const DISCORD_GUILD_ID = '1340213134949875835'
|
||||||
export const WYNN_GUILD_NAME = "less than three"
|
export const WYNN_GUILD_NAME = 'less than three'
|
||||||
export const WYNN_GUILD_ID = "2b717c60-ae61-4073-9d4f-c9c4583afed5";
|
export const WYNN_GUILD_ID = '2b717c60-ae61-4073-9d4f-c9c4583afed5'
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Container, InjectionToken } from "@needle-di/core";
|
import { Container, InjectionToken } from '@needle-di/core'
|
||||||
import { Sql } from "postgres";
|
import type { Sql } from 'postgres'
|
||||||
|
|
||||||
export const c = new Container();
|
export const c = new Container()
|
||||||
export const T_PG = new InjectionToken<Sql>("T_PG")
|
export const T_PG = new InjectionToken<Sql>('T_PG')
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { config } from "#/config";
|
import { InjectionToken } from '@needle-di/core'
|
||||||
import { c } from "#/di";
|
import { createBot } from 'discordeno'
|
||||||
import { InjectionToken } from "@needle-di/core";
|
import { config } from '#/config'
|
||||||
import { createBot, } from "discordeno";
|
import { c } from '#/di'
|
||||||
import { BotType, createBotParameters } from "./index";
|
import { type BotType, createBotParameters } from './index'
|
||||||
|
|
||||||
const createBotWithToken = (token: string) => {
|
const createBotWithToken = (token: string) => {
|
||||||
return createBot({
|
return createBot({
|
||||||
@ -10,13 +10,13 @@ const createBotWithToken = (token: string) => {
|
|||||||
token,
|
token,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
export const Bot = new InjectionToken<BotType>("DISCORD_BOT")
|
export const Bot = new InjectionToken<BotType>('DISCORD_BOT')
|
||||||
c.bind({
|
c.bind({
|
||||||
provide: Bot,
|
provide: Bot,
|
||||||
useFactory: () => {
|
useFactory: () => {
|
||||||
let token = config.DISCORD_TOKEN
|
const token = config.DISCORD_TOKEN
|
||||||
if (!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)
|
const bot = createBotWithToken(token)
|
||||||
return bot
|
return bot
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { describe, it, expect, vi } from 'vitest'
|
import { ApplicationCommandOptionTypes, ApplicationCommandTypes, type CreateApplicationCommand } from '@discordeno/types'
|
||||||
import { ApplicationCommandOptionTypes, ApplicationCommandTypes, CreateApplicationCommand } from '@discordeno/types'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
import { createCommandHandler, ExtractCommands } from './command_parser'
|
|
||||||
import type { InteractionData } from '..'
|
import type { InteractionData } from '..'
|
||||||
|
import { type ExtractCommands, createCommandHandler } from './command_parser'
|
||||||
|
|
||||||
// Test command definitions
|
// Test command definitions
|
||||||
const TEST_COMMANDS = [
|
const TEST_COMMANDS = [
|
||||||
@ -83,9 +83,7 @@ describe('createCommandHandler', () => {
|
|||||||
|
|
||||||
const interactionData: InteractionData = {
|
const interactionData: InteractionData = {
|
||||||
name: 'simple',
|
name: 'simple',
|
||||||
options: [
|
options: [{ name: 'message', type: ApplicationCommandOptionTypes.String, value: 'Hello world' }],
|
||||||
{ name: 'message', type: ApplicationCommandOptionTypes.String, value: 'Hello world' },
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await handler(interactionData)
|
await handler(interactionData)
|
||||||
|
|||||||
@ -1,28 +1,22 @@
|
|||||||
import { ApplicationCommandOptionTypes, CreateApplicationCommand, DiscordInteractionDataOption} from "@discordeno/types";
|
import { ApplicationCommandOptionTypes, type CreateApplicationCommand, type DiscordInteractionDataOption } from '@discordeno/types'
|
||||||
import { SLASH_COMMANDS } from "./slash_commands";
|
import type { InteractionData } from '..'
|
||||||
import { InteractionData } from "..";
|
import type { SLASH_COMMANDS } from './slash_commands'
|
||||||
|
|
||||||
// Map option types to their TypeScript types
|
// Map option types to their TypeScript types
|
||||||
type OptionTypeMap = {
|
type OptionTypeMap = {
|
||||||
[ApplicationCommandOptionTypes.String]: string;
|
[ApplicationCommandOptionTypes.String]: string
|
||||||
[ApplicationCommandOptionTypes.Integer]: number;
|
[ApplicationCommandOptionTypes.Integer]: number
|
||||||
[ApplicationCommandOptionTypes.Boolean]: boolean;
|
[ApplicationCommandOptionTypes.Boolean]: boolean
|
||||||
[ApplicationCommandOptionTypes.User]: string; // user ID
|
[ApplicationCommandOptionTypes.User]: string // user ID
|
||||||
[ApplicationCommandOptionTypes.Channel]: string; // channel ID
|
[ApplicationCommandOptionTypes.Channel]: string // channel ID
|
||||||
[ApplicationCommandOptionTypes.Role]: string; // role ID
|
[ApplicationCommandOptionTypes.Role]: string // role ID
|
||||||
[ApplicationCommandOptionTypes.Number]: number;
|
[ApplicationCommandOptionTypes.Number]: number
|
||||||
[ApplicationCommandOptionTypes.Mentionable]: string; // ID
|
[ApplicationCommandOptionTypes.Mentionable]: string // ID
|
||||||
[ApplicationCommandOptionTypes.Attachment]: string; // attachment ID
|
[ApplicationCommandOptionTypes.Attachment]: string // attachment ID
|
||||||
};
|
}
|
||||||
|
|
||||||
// Helper type to get option by name
|
// Helper type to get option by name
|
||||||
type GetOption<Options, Name> = Options extends readonly any[]
|
type GetOption<Options, Name> = Options extends readonly any[] ? (Options[number] extends infer O ? (O extends { name: Name } ? O : never) : never) : never
|
||||||
? Options[number] extends infer O
|
|
||||||
? O extends { name: Name }
|
|
||||||
? O
|
|
||||||
: never
|
|
||||||
: never
|
|
||||||
: never;
|
|
||||||
|
|
||||||
// Extract the argument types from command options
|
// Extract the argument types from command options
|
||||||
export type ExtractArgs<Options extends readonly any[]> = {
|
export type ExtractArgs<Options extends readonly any[]> = {
|
||||||
@ -32,11 +26,11 @@ export type ExtractArgs<Options extends readonly any[]> = {
|
|||||||
? OptionTypeMap[T]
|
? OptionTypeMap[T]
|
||||||
: OptionTypeMap[T] | undefined
|
: OptionTypeMap[T] | undefined
|
||||||
: never
|
: never
|
||||||
: never;
|
: never
|
||||||
};
|
}
|
||||||
|
|
||||||
// Handler function type that accepts typed arguments
|
// 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
|
// Get subcommand by name
|
||||||
type GetSubcommand<Options, Name> = Options extends readonly any[]
|
type GetSubcommand<Options, Name> = Options extends readonly any[]
|
||||||
@ -45,21 +39,17 @@ type GetSubcommand<Options, Name> = Options extends readonly any[]
|
|||||||
? O
|
? O
|
||||||
: never
|
: never
|
||||||
: never
|
: never
|
||||||
: never;
|
: never
|
||||||
|
|
||||||
// Check if all options are subcommands
|
// Check if all options are subcommands
|
||||||
type HasOnlySubcommands<Options extends readonly any[]> =
|
type HasOnlySubcommands<Options extends readonly any[]> = Options[number] extends { type: ApplicationCommandOptionTypes.SubCommand } ? true : false
|
||||||
Options[number] extends { type: ApplicationCommandOptionTypes.SubCommand }
|
|
||||||
? true
|
|
||||||
: false;
|
|
||||||
|
|
||||||
// Extract subcommand names from options
|
// Extract subcommand names from options
|
||||||
type SubcommandNames<Options extends readonly any[]> =
|
type SubcommandNames<Options extends readonly any[]> = Options[number] extends { name: infer N; type: ApplicationCommandOptionTypes.SubCommand }
|
||||||
Options[number] extends { name: infer N; type: ApplicationCommandOptionTypes.SubCommand }
|
|
||||||
? N extends string
|
? N extends string
|
||||||
? N
|
? N
|
||||||
: never
|
: never
|
||||||
: never;
|
: never
|
||||||
|
|
||||||
// Type to extract subcommand handlers
|
// Type to extract subcommand handlers
|
||||||
export type SubcommandHandlers<Options extends readonly any[]> = {
|
export type SubcommandHandlers<Options extends readonly any[]> = {
|
||||||
@ -68,14 +58,10 @@ export type SubcommandHandlers<Options extends readonly any[]> = {
|
|||||||
? HandlerFunction<ExtractArgs<SubOpts>>
|
? HandlerFunction<ExtractArgs<SubOpts>>
|
||||||
: HandlerFunction<{}>
|
: HandlerFunction<{}>
|
||||||
: HandlerFunction<{}>
|
: HandlerFunction<{}>
|
||||||
};
|
}
|
||||||
|
|
||||||
// Get command by name from array
|
// Get command by name from array
|
||||||
type GetCommand<Commands extends readonly any[], Name> = Commands[number] extends infer C
|
type GetCommand<Commands extends readonly any[], Name> = Commands[number] extends infer C ? (C extends { name: Name } ? C : never) : never
|
||||||
? C extends { name: Name }
|
|
||||||
? C
|
|
||||||
: never
|
|
||||||
: never;
|
|
||||||
|
|
||||||
// Main type to extract command handlers from slash commands
|
// Main type to extract command handlers from slash commands
|
||||||
export type ExtractCommands<T extends readonly any[]> = {
|
export type ExtractCommands<T extends readonly any[]> = {
|
||||||
@ -86,76 +72,75 @@ export type ExtractCommands<T extends readonly any[]> = {
|
|||||||
: HandlerFunction<ExtractArgs<Options>>
|
: HandlerFunction<ExtractArgs<Options>>
|
||||||
: HandlerFunction<{}>
|
: HandlerFunction<{}>
|
||||||
: HandlerFunction<{}>
|
: HandlerFunction<{}>
|
||||||
};
|
}
|
||||||
|
|
||||||
// The actual command handler type based on SLASH_COMMANDS
|
// 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
|
// Helper function to parse option values from interaction data
|
||||||
function parseOptions(options?: DiscordInteractionDataOption[]): Record<string, any> {
|
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) {
|
for (const option of options) {
|
||||||
if (option.type === ApplicationCommandOptionTypes.SubCommand ||
|
if (option.type === ApplicationCommandOptionTypes.SubCommand || option.type === ApplicationCommandOptionTypes.SubCommandGroup) {
|
||||||
option.type === ApplicationCommandOptionTypes.SubCommandGroup) {
|
continue
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args[option.name] = option.value;
|
args[option.name] = option.value
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to create command handlers with type safety
|
// Helper function to create command handlers with type safety
|
||||||
export function createCommandHandler<T extends readonly CreateApplicationCommand[]>(
|
export function createCommandHandler<T extends readonly CreateApplicationCommand[]>({
|
||||||
{handler, notFoundHandler}:{
|
handler,
|
||||||
|
notFoundHandler,
|
||||||
|
}: {
|
||||||
handler: ExtractCommands<T>
|
handler: ExtractCommands<T>
|
||||||
notFoundHandler: HandlerFunction<{}>
|
notFoundHandler: HandlerFunction<{}>
|
||||||
}) {
|
}) {
|
||||||
return async (data: InteractionData): Promise<void> => {
|
return async (data: InteractionData): Promise<void> => {
|
||||||
if (!data || !data.name) {
|
if (!data || !data.name) {
|
||||||
await notFoundHandler({});
|
await notFoundHandler({})
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const commandName = data.name as keyof typeof handler;
|
const commandName = data.name as keyof typeof handler
|
||||||
const commandHandler = handler[commandName];
|
const commandHandler = handler[commandName]
|
||||||
|
|
||||||
if (!commandHandler) {
|
if (!commandHandler) {
|
||||||
await notFoundHandler({});
|
await notFoundHandler({})
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a direct command or has subcommands
|
// Check if it's a direct command or has subcommands
|
||||||
if (typeof commandHandler === 'function') {
|
if (typeof commandHandler === 'function') {
|
||||||
// Parse arguments from top-level options
|
// Parse arguments from top-level options
|
||||||
const args = parseOptions(data.options);
|
const args = parseOptions(data.options)
|
||||||
await commandHandler(args);
|
await commandHandler(args)
|
||||||
} else {
|
} else {
|
||||||
// Handle subcommands
|
// Handle subcommands
|
||||||
const subcommand = data.options?.find(
|
const subcommand = data.options?.find(
|
||||||
opt => opt.type === ApplicationCommandOptionTypes.SubCommand ||
|
(opt) => opt.type === ApplicationCommandOptionTypes.SubCommand || opt.type === ApplicationCommandOptionTypes.SubCommandGroup
|
||||||
opt.type === ApplicationCommandOptionTypes.SubCommandGroup
|
)
|
||||||
);
|
|
||||||
|
|
||||||
if (!subcommand) {
|
if (!subcommand) {
|
||||||
await notFoundHandler({});
|
await notFoundHandler({})
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const subHandler = commandHandler[subcommand.name as keyof typeof commandHandler];
|
const subHandler = commandHandler[subcommand.name as keyof typeof commandHandler]
|
||||||
if (!subHandler || typeof subHandler !== 'function') {
|
if (!subHandler || typeof subHandler !== 'function') {
|
||||||
await notFoundHandler({});
|
await notFoundHandler({})
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse arguments from subcommand options
|
// Parse arguments from subcommand options
|
||||||
const args = parseOptions(subcommand.options);
|
const args = parseOptions(subcommand.options)
|
||||||
await (subHandler as HandlerFunction<any>)(args);
|
await (subHandler as HandlerFunction<any>)(args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { Bot } from "#/discord/bot"
|
import { Client } from '@temporalio/client'
|
||||||
import { ActivityTypes, InteractionTypes } from "discordeno"
|
import { ActivityTypes, InteractionTypes } from 'discordeno'
|
||||||
import { c } from "#/di"
|
import { c } from '#/di'
|
||||||
import { Client } from "@temporalio/client"
|
import type { BotType } from '#/discord'
|
||||||
import { workflowHandleInteractionCreate } from "#/workflows"
|
import { Bot } from '#/discord/bot'
|
||||||
import { BotType } from "#/discord"
|
import { workflowHandleInteractionCreate } from '#/workflows'
|
||||||
|
|
||||||
export const events = () => {return {
|
export const events = () => {
|
||||||
|
return {
|
||||||
interactionCreate: async (interaction) => {
|
interactionCreate: async (interaction) => {
|
||||||
if (interaction.acknowledged) {
|
if (interaction.acknowledged) {
|
||||||
return
|
return
|
||||||
@ -14,11 +15,12 @@ export const events = () => {return {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const temporalClient = await c.getAsync(Client);
|
const temporalClient = await c.getAsync(Client)
|
||||||
|
|
||||||
// Start the workflow to handle the interaction
|
// Start the workflow to handle the interaction
|
||||||
const handle = await temporalClient.workflow.start(workflowHandleInteractionCreate, {
|
const handle = await temporalClient.workflow.start(workflowHandleInteractionCreate, {
|
||||||
args: [{
|
args: [
|
||||||
|
{
|
||||||
ref: {
|
ref: {
|
||||||
id: interaction.id,
|
id: interaction.id,
|
||||||
token: interaction.token,
|
token: interaction.token,
|
||||||
@ -26,13 +28,14 @@ export const events = () => {return {
|
|||||||
acknowledged: interaction.acknowledged,
|
acknowledged: interaction.acknowledged,
|
||||||
},
|
},
|
||||||
data: interaction.data,
|
data: interaction.data,
|
||||||
}],
|
},
|
||||||
|
],
|
||||||
workflowId: `discord-interaction-${interaction.id}`,
|
workflowId: `discord-interaction-${interaction.id}`,
|
||||||
taskQueue: 'wynn-worker-ts',
|
taskQueue: 'wynn-worker-ts',
|
||||||
});
|
})
|
||||||
|
|
||||||
// Wait for the workflow to complete
|
// Wait for the workflow to complete
|
||||||
await handle.result();
|
await handle.result()
|
||||||
|
|
||||||
return
|
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 = [
|
export const SLASH_COMMANDS = [
|
||||||
{
|
{
|
||||||
name: `guild`,
|
name: `guild`,
|
||||||
description: "guild commands",
|
description: 'guild commands',
|
||||||
type: ApplicationCommandTypes.ChatInput,
|
type: ApplicationCommandTypes.ChatInput,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: "leaderboard",
|
name: 'leaderboard',
|
||||||
description: "view the current leaderboard",
|
description: 'view the current leaderboard',
|
||||||
type: ApplicationCommandOptionTypes.SubCommand,
|
type: ApplicationCommandOptionTypes.SubCommand,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "info",
|
name: 'info',
|
||||||
description: "view guild information",
|
description: 'view guild information',
|
||||||
type: ApplicationCommandOptionTypes.SubCommand,
|
type: ApplicationCommandOptionTypes.SubCommand,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "online",
|
name: 'online',
|
||||||
description: "show online players",
|
description: 'show online players',
|
||||||
type: ApplicationCommandOptionTypes.SubCommand,
|
type: ApplicationCommandOptionTypes.SubCommand,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "admin",
|
name: 'admin',
|
||||||
description: "admin commands",
|
description: 'admin commands',
|
||||||
type: ApplicationCommandTypes.ChatInput,
|
type: ApplicationCommandTypes.ChatInput,
|
||||||
defaultMemberPermissions: [
|
defaultMemberPermissions: ['ADMINISTRATOR'],
|
||||||
"ADMINISTRATOR",
|
|
||||||
],
|
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: "set_wynn_guild",
|
name: 'set_wynn_guild',
|
||||||
description: "set the default wynncraft guild for the server",
|
description: 'set the default wynncraft guild for the server',
|
||||||
type: ApplicationCommandOptionTypes.SubCommand,
|
type: ApplicationCommandOptionTypes.SubCommand,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},{
|
},
|
||||||
name: "player",
|
{
|
||||||
description: "player commands",
|
name: 'player',
|
||||||
|
description: 'player commands',
|
||||||
type: ApplicationCommandTypes.ChatInput,
|
type: ApplicationCommandTypes.ChatInput,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: "lookup",
|
name: 'lookup',
|
||||||
description: "view player information",
|
description: 'view player information',
|
||||||
type: ApplicationCommandOptionTypes.SubCommand,
|
type: ApplicationCommandOptionTypes.SubCommand,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: "player",
|
name: 'player',
|
||||||
description: "player name",
|
description: 'player name',
|
||||||
type: ApplicationCommandOptionTypes.String,
|
type: ApplicationCommandOptionTypes.String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
] as const satisfies CreateApplicationCommand[]
|
] as const satisfies CreateApplicationCommand[]
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import {BotType} from "#/discord"
|
import type { BotType } from '#/discord'
|
||||||
|
|
||||||
|
|
||||||
export type BotEventsType = BotType['events']
|
export type BotEventsType = BotType['events']
|
||||||
export type InteractionHandler = NonNullable<BotType['events']['interactionCreate']>
|
export type InteractionHandler = NonNullable<BotType['events']['interactionCreate']>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Intents, InteractionTypes } from "@discordeno/types";
|
import { Intents, type InteractionTypes } from '@discordeno/types'
|
||||||
import type { Bot, DesiredPropertiesBehavior, CompleteDesiredProperties } from "discordeno";
|
import type { Bot, CompleteDesiredProperties, DesiredPropertiesBehavior } from 'discordeno'
|
||||||
export const intents = [
|
export const intents = [
|
||||||
Intents.GuildModeration,
|
Intents.GuildModeration,
|
||||||
Intents.GuildWebhooks,
|
Intents.GuildWebhooks,
|
||||||
@ -39,29 +39,28 @@ export const createBotParameters = {
|
|||||||
member: true,
|
member: true,
|
||||||
guildId: true,
|
guildId: true,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
// Extract the type of desired properties from our parameters
|
// 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
|
// 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
|
// Type for the interaction reference passed to workflows/activities
|
||||||
export interface InteractionRef {
|
export interface InteractionRef {
|
||||||
id: bigint;
|
id: bigint
|
||||||
token: string;
|
token: string
|
||||||
type: InteractionTypes;
|
type: InteractionTypes
|
||||||
acknowledged?: boolean;
|
acknowledged?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type for the interaction data payload
|
// 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
|
// Type for the complete interaction handling payload
|
||||||
export interface InteractionCreatePayload {
|
export interface InteractionCreatePayload {
|
||||||
ref: InteractionRef;
|
ref: InteractionRef
|
||||||
data: InteractionData;
|
data: InteractionData
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,5 @@
|
|||||||
import { c } from "#/di";
|
import { c } from '#/di'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class EventMux {
|
export class EventMux {
|
||||||
constructor() {
|
constructor() {}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import crypto from "node:crypto";
|
import crypto from 'node:crypto'
|
||||||
import {IDataType, xxhash128} from "hash-wasm";
|
import { type IDataType, xxhash128 } from 'hash-wasm'
|
||||||
|
|
||||||
export function sha1Hash(data: crypto.BinaryLike) {
|
export function sha1Hash(data: crypto.BinaryLike) {
|
||||||
const hash = crypto.createHash('sha1');
|
const hash = crypto.createHash('sha1')
|
||||||
hash.update(data);
|
hash.update(data)
|
||||||
return hash.digest('hex');
|
return hash.digest('hex')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function fastHashFileV1(data: IDataType): Promise<string> {
|
export async function fastHashFileV1(data: IDataType): Promise<string> {
|
||||||
const hash = xxhash128(data)
|
const hash = xxhash128(data)
|
||||||
return hash
|
return hash
|
||||||
|
|||||||
@ -1,39 +1,34 @@
|
|||||||
|
|
||||||
|
|
||||||
export class TabWriter {
|
export class TabWriter {
|
||||||
|
columns: string[][]
|
||||||
|
|
||||||
columns: string[][];
|
constructor(private readonly spacing: number = 2) {
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly spacing: number = 2
|
|
||||||
) {
|
|
||||||
this.columns = []
|
this.columns = []
|
||||||
}
|
}
|
||||||
|
|
||||||
add(row: string[]) {
|
add(row: string[]) {
|
||||||
if (this.columns.length == 0) {
|
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) {
|
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++) {
|
for (let i = 0; i < row.length; i++) {
|
||||||
this.columns[i].push(row[i]);
|
this.columns[i].push(row[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
let out = ""
|
let out = ''
|
||||||
if (this.columns.length == 0) {
|
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++) {
|
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++) {
|
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
|
* Mapping of Wynncraft guild ranks to their corresponding emojis
|
||||||
*/
|
*/
|
||||||
export const RANK_EMOJIS = {
|
export const RANK_EMOJIS = {
|
||||||
"OWNER": "👑",
|
OWNER: '👑',
|
||||||
"CHIEF": "⭐",
|
CHIEF: '⭐',
|
||||||
"STRATEGIST": "🎯",
|
STRATEGIST: '🎯',
|
||||||
"CAPTAIN": "⚔️",
|
CAPTAIN: '⚔️',
|
||||||
"RECRUITER": "📢",
|
RECRUITER: '📢',
|
||||||
"RECRUIT": "🌱",
|
RECRUIT: '🌱',
|
||||||
} as const;
|
} as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the emoji for a given guild rank
|
* 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
|
* @returns The corresponding emoji or a default bullet point
|
||||||
*/
|
*/
|
||||||
export function getRankEmoji(rank: string): string {
|
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
|
* @returns Formatted string with appropriate suffix
|
||||||
*/
|
*/
|
||||||
export function formatNumber(num: number): string {
|
export function formatNumber(num: number): string {
|
||||||
if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1)}M`;
|
if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1)}M`
|
||||||
if (num >= 1_000) return `${(num / 1_000).toFixed(0)}K`;
|
if (num >= 1_000) return `${(num / 1_000).toFixed(0)}K`
|
||||||
return num.toLocaleString();
|
return num.toLocaleString()
|
||||||
}
|
}
|
||||||
@ -1,29 +1,29 @@
|
|||||||
import { type } from "arktype"
|
import { type } from 'arktype'
|
||||||
|
|
||||||
export const WynnGuildOverviewMember = type({
|
export const WynnGuildOverviewMember = type({
|
||||||
uuid: "string",
|
uuid: 'string',
|
||||||
online: "boolean",
|
online: 'boolean',
|
||||||
server: "null | string",
|
server: 'null | string',
|
||||||
contributed: "number",
|
contributed: 'number',
|
||||||
contributionRank: "number",
|
contributionRank: 'number',
|
||||||
joined: "string"
|
joined: 'string',
|
||||||
})
|
})
|
||||||
|
|
||||||
const WapiV3GuildMembers = type({
|
const WapiV3GuildMembers = type({
|
||||||
"[string]": WynnGuildOverviewMember
|
'[string]': WynnGuildOverviewMember,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const WapiV3GuildOverview = type({
|
export const WapiV3GuildOverview = type({
|
||||||
uuid: "string",
|
uuid: 'string',
|
||||||
name: "string",
|
name: 'string',
|
||||||
prefix: "string",
|
prefix: 'string',
|
||||||
level: "number",
|
level: 'number',
|
||||||
xpPercent: "number",
|
xpPercent: 'number',
|
||||||
territories: "number",
|
territories: 'number',
|
||||||
wars: "number",
|
wars: 'number',
|
||||||
created: "string",
|
created: 'string',
|
||||||
members: {
|
members: {
|
||||||
total: "number",
|
total: 'number',
|
||||||
owner: WapiV3GuildMembers,
|
owner: WapiV3GuildMembers,
|
||||||
chief: WapiV3GuildMembers,
|
chief: WapiV3GuildMembers,
|
||||||
strategist: WapiV3GuildMembers,
|
strategist: WapiV3GuildMembers,
|
||||||
@ -31,235 +31,226 @@ export const WapiV3GuildOverview = type({
|
|||||||
recruiter: WapiV3GuildMembers,
|
recruiter: WapiV3GuildMembers,
|
||||||
recruit: WapiV3GuildMembers,
|
recruit: WapiV3GuildMembers,
|
||||||
},
|
},
|
||||||
online: "number",
|
online: 'number',
|
||||||
banner: {
|
banner: {
|
||||||
base: "string",
|
base: 'string',
|
||||||
tier: "number",
|
tier: 'number',
|
||||||
structure: "string",
|
structure: 'string',
|
||||||
layers: type({ colour: "string", pattern: "string" }).array(),
|
layers: type({ colour: 'string', pattern: 'string' }).array(),
|
||||||
},
|
},
|
||||||
seasonRanks: {
|
seasonRanks: {
|
||||||
"[string]": {
|
'[string]': {
|
||||||
rating: "number",
|
rating: 'number',
|
||||||
finalTerritories: "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(
|
const WynnSkills = type.enumerated('alchemism', 'armouring', 'cooking', 'jeweling', 'scribing', 'tailoring', 'weaponsmithing', 'woodworking')
|
||||||
"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({
|
const WynnEquipRequirements = type({
|
||||||
level: "number",
|
level: 'number',
|
||||||
"classRequirement?": "string",
|
'classRequirement?': 'string',
|
||||||
"intelligence?": "number",
|
'intelligence?': 'number',
|
||||||
"strength?": "number",
|
'strength?': 'number',
|
||||||
"dexterity?": "number",
|
'dexterity?': 'number',
|
||||||
"defence?": "number",
|
'defence?': 'number',
|
||||||
"agility?": "number",
|
'agility?': 'number',
|
||||||
"skills?": WynnSkills.array(),
|
'skills?': WynnSkills.array(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const WynnBaseStats = type({
|
const WynnBaseStats = type({
|
||||||
"[string]": type("number").or({
|
'[string]': type('number').or({
|
||||||
min: "number",
|
min: 'number',
|
||||||
raw: "number",
|
raw: 'number',
|
||||||
max: "number",
|
max: 'number',
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
const WynnIdentifications = type({
|
const WynnIdentifications = type({
|
||||||
"[string]": type("number").or({
|
'[string]': type('number').or({
|
||||||
min: "number",
|
min: 'number',
|
||||||
raw: "number",
|
raw: 'number',
|
||||||
max: "number",
|
max: 'number',
|
||||||
})
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const WynnItemIcon = type({
|
const WynnItemIcon = type({
|
||||||
format: "string",
|
format: 'string',
|
||||||
value: "unknown"
|
value: 'unknown',
|
||||||
})
|
})
|
||||||
|
|
||||||
export const WapiV3ItemTool = type({
|
export const WapiV3ItemTool = type({
|
||||||
internalName: "string",
|
internalName: 'string',
|
||||||
type: '"tool"',
|
type: '"tool"',
|
||||||
toolType: type.enumerated("axe", "pickaxe", "rod", "scythe"),
|
toolType: type.enumerated('axe', 'pickaxe', 'rod', 'scythe'),
|
||||||
"identified?": "boolean",
|
'identified?': 'boolean',
|
||||||
gatheringSpeed: "number",
|
gatheringSpeed: 'number',
|
||||||
requirements: {
|
requirements: {
|
||||||
level: "number",
|
level: 'number',
|
||||||
},
|
},
|
||||||
icon: WynnItemIcon,
|
icon: WynnItemIcon,
|
||||||
rarity: WynnItemRarity
|
rarity: WynnItemRarity,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const WapiV3ItemTome = type({
|
export const WapiV3ItemTome = type({
|
||||||
internalName: "string",
|
internalName: 'string',
|
||||||
tomeType: type.enumerated(
|
tomeType: type.enumerated('guild_tome', 'marathon_tome', 'mysticism_tome', 'weapon_tome', 'armour_tome', 'expertise_tome', 'lootrun_tome'),
|
||||||
"guild_tome",
|
raidReward: 'boolean',
|
||||||
"marathon_tome",
|
|
||||||
"mysticism_tome",
|
|
||||||
"weapon_tome",
|
|
||||||
"armour_tome",
|
|
||||||
"expertise_tome",
|
|
||||||
"lootrun_tome",
|
|
||||||
),
|
|
||||||
raidReward: "boolean",
|
|
||||||
type: '"tome"',
|
type: '"tome"',
|
||||||
"restrictions?": WynnItemRestrictions,
|
'restrictions?': WynnItemRestrictions,
|
||||||
"dropMeta?": WynnDropMeta,
|
'dropMeta?': WynnDropMeta,
|
||||||
dropRestriction: WynnDropRestriction,
|
dropRestriction: WynnDropRestriction,
|
||||||
requirements: WynnEquipRequirements,
|
requirements: WynnEquipRequirements,
|
||||||
"lore?": "string",
|
'lore?': 'string',
|
||||||
icon: WynnItemIcon,
|
icon: WynnItemIcon,
|
||||||
"base?": WynnBaseStats,
|
'base?': WynnBaseStats,
|
||||||
rarity: WynnItemRarity
|
rarity: WynnItemRarity,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const WapiV3ItemCharm = type({
|
export const WapiV3ItemCharm = type({
|
||||||
internalName: "string",
|
internalName: 'string',
|
||||||
type: '"charm"',
|
type: '"charm"',
|
||||||
"restrictions?": WynnItemRestrictions,
|
'restrictions?': WynnItemRestrictions,
|
||||||
"dropMeta?": WynnDropMeta,
|
'dropMeta?': WynnDropMeta,
|
||||||
dropRestriction: WynnDropRestriction,
|
dropRestriction: WynnDropRestriction,
|
||||||
requirements: WynnEquipRequirements,
|
requirements: WynnEquipRequirements,
|
||||||
"lore?": "string",
|
'lore?': 'string',
|
||||||
icon: WynnItemIcon,
|
icon: WynnItemIcon,
|
||||||
base: WynnBaseStats,
|
base: WynnBaseStats,
|
||||||
rarity: WynnItemRarity
|
rarity: WynnItemRarity,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const WapiV3ItemAccessory = type({
|
export const WapiV3ItemAccessory = type({
|
||||||
internalName: "string",
|
internalName: 'string',
|
||||||
type: '"accessory"',
|
type: '"accessory"',
|
||||||
"identified?": "boolean",
|
'identified?': 'boolean',
|
||||||
accessoryType: type.enumerated("ring", "necklace", "bracelet"),
|
accessoryType: type.enumerated('ring', 'necklace', 'bracelet'),
|
||||||
"majorIds?": {
|
'majorIds?': {
|
||||||
"[string]": "string"
|
'[string]': 'string',
|
||||||
},
|
},
|
||||||
"restrictions?": WynnItemRestrictions,
|
'restrictions?': WynnItemRestrictions,
|
||||||
"dropMeta?": WynnDropMeta,
|
'dropMeta?': WynnDropMeta,
|
||||||
dropRestriction: WynnDropRestriction,
|
dropRestriction: WynnDropRestriction,
|
||||||
requirements: WynnEquipRequirements,
|
requirements: WynnEquipRequirements,
|
||||||
"lore?": "string",
|
'lore?': 'string',
|
||||||
icon: WynnItemIcon,
|
icon: WynnItemIcon,
|
||||||
"identifications?": WynnIdentifications,
|
'identifications?': WynnIdentifications,
|
||||||
"base?": WynnBaseStats,
|
'base?': WynnBaseStats,
|
||||||
rarity: WynnItemRarity
|
rarity: WynnItemRarity,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const WapiV3ItemIngredient = type({
|
export const WapiV3ItemIngredient = type({
|
||||||
internalName: "string",
|
internalName: 'string',
|
||||||
type: '"ingredient"',
|
type: '"ingredient"',
|
||||||
requirements: {
|
requirements: {
|
||||||
level: "number",
|
level: 'number',
|
||||||
skills: WynnSkills.array(),
|
skills: WynnSkills.array(),
|
||||||
},
|
},
|
||||||
icon: WynnItemIcon,
|
icon: WynnItemIcon,
|
||||||
"identifications?": WynnIdentifications,
|
'identifications?': WynnIdentifications,
|
||||||
tier: "number",
|
tier: 'number',
|
||||||
consumableOnlyIDs: {
|
consumableOnlyIDs: {
|
||||||
"[string]": "number"
|
'[string]': 'number',
|
||||||
},
|
},
|
||||||
ingredientPositionModifiers: {
|
ingredientPositionModifiers: {
|
||||||
"[string]": "number"
|
'[string]': 'number',
|
||||||
},
|
},
|
||||||
itemOnlyIDs: {
|
itemOnlyIDs: {
|
||||||
"[string]": "number"
|
'[string]': 'number',
|
||||||
},
|
},
|
||||||
"droppedBy?": type({
|
'droppedBy?': type({
|
||||||
name: "string",
|
name: 'string',
|
||||||
coords: type("boolean | null")
|
coords: type('boolean | null').or(type('number[] == 4')).or(type('number[] == 4').array()),
|
||||||
.or(type("number[] == 4"))
|
}).array(),
|
||||||
.or(type("number[] == 4").array())
|
|
||||||
}).array()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const WapiV3ItemMaterial = type({
|
export const WapiV3ItemMaterial = type({
|
||||||
internalName: "string",
|
internalName: 'string',
|
||||||
type: '"material"',
|
type: '"material"',
|
||||||
identified: "boolean",
|
identified: 'boolean',
|
||||||
requirements: {
|
requirements: {
|
||||||
level: "number",
|
level: 'number',
|
||||||
},
|
},
|
||||||
craftable: type.enumerated(
|
craftable: type
|
||||||
"potions", "food", "scrolls",
|
.enumerated(
|
||||||
"helmets", "chestplates", "rings", "bracelets",
|
'potions',
|
||||||
"necklaces", "boots", "leggings", "bows", "wands", "spears",
|
'food',
|
||||||
"daggers", "chestplates", "helmets"
|
'scrolls',
|
||||||
).array(),
|
'helmets',
|
||||||
|
'chestplates',
|
||||||
|
'rings',
|
||||||
|
'bracelets',
|
||||||
|
'necklaces',
|
||||||
|
'boots',
|
||||||
|
'leggings',
|
||||||
|
'bows',
|
||||||
|
'wands',
|
||||||
|
'spears',
|
||||||
|
'daggers',
|
||||||
|
'chestplates',
|
||||||
|
'helmets'
|
||||||
|
)
|
||||||
|
.array(),
|
||||||
icon: WynnItemIcon,
|
icon: WynnItemIcon,
|
||||||
tier: "number"
|
tier: 'number',
|
||||||
})
|
})
|
||||||
|
|
||||||
export const WapiV3ItemWeapon = type({
|
export const WapiV3ItemWeapon = type({
|
||||||
internalName: "string",
|
internalName: 'string',
|
||||||
type: '"weapon"',
|
type: '"weapon"',
|
||||||
"identified?": "boolean",
|
'identified?': 'boolean',
|
||||||
"allowCraftsman?": "boolean",
|
'allowCraftsman?': 'boolean',
|
||||||
weaponType: type.enumerated("bow", "relik", "wand", "dagger", "spear"),
|
weaponType: type.enumerated('bow', 'relik', 'wand', 'dagger', 'spear'),
|
||||||
attackSpeed: type.enumerated(
|
attackSpeed: type.enumerated('super_slow', 'very_slow', 'slow', 'normal', 'fast', 'very_fast', 'super_fast'),
|
||||||
"super_slow", "very_slow", "slow", "normal", "fast", "very_fast", "super_fast"
|
'powderSlots?': 'number',
|
||||||
),
|
'averageDps?': 'number',
|
||||||
"powderSlots?": "number",
|
'restrictions?': WynnItemRestrictions,
|
||||||
"averageDps?": "number",
|
'dropMeta?': WynnDropMeta,
|
||||||
"restrictions?": WynnItemRestrictions,
|
'dropRestriction?': WynnDropRestriction,
|
||||||
"dropMeta?": WynnDropMeta,
|
|
||||||
"dropRestriction?": WynnDropRestriction,
|
|
||||||
requirements: WynnEquipRequirements,
|
requirements: WynnEquipRequirements,
|
||||||
"majorIds?": {
|
'majorIds?': {
|
||||||
"[string]": "string"
|
'[string]': 'string',
|
||||||
},
|
},
|
||||||
"lore?": "string",
|
'lore?': 'string',
|
||||||
icon: WynnItemIcon,
|
icon: WynnItemIcon,
|
||||||
"identifications?": WynnIdentifications,
|
'identifications?': WynnIdentifications,
|
||||||
"base?": WynnBaseStats,
|
'base?': WynnBaseStats,
|
||||||
rarity: WynnItemRarity
|
rarity: WynnItemRarity,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const WapiV3ItemArmour = type({
|
export const WapiV3ItemArmour = type({
|
||||||
internalName: "string",
|
internalName: 'string',
|
||||||
type: '"armour"',
|
type: '"armour"',
|
||||||
armourType: "string",
|
armourType: 'string',
|
||||||
"armourMaterial?": "string",
|
'armourMaterial?': 'string',
|
||||||
"armourColor?": "string",
|
'armourColor?': 'string',
|
||||||
"identified?": "boolean",
|
'identified?': 'boolean',
|
||||||
"allowCraftsman?": "boolean",
|
'allowCraftsman?': 'boolean',
|
||||||
"restrictions?": WynnItemRestrictions,
|
'restrictions?': WynnItemRestrictions,
|
||||||
dropRestriction: WynnDropRestriction,
|
dropRestriction: WynnDropRestriction,
|
||||||
"dropMeta?": WynnDropMeta,
|
'dropMeta?': WynnDropMeta,
|
||||||
"icon?": WynnItemIcon,
|
'icon?': WynnItemIcon,
|
||||||
requirements: WynnEquipRequirements,
|
requirements: WynnEquipRequirements,
|
||||||
"majorIds?": {
|
'majorIds?': {
|
||||||
"[string]": "string"
|
'[string]': 'string',
|
||||||
},
|
},
|
||||||
"powderSlots?": "number",
|
'powderSlots?': 'number',
|
||||||
"lore?": "string",
|
'lore?': 'string',
|
||||||
"identifications?": WynnIdentifications,
|
'identifications?': WynnIdentifications,
|
||||||
"base?": WynnBaseStats,
|
'base?': WynnBaseStats,
|
||||||
rarity: WynnItemRarity
|
rarity: WynnItemRarity,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const WApiV3Item = WapiV3ItemMaterial
|
export const WApiV3Item = WapiV3ItemMaterial.or(WapiV3ItemWeapon)
|
||||||
.or(WapiV3ItemWeapon)
|
|
||||||
.or(WapiV3ItemArmour)
|
.or(WapiV3ItemArmour)
|
||||||
.or(WapiV3ItemIngredient)
|
.or(WapiV3ItemIngredient)
|
||||||
.or(WapiV3ItemAccessory)
|
.or(WapiV3ItemAccessory)
|
||||||
@ -268,5 +259,5 @@ export const WApiV3Item = WapiV3ItemMaterial
|
|||||||
.or(WapiV3ItemTool)
|
.or(WapiV3ItemTool)
|
||||||
|
|
||||||
export const WApiV3ItemDatabase = type({
|
export const WApiV3ItemDatabase = type({
|
||||||
"[string]": WApiV3Item
|
'[string]': WApiV3Item,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -6,10 +6,9 @@ export const WynnGuildOverviewMember = z.object({
|
|||||||
server: z.null(),
|
server: z.null(),
|
||||||
contributed: z.number(),
|
contributed: z.number(),
|
||||||
contributionRank: z.number(),
|
contributionRank: z.number(),
|
||||||
joined: z.string()
|
joined: z.string(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const WapiV3GuildMembers = z.record(z.string(), WynnGuildOverviewMember)
|
const WapiV3GuildMembers = z.record(z.string(), WynnGuildOverviewMember)
|
||||||
|
|
||||||
export const WapiV3GuildOverview = z.object({
|
export const WapiV3GuildOverview = z.object({
|
||||||
@ -35,22 +34,16 @@ export const WapiV3GuildOverview = z.object({
|
|||||||
base: z.string(),
|
base: z.string(),
|
||||||
tier: z.number(),
|
tier: z.number(),
|
||||||
structure: z.string(),
|
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 WynnDropMeta = z.any()
|
||||||
const WynnDropRestriction = z.enum(["normal","never","dungeon", "lootchest"])
|
const WynnDropRestriction = z.enum(['normal', 'never', 'dungeon', 'lootchest'])
|
||||||
|
|
||||||
const WynnItemRestrictions = z.enum([
|
const WynnItemRestrictions = z.enum(['untradable', 'quest item'])
|
||||||
"untradable", "quest item",
|
|
||||||
])
|
|
||||||
|
|
||||||
const WynnEquipRequirements = z.object({
|
const WynnEquipRequirements = z.object({
|
||||||
level: z.number(),
|
level: z.number(),
|
||||||
@ -62,23 +55,29 @@ const WynnEquipRequirements = z.object({
|
|||||||
agility: z.number().optional(),
|
agility: z.number().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const WynnBaseStats = z.record(z.string(),z.union([
|
const WynnBaseStats = z.record(
|
||||||
|
z.string(),
|
||||||
|
z.union([
|
||||||
z.number(),
|
z.number(),
|
||||||
z.object({
|
z.object({
|
||||||
min: z.number(),
|
min: z.number(),
|
||||||
raw: z.number(),
|
raw: z.number(),
|
||||||
max: z.number(),
|
max: z.number(),
|
||||||
})
|
}),
|
||||||
]))
|
])
|
||||||
|
)
|
||||||
|
|
||||||
const WynnIdentifications = z.record(z.string(), z.union([
|
const WynnIdentifications = z.record(
|
||||||
|
z.string(),
|
||||||
|
z.union([
|
||||||
z.number(),
|
z.number(),
|
||||||
z.object({
|
z.object({
|
||||||
min: z.number(),
|
min: z.number(),
|
||||||
raw: z.number(),
|
raw: z.number(),
|
||||||
max: z.number(),
|
max: z.number(),
|
||||||
})
|
}),
|
||||||
]))
|
])
|
||||||
|
)
|
||||||
|
|
||||||
const WynnItemIcon = z.object({
|
const WynnItemIcon = z.object({
|
||||||
format: z.string(),
|
format: z.string(),
|
||||||
@ -109,9 +108,7 @@ const WynnItemIcon = z.object({
|
|||||||
export const WapiV3ItemTool = z.object({
|
export const WapiV3ItemTool = z.object({
|
||||||
internalName: z.string(),
|
internalName: z.string(),
|
||||||
type: z.literal('tool'),
|
type: z.literal('tool'),
|
||||||
toolType: z.enum([
|
toolType: z.enum(['axe', 'pickaxe', 'rod', 'scythe']),
|
||||||
"axe","pickaxe","rod","scythe",
|
|
||||||
]),
|
|
||||||
identified: z.boolean().optional(),
|
identified: z.boolean().optional(),
|
||||||
gatheringSpeed: z.number(),
|
gatheringSpeed: z.number(),
|
||||||
requirements: z.object({
|
requirements: z.object({
|
||||||
@ -123,15 +120,7 @@ export const WapiV3ItemTool = z.object({
|
|||||||
|
|
||||||
export const WapiV3ItemTome = z.object({
|
export const WapiV3ItemTome = z.object({
|
||||||
internalName: z.string(),
|
internalName: z.string(),
|
||||||
tomeType: z.enum([
|
tomeType: z.enum(['guild_tome', 'marathon_tome', 'mysticism_tome', 'weapon_tome', 'armour_tome', 'expertise_tome', 'lootrun_tome']),
|
||||||
"guild_tome",
|
|
||||||
"marathon_tome",
|
|
||||||
"mysticism_tome",
|
|
||||||
"weapon_tome",
|
|
||||||
"armour_tome",
|
|
||||||
"expertise_tome",
|
|
||||||
"lootrun_tome",
|
|
||||||
]),
|
|
||||||
raidReward: z.boolean(),
|
raidReward: z.boolean(),
|
||||||
type: z.literal('tome'),
|
type: z.literal('tome'),
|
||||||
restrictions: WynnItemRestrictions.optional(),
|
restrictions: WynnItemRestrictions.optional(),
|
||||||
@ -157,13 +146,12 @@ export const WapiV3ItemCharm = z.object({
|
|||||||
rarity: WynnItemRarity,
|
rarity: WynnItemRarity,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const WapiV3ItemAccessory = z.object({
|
export const WapiV3ItemAccessory = z
|
||||||
|
.object({
|
||||||
internalName: z.string(),
|
internalName: z.string(),
|
||||||
type: z.literal('accessory'),
|
type: z.literal('accessory'),
|
||||||
identified: z.boolean().optional(),
|
identified: z.boolean().optional(),
|
||||||
accessoryType: z.enum([
|
accessoryType: z.enum(['ring', 'necklace', 'bracelet']),
|
||||||
"ring","necklace","bracelet",
|
|
||||||
]),
|
|
||||||
majorIds: z.record(z.string(), z.string()).optional(),
|
majorIds: z.record(z.string(), z.string()).optional(),
|
||||||
restrictions: WynnItemRestrictions.optional(),
|
restrictions: WynnItemRestrictions.optional(),
|
||||||
dropMeta: WynnDropMeta.optional(),
|
dropMeta: WynnDropMeta.optional(),
|
||||||
@ -174,23 +162,16 @@ export const WapiV3ItemAccessory = z.object({
|
|||||||
identifications: WynnIdentifications.optional(),
|
identifications: WynnIdentifications.optional(),
|
||||||
base: WynnBaseStats.optional(),
|
base: WynnBaseStats.optional(),
|
||||||
rarity: WynnItemRarity,
|
rarity: WynnItemRarity,
|
||||||
}).strict()
|
})
|
||||||
|
.strict()
|
||||||
|
|
||||||
export const WapiV3ItemIngredient = z.object({
|
export const WapiV3ItemIngredient = z
|
||||||
|
.object({
|
||||||
internalName: z.string(),
|
internalName: z.string(),
|
||||||
type: z.literal('ingredient'),
|
type: z.literal('ingredient'),
|
||||||
requirements: z.object({
|
requirements: z.object({
|
||||||
level: z.number(),
|
level: z.number(),
|
||||||
skills: z.array(z.enum([
|
skills: z.array(z.enum(['alchemism', 'armouring', 'cooking', 'jeweling', 'scribing', 'tailoring', 'weaponsmithing', 'woodworking'])),
|
||||||
"alchemism",
|
|
||||||
"armouring",
|
|
||||||
"cooking",
|
|
||||||
"jeweling",
|
|
||||||
"scribing",
|
|
||||||
"tailoring",
|
|
||||||
"weaponsmithing",
|
|
||||||
"woodworking",
|
|
||||||
])),
|
|
||||||
}),
|
}),
|
||||||
icon: WynnItemIcon,
|
icon: WynnItemIcon,
|
||||||
identifications: WynnIdentifications.optional(),
|
identifications: WynnIdentifications.optional(),
|
||||||
@ -198,43 +179,58 @@ export const WapiV3ItemIngredient = z.object({
|
|||||||
consumableOnlyIDs: z.record(z.string(), z.number()),
|
consumableOnlyIDs: z.record(z.string(), z.number()),
|
||||||
ingredientPositionModifiers: z.record(z.string(), z.number()),
|
ingredientPositionModifiers: z.record(z.string(), z.number()),
|
||||||
itemOnlyIDs: 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(),
|
name: z.string(),
|
||||||
coords: z.union([
|
coords: z.union([z.boolean(), z.array(z.number()).length(4), z.array(z.array(z.number()).length(4))]).nullable(),
|
||||||
z.boolean(),
|
})
|
||||||
z.array(z.number()).length(4),
|
)
|
||||||
z.array(z.array(z.number()).length(4)),
|
.optional(),
|
||||||
]).nullable()
|
})
|
||||||
})).optional()
|
.strict()
|
||||||
}).strict()
|
|
||||||
|
|
||||||
export const WapiV3ItemMaterial = z.object({
|
export const WapiV3ItemMaterial = z
|
||||||
|
.object({
|
||||||
internalName: z.string(),
|
internalName: z.string(),
|
||||||
type: z.literal('material'),
|
type: z.literal('material'),
|
||||||
identified: z.boolean(),
|
identified: z.boolean(),
|
||||||
requirements: z.object({
|
requirements: z.object({
|
||||||
level: z.number(),
|
level: z.number(),
|
||||||
}),
|
}),
|
||||||
craftable: z.array(z.enum([
|
craftable: z.array(
|
||||||
'potions','food','scrolls',
|
z.enum([
|
||||||
'helmets','chestplates','rings','bracelets',
|
'potions',
|
||||||
'necklaces','boots','leggings','bows','wands','spears',
|
'food',
|
||||||
'daggers','chestplates','helmets'])),
|
'scrolls',
|
||||||
|
'helmets',
|
||||||
|
'chestplates',
|
||||||
|
'rings',
|
||||||
|
'bracelets',
|
||||||
|
'necklaces',
|
||||||
|
'boots',
|
||||||
|
'leggings',
|
||||||
|
'bows',
|
||||||
|
'wands',
|
||||||
|
'spears',
|
||||||
|
'daggers',
|
||||||
|
'chestplates',
|
||||||
|
'helmets',
|
||||||
|
])
|
||||||
|
),
|
||||||
icon: WynnItemIcon,
|
icon: WynnItemIcon,
|
||||||
tier: z.number(),
|
tier: z.number(),
|
||||||
}).strict()
|
})
|
||||||
|
.strict()
|
||||||
|
|
||||||
export const WapiV3ItemWeapon = z.object({
|
export const WapiV3ItemWeapon = z
|
||||||
|
.object({
|
||||||
internalName: z.string(),
|
internalName: z.string(),
|
||||||
type: z.literal('weapon'),
|
type: z.literal('weapon'),
|
||||||
identified: z.boolean().optional(),
|
identified: z.boolean().optional(),
|
||||||
allowCraftsman: z.boolean().optional(),
|
allowCraftsman: z.boolean().optional(),
|
||||||
weaponType: z.enum([
|
weaponType: z.enum(['bow', 'relik', 'wand', 'dagger', 'spear']),
|
||||||
"bow","relik","wand","dagger","spear"
|
attackSpeed: z.enum(['super_slow', 'very_slow', 'slow', 'normal', 'fast', 'very_fast', 'super_fast']),
|
||||||
]),
|
|
||||||
attackSpeed: z.enum([
|
|
||||||
"super_slow", "very_slow", "slow","normal","fast", "very_fast","super_fast"
|
|
||||||
]),
|
|
||||||
powderSlots: z.number().optional(),
|
powderSlots: z.number().optional(),
|
||||||
averageDps: z.number().optional(),
|
averageDps: z.number().optional(),
|
||||||
restrictions: WynnItemRestrictions.optional(),
|
restrictions: WynnItemRestrictions.optional(),
|
||||||
@ -247,10 +243,11 @@ export const WapiV3ItemWeapon = z.object({
|
|||||||
identifications: WynnIdentifications.optional(),
|
identifications: WynnIdentifications.optional(),
|
||||||
base: WynnBaseStats.optional(),
|
base: WynnBaseStats.optional(),
|
||||||
rarity: WynnItemRarity,
|
rarity: WynnItemRarity,
|
||||||
}).strict()
|
})
|
||||||
|
.strict()
|
||||||
|
|
||||||
|
export const WapiV3ItemArmour = z
|
||||||
export const WapiV3ItemArmour = z.object({
|
.object({
|
||||||
internalName: z.string(),
|
internalName: z.string(),
|
||||||
type: z.literal('armour'),
|
type: z.literal('armour'),
|
||||||
armourType: z.string(),
|
armourType: z.string(),
|
||||||
@ -274,19 +271,25 @@ export const WapiV3ItemArmour = z.object({
|
|||||||
majorIds: z.record(z.string(), z.string()).optional(),
|
majorIds: z.record(z.string(), z.string()).optional(),
|
||||||
powderSlots: z.number().optional(),
|
powderSlots: z.number().optional(),
|
||||||
lore: z.string().optional(),
|
lore: z.string().optional(),
|
||||||
identifications: z.record(z.string(), z.union([
|
identifications: z
|
||||||
|
.record(
|
||||||
|
z.string(),
|
||||||
|
z.union([
|
||||||
z.number(),
|
z.number(),
|
||||||
z.object({
|
z.object({
|
||||||
min: z.number(),
|
min: z.number(),
|
||||||
raw: z.number(),
|
raw: z.number(),
|
||||||
max: z.number(),
|
max: z.number(),
|
||||||
})
|
}),
|
||||||
])).optional(),
|
])
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
base: WynnBaseStats.optional(),
|
base: WynnBaseStats.optional(),
|
||||||
rarity: WynnItemRarity,
|
rarity: WynnItemRarity,
|
||||||
}).strict()
|
})
|
||||||
|
.strict()
|
||||||
|
|
||||||
export const WApiV3Item = z.discriminatedUnion("type",[
|
export const WApiV3Item = z.discriminatedUnion('type', [
|
||||||
WapiV3ItemMaterial,
|
WapiV3ItemMaterial,
|
||||||
WapiV3ItemWeapon,
|
WapiV3ItemWeapon,
|
||||||
WapiV3ItemArmour,
|
WapiV3ItemArmour,
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import { config } from "#/config";
|
import { inject, injectable } from '@needle-di/core'
|
||||||
import { inject, injectable } from "@needle-di/core";
|
import axios, { type AxiosInstance } from 'axios'
|
||||||
import axios, { AxiosInstance } from "axios";
|
import { buildStorage, canStale, setupCache } from 'axios-cache-interceptor'
|
||||||
import { buildStorage, canStale, setupCache } from 'axios-cache-interceptor';
|
import { BentoCache } from 'bentocache'
|
||||||
import { BentoCache } from "bentocache";
|
import { config } from '#/config'
|
||||||
|
|
||||||
import "#/services/bento";
|
|
||||||
import { logger } from "#/logger";
|
|
||||||
|
|
||||||
|
import '#/services/bento'
|
||||||
|
import { logger } from '#/logger'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class WApi {
|
export class WApi {
|
||||||
@ -14,18 +13,14 @@ export class WApi {
|
|||||||
|
|
||||||
private readonly log = logger.child({ module: 'wapi' })
|
private readonly log = logger.child({ module: 'wapi' })
|
||||||
|
|
||||||
constructor(
|
constructor(private readonly bento = inject(BentoCache)) {
|
||||||
private readonly bento = inject(BentoCache)
|
|
||||||
) {
|
|
||||||
const c = axios.create({
|
const c = axios.create({
|
||||||
baseURL: config.WAPI_URL,
|
baseURL: config.WAPI_URL,
|
||||||
headers: {
|
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 store = this.bento.namespace('wapi-cache')
|
||||||
|
|
||||||
const self = this
|
|
||||||
setupCache(c, {
|
setupCache(c, {
|
||||||
interpretHeader: true,
|
interpretHeader: true,
|
||||||
ttl: 5000,
|
ttl: 5000,
|
||||||
@ -33,7 +28,7 @@ export class WApi {
|
|||||||
async find(key, currentRequest) {
|
async find(key, currentRequest) {
|
||||||
const value = await store.get({ key })
|
const value = await store.get({ key })
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
return JSON.parse(value)
|
return JSON.parse(value)
|
||||||
},
|
},
|
||||||
@ -41,18 +36,13 @@ export class WApi {
|
|||||||
await store.delete({ key })
|
await store.delete({ key })
|
||||||
},
|
},
|
||||||
async set(key, value, req) {
|
async set(key, value, req) {
|
||||||
let expireTime = value.state === 'loading'
|
const expireTime =
|
||||||
? Date.now() +
|
value.state === 'loading'
|
||||||
(req?.cache && typeof req.cache.ttl === 'number'
|
? Date.now() + (req?.cache && typeof req.cache.ttl === 'number' ? req.cache.ttl : 3000)
|
||||||
? req.cache.ttl
|
|
||||||
:
|
|
||||||
3000)
|
|
||||||
: // When a stale state has a determined value to expire, we can use it.
|
: // When a stale state has a determined value to expire, we can use it.
|
||||||
// Or if the cached value cannot enter in stale state.
|
// Or if the cached value cannot enter in stale state.
|
||||||
(value.state === 'stale' && value.ttl) ||
|
(value.state === 'stale' && value.ttl) || (value.state === 'cached' && !canStale(value))
|
||||||
(value.state === 'cached' && !canStale(value))
|
? value.createdAt + value.ttl!
|
||||||
?
|
|
||||||
value.createdAt + value.ttl!
|
|
||||||
: // otherwise, we can't determine when it should expire, so we keep
|
: // otherwise, we can't determine when it should expire, so we keep
|
||||||
// it indefinitely.
|
// it indefinitely.
|
||||||
undefined
|
undefined
|
||||||
@ -70,20 +60,18 @@ export class WApi {
|
|||||||
async clear() {
|
async clear() {
|
||||||
await store.clear({})
|
await store.clear({})
|
||||||
},
|
},
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
});
|
this.c = c
|
||||||
this.c = c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(path: string, params?: any) {
|
async get(path: string, params?: any) {
|
||||||
return this.c.get(path, {
|
return this.c.get(path, {
|
||||||
params,
|
params,
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
Accept: 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { pino } from 'pino'
|
import { pino } from 'pino'
|
||||||
|
|
||||||
|
|
||||||
export const logger = pino({
|
export const logger = pino({
|
||||||
transport: {
|
transport: {
|
||||||
target: 'pino-logfmt',
|
target: 'pino-logfmt',
|
||||||
@ -8,4 +7,4 @@ export const logger = pino({
|
|||||||
level: process.env.PINO_LOG_LEVEL || 'info',
|
level: process.env.PINO_LOG_LEVEL || 'info',
|
||||||
|
|
||||||
redact: [], // prevent logging of sensitive data
|
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 {
|
import { type EncodingType, METADATA_ENCODING_KEY, type Payload, PayloadConverterError, type PayloadConverterWithEncoding } from '@temporalio/common'
|
||||||
EncodingType,
|
import { decode, encode } from '@temporalio/common/lib/encoding'
|
||||||
METADATA_ENCODING_KEY,
|
import { errorMessage } from '@temporalio/common/lib/type-helpers'
|
||||||
Payload,
|
import superjson from 'superjson'
|
||||||
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';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts between values and [superjson](https://github.com/flightcontrolhq/superjson) Payloads.
|
* Converts between values and [superjson](https://github.com/flightcontrolhq/superjson) Payloads.
|
||||||
*/
|
*/
|
||||||
export class SuperJsonPayloadConverter implements PayloadConverterWithEncoding {
|
export class SuperJsonPayloadConverter implements PayloadConverterWithEncoding {
|
||||||
// Use 'json/plain' so that Payloads are displayed in the UI
|
// 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 {
|
public toPayload(value: unknown): Payload | undefined {
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined
|
||||||
let ejson;
|
let ejson
|
||||||
try {
|
try {
|
||||||
ejson = superjson.stringify(value);
|
ejson = superjson.stringify(value)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new UnsupportedSuperJsonTypeError(
|
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: ${
|
`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(
|
||||||
errorMessage(
|
e
|
||||||
e,
|
)}`,
|
||||||
|
e as Error
|
||||||
)
|
)
|
||||||
}`,
|
|
||||||
e as Error,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -41,21 +31,21 @@ export class SuperJsonPayloadConverter implements PayloadConverterWithEncoding {
|
|||||||
format: encode('extended'),
|
format: encode('extended'),
|
||||||
},
|
},
|
||||||
data: encode(ejson),
|
data: encode(ejson),
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fromPayload<T>(content: Payload): T {
|
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 {
|
export class UnsupportedSuperJsonTypeError extends PayloadConverterError {
|
||||||
public readonly name: string = 'UnsupportedJsonTypeError';
|
public readonly name: string = 'UnsupportedJsonTypeError'
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
message: string | undefined,
|
message: string | undefined,
|
||||||
public readonly cause?: Error,
|
public readonly cause?: Error
|
||||||
) {
|
) {
|
||||||
super(message ?? undefined);
|
super(message ?? undefined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,4 @@
|
|||||||
import {
|
import { CompositePayloadConverter, UndefinedPayloadConverter } from '@temporalio/common'
|
||||||
CompositePayloadConverter,
|
import { SuperJsonPayloadConverter } from './adapter'
|
||||||
UndefinedPayloadConverter,
|
|
||||||
} from '@temporalio/common';
|
|
||||||
import { SuperJsonPayloadConverter } from './adapter';
|
|
||||||
|
|
||||||
export const payloadConverter = new CompositePayloadConverter(
|
export const payloadConverter = new CompositePayloadConverter(new UndefinedPayloadConverter(), new SuperJsonPayloadConverter())
|
||||||
new UndefinedPayloadConverter(),
|
|
||||||
new SuperJsonPayloadConverter(),
|
|
||||||
);
|
|
||||||
|
|||||||
@ -1,31 +1,29 @@
|
|||||||
import { config } from '#/config'
|
|
||||||
import { c } from '#/di'
|
|
||||||
import { BentoCache, bentostore } from 'bentocache'
|
import { BentoCache, bentostore } from 'bentocache'
|
||||||
import { memoryDriver } from 'bentocache/drivers/memory'
|
import { memoryDriver } from 'bentocache/drivers/memory'
|
||||||
import { redisDriver } from 'bentocache/drivers/redis'
|
import { redisDriver } from 'bentocache/drivers/redis'
|
||||||
import IORedis from 'ioredis'
|
import IORedis from 'ioredis'
|
||||||
|
import { config } from '#/config'
|
||||||
|
import { c } from '#/di'
|
||||||
|
|
||||||
c.bind({
|
c.bind({
|
||||||
provide: BentoCache,
|
provide: BentoCache,
|
||||||
useFactory: () => {
|
useFactory: () => {
|
||||||
|
|
||||||
const defaultStore = bentostore()
|
const defaultStore = bentostore()
|
||||||
defaultStore.useL1Layer(memoryDriver({ maxSize: '32mb' }))
|
defaultStore.useL1Layer(memoryDriver({ maxSize: '32mb' }))
|
||||||
if (config.REDIS_URL) {
|
if (config.REDIS_URL) {
|
||||||
defaultStore.useL2Layer(redisDriver({
|
defaultStore.useL2Layer(
|
||||||
|
redisDriver({
|
||||||
connection: new IORedis(config.REDIS_URL),
|
connection: new IORedis(config.REDIS_URL),
|
||||||
prefix: 'wynn-bento',
|
prefix: 'wynn-bento',
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
const bento = new BentoCache({
|
const bento = new BentoCache({
|
||||||
default: 'cache',
|
default: 'cache',
|
||||||
stores: {
|
stores: {
|
||||||
cache: defaultStore,
|
cache: defaultStore,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
return bento
|
return bento
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { config } from "#/config";
|
import { injectable } from '@needle-di/core'
|
||||||
import { injectable } from "@needle-di/core";
|
import postgres, { type Sql } from 'postgres'
|
||||||
import postgres, { Sql } from "postgres";
|
import { config } from '#/config'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class PG {
|
export class PG {
|
||||||
readonly db: Sql;
|
readonly db: Sql
|
||||||
get sql() {
|
get sql() {
|
||||||
return this.db
|
return this.db
|
||||||
}
|
}
|
||||||
@ -13,9 +13,9 @@ export class PG {
|
|||||||
const opts = {
|
const opts = {
|
||||||
onnotice: () => {},
|
onnotice: () => {},
|
||||||
}
|
}
|
||||||
let db: Sql;
|
let db: Sql
|
||||||
if (config.PG_URL) {
|
if (config.PG_URL) {
|
||||||
db = postgres(config.PG_URL, opts);
|
db = postgres(config.PG_URL, opts)
|
||||||
} else {
|
} else {
|
||||||
db = postgres({
|
db = postgres({
|
||||||
host: config.PG_HOST,
|
host: config.PG_HOST,
|
||||||
@ -30,4 +30,3 @@ export class PG {
|
|||||||
this.db = db
|
this.db = db
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { config } from "#/config";
|
import { Client, Connection } from '@temporalio/client'
|
||||||
import { c } from "#/di";
|
import { config } from '#/config'
|
||||||
import { Client, Connection} from '@temporalio/client';
|
import { c } from '#/di'
|
||||||
|
|
||||||
c.bind({
|
c.bind({
|
||||||
provide: Client,
|
provide: Client,
|
||||||
@ -15,11 +15,11 @@ c.bind({
|
|||||||
dataConverter: {
|
dataConverter: {
|
||||||
payloadConverterPath: require.resolve('../../payload_converter'),
|
payloadConverterPath: require.resolve('../../payload_converter'),
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
process.on('exit', () => {
|
process.on('exit', () => {
|
||||||
console.log('closing temporal client');
|
console.log('closing temporal client')
|
||||||
client.connection.close();
|
client.connection.close()
|
||||||
});
|
})
|
||||||
return client
|
return client
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|||||||
@ -1,21 +1,19 @@
|
|||||||
import { proxyActivities, startChild, workflowInfo } from '@temporalio/workflow';
|
import { InteractionTypes } from '@discordeno/types'
|
||||||
import type * as activities from '#/activities';
|
import { proxyActivities, startChild, workflowInfo } from '@temporalio/workflow'
|
||||||
import { InteractionTypes } from '@discordeno/types';
|
import type * as activities from '#/activities'
|
||||||
import { handleCommandGuildInfo, handleCommandGuildOnline, handleCommandGuildLeaderboard } from './guild_messages';
|
import type { InteractionCreatePayload } from '#/discord'
|
||||||
import { handleCommandPlayerLookup } from './player_messages';
|
import { createCommandHandler } from '#/discord/botevent/command_parser'
|
||||||
import { createCommandHandler } from '#/discord/botevent/command_parser';
|
import type { SLASH_COMMANDS } from '#/discord/botevent/slash_commands'
|
||||||
import { SLASH_COMMANDS } from '#/discord/botevent/slash_commands';
|
import { handleCommandGuildInfo, handleCommandGuildLeaderboard, handleCommandGuildOnline } from './guild_messages'
|
||||||
import { InteractionCreatePayload} from '#/discord';
|
import { handleCommandPlayerLookup } from './player_messages'
|
||||||
|
|
||||||
const { reply_to_interaction } = proxyActivities<typeof activities>({
|
const { reply_to_interaction } = proxyActivities<typeof activities>({
|
||||||
startToCloseTimeout: '1 minute',
|
startToCloseTimeout: '1 minute',
|
||||||
});
|
})
|
||||||
|
|
||||||
// Define command handlers with type safety
|
// Define command handlers with type safety
|
||||||
const workflowHandleApplicationCommand = async (
|
const workflowHandleApplicationCommand = async (payload: InteractionCreatePayload) => {
|
||||||
payload: InteractionCreatePayload,
|
const { ref, data } = payload
|
||||||
) => {
|
|
||||||
const { ref, data } = payload;
|
|
||||||
|
|
||||||
const notFoundHandler = async (content: string) => {
|
const notFoundHandler = async (content: string) => {
|
||||||
await reply_to_interaction({
|
await reply_to_interaction({
|
||||||
@ -24,52 +22,52 @@ const workflowHandleApplicationCommand = async (
|
|||||||
options: {
|
options: {
|
||||||
content: content,
|
content: content,
|
||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
if (!data || !data.name) {
|
if (!data || !data.name) {
|
||||||
await notFoundHandler(`Invalid command data`);
|
await notFoundHandler(`Invalid command data`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const commandHandler = createCommandHandler<typeof SLASH_COMMANDS>({
|
const commandHandler = createCommandHandler<typeof SLASH_COMMANDS>({
|
||||||
notFoundHandler: async () => {
|
notFoundHandler: async () => {
|
||||||
await notFoundHandler(`command not found`);
|
await notFoundHandler(`command not found`)
|
||||||
},
|
},
|
||||||
handler: {
|
handler: {
|
||||||
player: {
|
player: {
|
||||||
lookup: async (args) => {
|
lookup: async (args) => {
|
||||||
const { workflowId } = workflowInfo();
|
const { workflowId } = workflowInfo()
|
||||||
const handle = await startChild(handleCommandPlayerLookup, {
|
const handle = await startChild(handleCommandPlayerLookup, {
|
||||||
args: [{ ref, args }],
|
args: [{ ref, args }],
|
||||||
workflowId: `${workflowId}-player-lookup`,
|
workflowId: `${workflowId}-player-lookup`,
|
||||||
});
|
})
|
||||||
await handle.result();
|
await handle.result()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
guild: {
|
guild: {
|
||||||
info: async (args) => {
|
info: async (args) => {
|
||||||
const { workflowId } = workflowInfo();
|
const { workflowId } = workflowInfo()
|
||||||
const handle = await startChild(handleCommandGuildInfo, {
|
const handle = await startChild(handleCommandGuildInfo, {
|
||||||
args: [{ ref }],
|
args: [{ ref }],
|
||||||
workflowId: `${workflowId}-guild-info`,
|
workflowId: `${workflowId}-guild-info`,
|
||||||
});
|
})
|
||||||
await handle.result();
|
await handle.result()
|
||||||
},
|
},
|
||||||
online: async (args) => {
|
online: async (args) => {
|
||||||
const { workflowId } = workflowInfo();
|
const { workflowId } = workflowInfo()
|
||||||
const handle = await startChild(handleCommandGuildOnline, {
|
const handle = await startChild(handleCommandGuildOnline, {
|
||||||
args: [{ ref }],
|
args: [{ ref }],
|
||||||
workflowId: `${workflowId}-guild-online`,
|
workflowId: `${workflowId}-guild-online`,
|
||||||
});
|
})
|
||||||
await handle.result();
|
await handle.result()
|
||||||
},
|
},
|
||||||
leaderboard: async (args) => {
|
leaderboard: async (args) => {
|
||||||
const { workflowId } = workflowInfo();
|
const { workflowId } = workflowInfo()
|
||||||
const handle = await startChild(handleCommandGuildLeaderboard, {
|
const handle = await startChild(handleCommandGuildLeaderboard, {
|
||||||
args: [{ ref }],
|
args: [{ ref }],
|
||||||
workflowId: `${workflowId}-guild-leaderboard`,
|
workflowId: `${workflowId}-guild-leaderboard`,
|
||||||
});
|
})
|
||||||
await handle.result();
|
await handle.result()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
admin: {
|
admin: {
|
||||||
@ -78,21 +76,19 @@ const workflowHandleApplicationCommand = async (
|
|||||||
ref,
|
ref,
|
||||||
type: 4,
|
type: 4,
|
||||||
options: {
|
options: {
|
||||||
content: "Not implemented yet",
|
content: 'Not implemented yet',
|
||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
await commandHandler(data);
|
await commandHandler(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const workflowHandleInteractionCreate = async (
|
export const workflowHandleInteractionCreate = async (payload: InteractionCreatePayload) => {
|
||||||
payload: InteractionCreatePayload,
|
|
||||||
) => {
|
|
||||||
const { ref, data } = payload
|
const { ref, data } = payload
|
||||||
|
|
||||||
if (ref.type === InteractionTypes.ApplicationCommand) {
|
if (ref.type === InteractionTypes.ApplicationCommand) {
|
||||||
|
|||||||
@ -1,47 +1,42 @@
|
|||||||
import { proxyActivities } from "@temporalio/workflow";
|
import { proxyActivities } from '@temporalio/workflow'
|
||||||
import type * as activities from "#/activities";
|
import type * as activities from '#/activities'
|
||||||
import { WYNN_GUILD_ID } from "#/constants";
|
import { WYNN_GUILD_ID } from '#/constants'
|
||||||
import { InteractionRef } from "#/discord";
|
import type { InteractionRef } from '#/discord'
|
||||||
|
|
||||||
const {
|
const { formGuildInfoMessage, formGuildOnlineMessage, formGuildLeaderboardMessage, reply_to_interaction } = proxyActivities<typeof activities>({
|
||||||
formGuildInfoMessage,
|
|
||||||
formGuildOnlineMessage,
|
|
||||||
formGuildLeaderboardMessage,
|
|
||||||
reply_to_interaction
|
|
||||||
} = proxyActivities<typeof activities>({
|
|
||||||
startToCloseTimeout: '30 seconds',
|
startToCloseTimeout: '30 seconds',
|
||||||
});
|
})
|
||||||
|
|
||||||
interface CommandPayload {
|
interface CommandPayload {
|
||||||
ref: InteractionRef;
|
ref: InteractionRef
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleCommandGuildInfo(payload: CommandPayload): Promise<void> {
|
export async function handleCommandGuildInfo(payload: CommandPayload): Promise<void> {
|
||||||
const { ref } = payload;
|
const { ref } = payload
|
||||||
const msg = await formGuildInfoMessage(WYNN_GUILD_ID);
|
const msg = await formGuildInfoMessage(WYNN_GUILD_ID)
|
||||||
await reply_to_interaction({
|
await reply_to_interaction({
|
||||||
ref,
|
ref,
|
||||||
type: 4,
|
type: 4,
|
||||||
options: msg,
|
options: msg,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleCommandGuildOnline(payload: CommandPayload): Promise<void> {
|
export async function handleCommandGuildOnline(payload: CommandPayload): Promise<void> {
|
||||||
const { ref } = payload;
|
const { ref } = payload
|
||||||
const msg = await formGuildOnlineMessage(WYNN_GUILD_ID);
|
const msg = await formGuildOnlineMessage(WYNN_GUILD_ID)
|
||||||
await reply_to_interaction({
|
await reply_to_interaction({
|
||||||
ref,
|
ref,
|
||||||
type: 4,
|
type: 4,
|
||||||
options: msg,
|
options: msg,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleCommandGuildLeaderboard(payload: CommandPayload): Promise<void> {
|
export async function handleCommandGuildLeaderboard(payload: CommandPayload): Promise<void> {
|
||||||
const { ref } = payload;
|
const { ref } = payload
|
||||||
const msg = await formGuildLeaderboardMessage(WYNN_GUILD_ID);
|
const msg = await formGuildLeaderboardMessage(WYNN_GUILD_ID)
|
||||||
await reply_to_interaction({
|
await reply_to_interaction({
|
||||||
ref,
|
ref,
|
||||||
type: 4,
|
type: 4,
|
||||||
options: msg,
|
options: msg,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
@ -1,10 +1,9 @@
|
|||||||
|
import { proxyActivities } from '@temporalio/workflow'
|
||||||
import { proxyActivities } from '@temporalio/workflow';
|
import type * as activities from '#/activities'
|
||||||
import type * as activities from '#/activities';
|
|
||||||
|
|
||||||
const { update_guild, update_all_guilds, update_guild_levels } = proxyActivities<typeof activities>({
|
const { update_guild, update_all_guilds, update_guild_levels } = proxyActivities<typeof activities>({
|
||||||
startToCloseTimeout: '1 minute',
|
startToCloseTimeout: '1 minute',
|
||||||
});
|
})
|
||||||
|
|
||||||
export const workflowSyncAllGuilds = async () => {
|
export const workflowSyncAllGuilds = async () => {
|
||||||
await update_all_guilds()
|
await update_all_guilds()
|
||||||
@ -16,9 +15,7 @@ export const workflowSyncGuildLeaderboardInfo = async() => {
|
|||||||
|
|
||||||
export const workflowSyncGuilds = async () => {
|
export const workflowSyncGuilds = async () => {
|
||||||
// TODO side effect
|
// TODO side effect
|
||||||
const guildNames = [
|
const guildNames = ['less than three']
|
||||||
'less than three',
|
|
||||||
]
|
|
||||||
for (const guildName of guildNames) {
|
for (const guildName of guildNames) {
|
||||||
// update the guild
|
// update the guild
|
||||||
await update_guild({
|
await update_guild({
|
||||||
@ -26,4 +23,3 @@ export const workflowSyncGuilds = async() => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
* @file Automatically generated by barrelsby.
|
* @file Automatically generated by barrelsby.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from "./discord";
|
export * from './discord'
|
||||||
export * from "./guild_messages";
|
export * from './guild_messages'
|
||||||
export * from "./guilds";
|
export * from './guilds'
|
||||||
export * from "./items";
|
export * from './items'
|
||||||
export * from "./player_messages";
|
export * from './player_messages'
|
||||||
export * from "./players";
|
export * from './players'
|
||||||
|
|||||||
@ -1,15 +1,13 @@
|
|||||||
|
import { proxyActivities } from '@temporalio/workflow'
|
||||||
import { proxyActivities } from '@temporalio/workflow';
|
import type * as activities from '#/activities'
|
||||||
import type * as activities from '#/activities';
|
|
||||||
|
|
||||||
const { update_wynn_items } = proxyActivities<typeof activities>({
|
const { update_wynn_items } = proxyActivities<typeof activities>({
|
||||||
startToCloseTimeout: '1 minute',
|
startToCloseTimeout: '1 minute',
|
||||||
retry: {
|
retry: {
|
||||||
maximumAttempts: 1,
|
maximumAttempts: 1,
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
export const workflowSyncItemDatabase = async () => {
|
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 { proxyActivities } from '@temporalio/workflow'
|
||||||
import type * as activities from "#/activities";
|
import type * as activities from '#/activities'
|
||||||
import { InteractionRef } from "#/discord";
|
import type { InteractionRef } from '#/discord'
|
||||||
|
|
||||||
const {
|
const { reply_to_interaction } = proxyActivities<typeof activities>({
|
||||||
reply_to_interaction
|
|
||||||
} = proxyActivities<typeof activities>({
|
|
||||||
startToCloseTimeout: '30 seconds',
|
startToCloseTimeout: '30 seconds',
|
||||||
});
|
})
|
||||||
|
|
||||||
interface CommandPayload {
|
interface CommandPayload {
|
||||||
ref: InteractionRef;
|
ref: InteractionRef
|
||||||
args: {
|
args: {
|
||||||
player: string;
|
player: string
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleCommandPlayerLookup(payload: CommandPayload): Promise<void> {
|
export async function handleCommandPlayerLookup(payload: CommandPayload): Promise<void> {
|
||||||
const { ref, args } = payload;
|
const { ref, args } = payload
|
||||||
const playerName = args.player;
|
const playerName = args.player
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// For now, we'll send a simple response
|
// 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!*`,
|
content: `Looking up player: **${playerName}**\n\n*Player lookup functionality coming soon!*`,
|
||||||
isPrivate: false,
|
isPrivate: false,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await reply_to_interaction({
|
await reply_to_interaction({
|
||||||
ref,
|
ref,
|
||||||
@ -38,6 +36,6 @@ export async function handleCommandPlayerLookup(payload: CommandPayload): Promis
|
|||||||
content: `Error looking up player: ${playerName}`,
|
content: `Error looking up player: ${playerName}`,
|
||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,14 +1,13 @@
|
|||||||
|
import { proxyActivities } from '@temporalio/workflow'
|
||||||
import { proxyActivities } from '@temporalio/workflow';
|
import type * as activities from '#/activities'
|
||||||
import type * as activities from '#/activities';
|
|
||||||
|
|
||||||
const { scrape_online_players } = proxyActivities<typeof activities>({
|
const { scrape_online_players } = proxyActivities<typeof activities>({
|
||||||
startToCloseTimeout: '1 minute',
|
startToCloseTimeout: '1 minute',
|
||||||
retry: {
|
retry: {
|
||||||
maximumAttempts: 1,
|
maximumAttempts: 1,
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
export const workflowSyncOnline = async () => {
|
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
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@boringnode/bus@npm:^0.7.1":
|
||||||
version: 0.7.1
|
version: 0.7.1
|
||||||
resolution: "@boringnode/bus@npm:0.7.1"
|
resolution: "@boringnode/bus@npm:0.7.1"
|
||||||
@ -1168,27 +1259,25 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@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.52.0
|
version: 3.53.0-rc.1
|
||||||
resolution: "@ts-rest/core@https://gitpkg.vercel.app/aidant/ts-rest/libs/ts-rest/core?feat-standard-schema"
|
resolution: "@ts-rest/core@npm:3.53.0-rc.1"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@types/node": ^18.18.7 || >=20.8.4
|
"@types/node": ^18.18.7 || >=20.8.4
|
||||||
zod: ^3.24.0
|
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
"@types/node":
|
"@types/node":
|
||||||
optional: true
|
optional: true
|
||||||
zod:
|
checksum: 10c0/a1a8c304f797da016ad968878a47c75fb0dcb7811c6f8e2ae81a3f9f3dedba30c5b5c8b14668437c136c9eca9b361e7048117478330bd449a2fbbc53f84f73cb
|
||||||
optional: true
|
|
||||||
checksum: 10c0/fba55ca7d1a5161d3ec1af850c9c98d34efd1b4373ad8ca4108737c2d75fa11ef033da6e9cb657cd5078465c09ff6761c029053f9c59022075b8d3e128f298a5
|
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@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.52.0
|
version: 3.53.0-rc.1
|
||||||
resolution: "@ts-rest/fastify@https://gitpkg.vercel.app/aidant/ts-rest/libs/ts-rest/fastify?feat-standard-schema"
|
resolution: "@ts-rest/fastify@npm:3.53.0-rc.1"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
"@ts-rest/core": 3.53.0-rc.1
|
||||||
fastify: ^4.0.0
|
fastify: ^4.0.0
|
||||||
checksum: 10c0/8e8a31fda5a49c4fc976962df29129a24ad3c9c4896af38a6deb95bf60e36943c29bef56ff601bb63cab883d845095c7793ae266e92876cf74a7a05d238ba00f
|
checksum: 10c0/5d935903743e457873036dc943538b8603081e1d4a1f28bb7b79b0f1a6cee8ddcd4aef839318f5f00c7b24f38491e68977aa8ef41b2665f4702d47eeba0cee9e
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -1799,6 +1888,7 @@ __metadata:
|
|||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "backend@workspace:."
|
resolution: "backend@workspace:."
|
||||||
dependencies:
|
dependencies:
|
||||||
|
"@biomejs/biome": "npm:1.9.4"
|
||||||
"@discordeno/types": "npm:^21.0.0"
|
"@discordeno/types": "npm:^21.0.0"
|
||||||
"@needle-di/core": "npm:^0.10.1"
|
"@needle-di/core": "npm:^0.10.1"
|
||||||
"@temporalio/activity": "npm:^1.11.7"
|
"@temporalio/activity": "npm:^1.11.7"
|
||||||
@ -1806,8 +1896,8 @@ __metadata:
|
|||||||
"@temporalio/common": "npm:^1.11.7"
|
"@temporalio/common": "npm:^1.11.7"
|
||||||
"@temporalio/worker": "npm:^1.11.7"
|
"@temporalio/worker": "npm:^1.11.7"
|
||||||
"@temporalio/workflow": "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/core": "npm:^3.53.0-rc.1"
|
||||||
"@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"
|
||||||
"@types/node": "npm:^22.13.4"
|
"@types/node": "npm:^22.13.4"
|
||||||
"@types/object-hash": "npm:^3"
|
"@types/object-hash": "npm:^3"
|
||||||
"@vitest/runner": "npm:^3.2.3"
|
"@vitest/runner": "npm:^3.2.3"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user