noot
This commit is contained in:
parent
4f89dd103b
commit
24c7c2b9b1
@ -1,160 +0,0 @@
|
|||||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
|
||||||
# yarn lockfile v1
|
|
||||||
|
|
||||||
|
|
||||||
call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
|
|
||||||
integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
|
|
||||||
dependencies:
|
|
||||||
es-errors "^1.3.0"
|
|
||||||
function-bind "^1.1.2"
|
|
||||||
|
|
||||||
call-bind@^1.0.8:
|
|
||||||
version "1.0.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c"
|
|
||||||
integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==
|
|
||||||
dependencies:
|
|
||||||
call-bind-apply-helpers "^1.0.0"
|
|
||||||
es-define-property "^1.0.0"
|
|
||||||
get-intrinsic "^1.2.4"
|
|
||||||
set-function-length "^1.2.2"
|
|
||||||
|
|
||||||
call-bound@^1.0.3:
|
|
||||||
version "1.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.3.tgz#41cfd032b593e39176a71533ab4f384aa04fd681"
|
|
||||||
integrity sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==
|
|
||||||
dependencies:
|
|
||||||
call-bind-apply-helpers "^1.0.1"
|
|
||||||
get-intrinsic "^1.2.6"
|
|
||||||
|
|
||||||
define-data-property@^1.1.4:
|
|
||||||
version "1.1.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
|
|
||||||
integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
|
|
||||||
dependencies:
|
|
||||||
es-define-property "^1.0.0"
|
|
||||||
es-errors "^1.3.0"
|
|
||||||
gopd "^1.0.1"
|
|
||||||
|
|
||||||
dunder-proto@^1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
|
|
||||||
integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
|
|
||||||
dependencies:
|
|
||||||
call-bind-apply-helpers "^1.0.1"
|
|
||||||
es-errors "^1.3.0"
|
|
||||||
gopd "^1.2.0"
|
|
||||||
|
|
||||||
es-define-property@^1.0.0, es-define-property@^1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
|
|
||||||
integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
|
|
||||||
|
|
||||||
es-errors@^1.3.0:
|
|
||||||
version "1.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
|
|
||||||
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
|
|
||||||
|
|
||||||
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
|
|
||||||
version "1.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1"
|
|
||||||
integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
|
|
||||||
dependencies:
|
|
||||||
es-errors "^1.3.0"
|
|
||||||
|
|
||||||
function-bind@^1.1.2:
|
|
||||||
version "1.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
|
|
||||||
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
|
|
||||||
|
|
||||||
get-intrinsic@^1.2.4, get-intrinsic@^1.2.6:
|
|
||||||
version "1.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
|
|
||||||
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
|
|
||||||
dependencies:
|
|
||||||
call-bind-apply-helpers "^1.0.2"
|
|
||||||
es-define-property "^1.0.1"
|
|
||||||
es-errors "^1.3.0"
|
|
||||||
es-object-atoms "^1.1.1"
|
|
||||||
function-bind "^1.1.2"
|
|
||||||
get-proto "^1.0.1"
|
|
||||||
gopd "^1.2.0"
|
|
||||||
has-symbols "^1.1.0"
|
|
||||||
hasown "^2.0.2"
|
|
||||||
math-intrinsics "^1.1.0"
|
|
||||||
|
|
||||||
get-proto@^1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
|
|
||||||
integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
|
|
||||||
dependencies:
|
|
||||||
dunder-proto "^1.0.1"
|
|
||||||
es-object-atoms "^1.0.0"
|
|
||||||
|
|
||||||
gopd@^1.0.1, gopd@^1.2.0:
|
|
||||||
version "1.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
|
|
||||||
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
|
|
||||||
|
|
||||||
has-property-descriptors@^1.0.2:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
|
|
||||||
integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
|
|
||||||
dependencies:
|
|
||||||
es-define-property "^1.0.0"
|
|
||||||
|
|
||||||
has-symbols@^1.1.0:
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
|
|
||||||
integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
|
|
||||||
|
|
||||||
hasown@^2.0.2:
|
|
||||||
version "2.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
|
|
||||||
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
|
|
||||||
dependencies:
|
|
||||||
function-bind "^1.1.2"
|
|
||||||
|
|
||||||
isarray@^2.0.5:
|
|
||||||
version "2.0.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
|
|
||||||
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
|
|
||||||
|
|
||||||
json-stable-stringify@^1.2.1:
|
|
||||||
version "1.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.2.1.tgz#addb683c2b78014d0b78d704c2fcbdf0695a60e2"
|
|
||||||
integrity sha512-Lp6HbbBgosLmJbjx0pBLbgvx68FaFU1sdkmBuckmhhJ88kL13OA51CDtR2yJB50eCNMH9wRqtQNNiAqQH4YXnA==
|
|
||||||
dependencies:
|
|
||||||
call-bind "^1.0.8"
|
|
||||||
call-bound "^1.0.3"
|
|
||||||
isarray "^2.0.5"
|
|
||||||
jsonify "^0.0.1"
|
|
||||||
object-keys "^1.1.1"
|
|
||||||
|
|
||||||
jsonify@^0.0.1:
|
|
||||||
version "0.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978"
|
|
||||||
integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==
|
|
||||||
|
|
||||||
math-intrinsics@^1.1.0:
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
|
|
||||||
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
|
|
||||||
|
|
||||||
object-keys@^1.1.1:
|
|
||||||
version "1.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
|
|
||||||
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
|
|
||||||
|
|
||||||
set-function-length@^1.2.2:
|
|
||||||
version "1.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
|
|
||||||
integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
|
|
||||||
dependencies:
|
|
||||||
define-data-property "^1.1.4"
|
|
||||||
es-errors "^1.3.0"
|
|
||||||
function-bind "^1.1.2"
|
|
||||||
get-intrinsic "^1.2.4"
|
|
||||||
gopd "^1.0.1"
|
|
||||||
has-property-descriptors "^1.0.2"
|
|
||||||
Binary file not shown.
@ -12,6 +12,7 @@
|
|||||||
"typescript": "5.7.3"
|
"typescript": "5.7.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@discordeno/types": "^21.0.0",
|
||||||
"@needle-di/core": "^0.10.1",
|
"@needle-di/core": "^0.10.1",
|
||||||
"@temporalio/activity": "^1.11.7",
|
"@temporalio/activity": "^1.11.7",
|
||||||
"@temporalio/client": "^1.11.7",
|
"@temporalio/client": "^1.11.7",
|
||||||
|
|||||||
@ -1,14 +1,7 @@
|
|||||||
import { Bot } from "#/bot";
|
import { Bot } from "#/discord/bot";
|
||||||
import {c} from "#/di"
|
import {c} from "#/di"
|
||||||
import { InteractionResponseTypes, InteractionCallbackOptions, InteractionCallbackData, InteractionTypes, MessageFlags } from "discordeno";
|
import { InteractionResponseTypes, InteractionCallbackOptions, InteractionCallbackData, InteractionTypes, MessageFlags } from "discordeno";
|
||||||
|
import { InteractionRef } from "#/discord";
|
||||||
|
|
||||||
export interface InteractionRef {
|
|
||||||
id: bigint
|
|
||||||
token: string
|
|
||||||
type: InteractionTypes
|
|
||||||
acknowledged?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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: {
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
export * from "./database";
|
export * from "./database";
|
||||||
export * from "./discord";
|
export * from "./discord";
|
||||||
export * from "./guild-messages";
|
|
||||||
export * from "./guild";
|
export * from "./guild";
|
||||||
|
export * from "./guild_messages";
|
||||||
export * from "./leaderboards";
|
export * from "./leaderboards";
|
||||||
export * from "./players";
|
export * from "./players";
|
||||||
|
|||||||
@ -4,9 +4,9 @@ import { Command } from 'clipanion';
|
|||||||
// di
|
// di
|
||||||
import "#/services/pg"
|
import "#/services/pg"
|
||||||
import { DISCORD_GUILD_ID } from '#/constants';
|
import { DISCORD_GUILD_ID } from '#/constants';
|
||||||
import { Bot } from '#/bot';
|
import { Bot } from '#/discord/bot';
|
||||||
import { events } from '#/bot/botevent/handler';
|
import { events } from '#/discord/botevent/handler';
|
||||||
import { SLASH_COMMANDS } from '#/bot/botevent/slash_commands';
|
import { SLASH_COMMANDS } from '#/discord/botevent/slash_commands';
|
||||||
import { c } from '#/di';
|
import { c } from '#/di';
|
||||||
import { config } from '#/config';
|
import { config } from '#/config';
|
||||||
|
|
||||||
|
|||||||
@ -117,7 +117,7 @@ export class WorkerCommand extends Command {
|
|||||||
namespace: config.TEMPORAL_NAMESPACE,
|
namespace: config.TEMPORAL_NAMESPACE,
|
||||||
workflowsPath: require.resolve('../workflows'),
|
workflowsPath: require.resolve('../workflows'),
|
||||||
dataConverter: {
|
dataConverter: {
|
||||||
payloadConverterPath: require.resolve('../payload-converter'),
|
payloadConverterPath: require.resolve('../payload_converter'),
|
||||||
},
|
},
|
||||||
bundlerOptions: {
|
bundlerOptions: {
|
||||||
webpackConfigHook: (config)=>{
|
webpackConfigHook: (config)=>{
|
||||||
|
|||||||
24
ts/src/discord/bot.ts
Normal file
24
ts/src/discord/bot.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { config } from "#/config";
|
||||||
|
import { c } from "#/di";
|
||||||
|
import { InjectionToken } from "@needle-di/core";
|
||||||
|
import { createBot, } from "discordeno";
|
||||||
|
import { BotType, createBotParameters } from "./index";
|
||||||
|
|
||||||
|
const createBotWithToken = (token: string) => {
|
||||||
|
return createBot({
|
||||||
|
...createBotParameters,
|
||||||
|
token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export const Bot = new InjectionToken<BotType>("DISCORD_BOT")
|
||||||
|
c.bind({
|
||||||
|
provide: Bot,
|
||||||
|
useFactory: () => {
|
||||||
|
let token = config.DISCORD_TOKEN
|
||||||
|
if(!token) {
|
||||||
|
throw new Error('no discord token found. bot cant start');
|
||||||
|
}
|
||||||
|
const bot = createBotWithToken(token)
|
||||||
|
return bot
|
||||||
|
},
|
||||||
|
})
|
||||||
189
ts/src/discord/botevent/command_parser.ts
Normal file
189
ts/src/discord/botevent/command_parser.ts
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
import { ApplicationCommandOptionTypes, CreateApplicationCommand, DiscordInteractionDataOption} from "@discordeno/types";
|
||||||
|
import { SLASH_COMMANDS } from "./slash_commands";
|
||||||
|
import { InteractionData } from "..";
|
||||||
|
|
||||||
|
// Map option types to their TypeScript types
|
||||||
|
type OptionTypeMap = {
|
||||||
|
[ApplicationCommandOptionTypes.String]: string;
|
||||||
|
[ApplicationCommandOptionTypes.Integer]: number;
|
||||||
|
[ApplicationCommandOptionTypes.Boolean]: boolean;
|
||||||
|
[ApplicationCommandOptionTypes.User]: string; // user ID
|
||||||
|
[ApplicationCommandOptionTypes.Channel]: string; // channel ID
|
||||||
|
[ApplicationCommandOptionTypes.Role]: string; // role ID
|
||||||
|
[ApplicationCommandOptionTypes.Number]: number;
|
||||||
|
[ApplicationCommandOptionTypes.Mentionable]: string; // ID
|
||||||
|
[ApplicationCommandOptionTypes.Attachment]: string; // attachment ID
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract the argument types from command options
|
||||||
|
type ExtractArgs<Options extends readonly any[]> = {
|
||||||
|
[K in Options[number] as K extends { name: infer N; type: infer T }
|
||||||
|
? T extends keyof OptionTypeMap
|
||||||
|
? N extends string
|
||||||
|
? N
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: never]: Options[number] extends { name: K; type: infer T; required?: infer R }
|
||||||
|
? T extends keyof OptionTypeMap
|
||||||
|
? R extends true
|
||||||
|
? OptionTypeMap[T]
|
||||||
|
: OptionTypeMap[T] | undefined
|
||||||
|
: never
|
||||||
|
: never;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handler function type that accepts typed arguments
|
||||||
|
type HandlerFunction<Args = {}> = (args: Args) => Promise<void> | void;
|
||||||
|
|
||||||
|
// Helper types to extract command structure with proper argument types
|
||||||
|
type ExtractSubcommands<T> = T extends { options: infer O }
|
||||||
|
? O extends readonly any[]
|
||||||
|
? {
|
||||||
|
[K in O[number] as K extends { name: infer N; type: ApplicationCommandOptionTypes.SubCommand | ApplicationCommandOptionTypes.SubCommandGroup }
|
||||||
|
? N extends string
|
||||||
|
? N
|
||||||
|
: never
|
||||||
|
: never]: O[number] extends { name: K; options?: infer SubO }
|
||||||
|
? SubO extends readonly any[]
|
||||||
|
? HandlerFunction<ExtractArgs<SubO>>
|
||||||
|
: HandlerFunction<{}>
|
||||||
|
: HandlerFunction<{}>
|
||||||
|
}
|
||||||
|
: never
|
||||||
|
: never;
|
||||||
|
|
||||||
|
type ExtractCommands<T extends readonly CreateApplicationCommand[]> = {
|
||||||
|
[K in T[number] as K['name']]: ExtractSubcommands<K> extends never
|
||||||
|
? T[number] extends { name: K; options?: infer O }
|
||||||
|
? O extends readonly any[]
|
||||||
|
? HandlerFunction<ExtractArgs<O>>
|
||||||
|
: HandlerFunction<{}>
|
||||||
|
: HandlerFunction<{}>
|
||||||
|
: ExtractSubcommands<K>
|
||||||
|
};
|
||||||
|
|
||||||
|
// The actual command handler type based on SLASH_COMMANDS
|
||||||
|
export type CommandHandlers = ExtractCommands<typeof SLASH_COMMANDS>;
|
||||||
|
|
||||||
|
// Helper function to parse option values from interaction data
|
||||||
|
function parseOptions(options?: DiscordInteractionDataOption[]): Record<string, any> {
|
||||||
|
if (!options) return {};
|
||||||
|
|
||||||
|
const args: Record<string, any> = {};
|
||||||
|
|
||||||
|
for (const option of options) {
|
||||||
|
if (option.type === ApplicationCommandOptionTypes.SubCommand ||
|
||||||
|
option.type === ApplicationCommandOptionTypes.SubCommandGroup) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
args[option.name] = option.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to create command handlers with type safety
|
||||||
|
export function createCommandHandler<T extends readonly CreateApplicationCommand[]>(
|
||||||
|
{handler, notFoundHandler}:{
|
||||||
|
handler: ExtractCommands<T>
|
||||||
|
notFoundHandler: HandlerFunction<{}>
|
||||||
|
}) {
|
||||||
|
return async (data: InteractionData): Promise<void> => {
|
||||||
|
if (!data || !data.name) {
|
||||||
|
await notFoundHandler({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commandName = data.name as keyof typeof handler;
|
||||||
|
const commandHandler = handler[commandName];
|
||||||
|
|
||||||
|
if (!commandHandler) {
|
||||||
|
await notFoundHandler({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a direct command or has subcommands
|
||||||
|
if (typeof commandHandler === 'function') {
|
||||||
|
// Parse arguments from top-level options
|
||||||
|
const args = parseOptions(data.options);
|
||||||
|
await commandHandler(args);
|
||||||
|
} else {
|
||||||
|
// Handle subcommands
|
||||||
|
const subcommand = data.options?.find(
|
||||||
|
opt => opt.type === ApplicationCommandOptionTypes.SubCommand ||
|
||||||
|
opt.type === ApplicationCommandOptionTypes.SubCommandGroup
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!subcommand) {
|
||||||
|
await notFoundHandler({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subHandler = commandHandler[subcommand.name as keyof typeof commandHandler];
|
||||||
|
if (!subHandler || typeof subHandler !== 'function') {
|
||||||
|
await notFoundHandler({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse arguments from subcommand options
|
||||||
|
const args = parseOptions(subcommand.options);
|
||||||
|
await subHandler(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to validate that all commands are implemented
|
||||||
|
export function validateCommandImplementation(handlers: CommandHandlers): void {
|
||||||
|
for (const command of SLASH_COMMANDS) {
|
||||||
|
const commandName = command.name;
|
||||||
|
if (!(commandName in handlers)) {
|
||||||
|
throw new Error(`Command "${commandName}" is not implemented`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check subcommands if they exist
|
||||||
|
if (command.options) {
|
||||||
|
const subcommands = command.options.filter(
|
||||||
|
opt => opt.type === ApplicationCommandOptionTypes.SubCommand
|
||||||
|
);
|
||||||
|
|
||||||
|
if (subcommands.length > 0) {
|
||||||
|
const handlerObj = handlers[commandName as keyof CommandHandlers];
|
||||||
|
if (typeof handlerObj === 'function') {
|
||||||
|
throw new Error(`Command "${commandName}" has subcommands but is implemented as a function`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const subcommand of subcommands) {
|
||||||
|
if (!(subcommand.name in handlerObj)) {
|
||||||
|
throw new Error(`Subcommand "${commandName}.${subcommand.name}" is not implemented`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to create command handlers with type safety
|
||||||
|
export function createCommandHandlers<T extends CommandHandlers>(handlers: T): T {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example usage showing the type-safe implementation
|
||||||
|
export const exampleHandlers = createCommandHandlers({
|
||||||
|
guild: {
|
||||||
|
leaderboard: async (args: {}) => {
|
||||||
|
console.log("Guild leaderboard command");
|
||||||
|
},
|
||||||
|
info: async (args: {}) => {
|
||||||
|
console.log("Guild info command");
|
||||||
|
},
|
||||||
|
online: async (args: {}) => {
|
||||||
|
console.log("Guild online command");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
set_wynn_guild: async (args: {}) => {
|
||||||
|
console.log("Admin set_wynn_guild command");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -1,8 +1,9 @@
|
|||||||
import { Bot, BotType } from "#/bot"
|
import { Bot } from "#/discord/bot"
|
||||||
import { ActivityTypes, InteractionTypes } from "discordeno"
|
import { ActivityTypes, InteractionTypes } from "discordeno"
|
||||||
import { c } from "#/di"
|
import { c } from "#/di"
|
||||||
import { Client } from "@temporalio/client"
|
import { Client } from "@temporalio/client"
|
||||||
import { workflowHandleInteractionCreate } from "#/workflows"
|
import { workflowHandleInteractionCreate } from "#/workflows"
|
||||||
|
import { BotType } from "#/discord"
|
||||||
|
|
||||||
export const events = () => {return {
|
export const events = () => {return {
|
||||||
interactionCreate: async (interaction) => {
|
interactionCreate: async (interaction) => {
|
||||||
@ -1,9 +1,6 @@
|
|||||||
import { ApplicationCommandOptionTypes, ApplicationCommandTypes, CreateApplicationCommand } from "discordeno"
|
import { ApplicationCommandOptionTypes, ApplicationCommandTypes, CreateApplicationCommand } from "@discordeno/types"
|
||||||
|
|
||||||
const createCommands = <T extends CreateApplicationCommand[]>(commands: T): T => {
|
export const SLASH_COMMANDS = [
|
||||||
return commands
|
|
||||||
}
|
|
||||||
export const SLASH_COMMANDS = createCommands([
|
|
||||||
{
|
{
|
||||||
name: `guild`,
|
name: `guild`,
|
||||||
description: "guild commands",
|
description: "guild commands",
|
||||||
@ -41,4 +38,4 @@ export const SLASH_COMMANDS = createCommands([
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
])
|
] as const satisfies CreateApplicationCommand[]
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import {BotType} from "#/bot"
|
import {BotType} from "#/discord"
|
||||||
|
|
||||||
|
|
||||||
export type BotEventsType = BotType['events']
|
export type BotEventsType = BotType['events']
|
||||||
@ -1,10 +1,6 @@
|
|||||||
import { config } from "#/config";
|
import { Intents, InteractionTypes } from "@discordeno/types";
|
||||||
import { c } from "#/di";
|
import type { Bot, DesiredPropertiesBehavior, CompleteDesiredProperties } from "discordeno";
|
||||||
import { InjectionToken } from "@needle-di/core";
|
export const intents = [
|
||||||
|
|
||||||
import {createBot, Intents} from "discordeno"
|
|
||||||
|
|
||||||
const intents = [
|
|
||||||
Intents.GuildModeration ,
|
Intents.GuildModeration ,
|
||||||
Intents.GuildWebhooks ,
|
Intents.GuildWebhooks ,
|
||||||
Intents.GuildExpressions ,
|
Intents.GuildExpressions ,
|
||||||
@ -22,9 +18,8 @@ const intents = [
|
|||||||
Intents.GuildMessages,
|
Intents.GuildMessages,
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export const createBotWithToken = (token: string) => createBot({
|
export const createBotParameters = {
|
||||||
intents: intents.reduce((acc, curr) => acc | curr, Intents.Guilds),
|
intents: intents.reduce((acc, curr) => acc | curr, Intents.Guilds),
|
||||||
token: token,
|
|
||||||
desiredProperties: {
|
desiredProperties: {
|
||||||
interaction: {
|
interaction: {
|
||||||
id: true,
|
id: true,
|
||||||
@ -45,18 +40,28 @@ export const createBotWithToken = (token: string) => createBot({
|
|||||||
guildId: true,
|
guildId: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
} as const
|
||||||
|
|
||||||
export type BotType = ReturnType<typeof createBotWithToken>
|
// Extract the type of desired properties from our parameters
|
||||||
export const Bot = new InjectionToken<BotType>("DISCORD_BOT")
|
type ExtractedDesiredProperties = typeof createBotParameters.desiredProperties;
|
||||||
c.bind({
|
|
||||||
provide: Bot,
|
// The BotType uses the CompleteDesiredProperties helper to fill in the missing properties
|
||||||
async: true,
|
export type BotType = Bot<CompleteDesiredProperties<ExtractedDesiredProperties>, DesiredPropertiesBehavior.RemoveKey>;
|
||||||
useFactory: async () => {
|
|
||||||
let token = config.DISCORD_TOKEN
|
|
||||||
if(!token) {
|
// Type for the interaction reference passed to workflows/activities
|
||||||
throw new Error('no discord token found. bot cant start');
|
export interface InteractionRef {
|
||||||
}
|
id: bigint;
|
||||||
return createBotWithToken(token)
|
token: string;
|
||||||
},
|
type: InteractionTypes;
|
||||||
})
|
acknowledged?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type for the interaction data payload
|
||||||
|
export type InteractionData = Parameters<NonNullable<BotType['events']['interactionCreate']>>[0]['data'];
|
||||||
|
|
||||||
|
// Type for the complete interaction handling payload
|
||||||
|
export interface InteractionCreatePayload {
|
||||||
|
ref: InteractionRef;
|
||||||
|
data: InteractionData;
|
||||||
|
}
|
||||||
@ -13,7 +13,7 @@ c.bind({
|
|||||||
connection,
|
connection,
|
||||||
namespace: config.TEMPORAL_NAMESPACE,
|
namespace: config.TEMPORAL_NAMESPACE,
|
||||||
dataConverter: {
|
dataConverter: {
|
||||||
payloadConverterPath: require.resolve('../../payload-converter'),
|
payloadConverterPath: require.resolve('../../payload_converter'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
process.on('exit', () => {
|
process.on('exit', () => {
|
||||||
|
|||||||
@ -1,83 +1,68 @@
|
|||||||
import { proxyActivities, startChild, workflowInfo } from '@temporalio/workflow';
|
import { proxyActivities, startChild, workflowInfo } from '@temporalio/workflow';
|
||||||
import type * as activities from '#/activities';
|
import type * as activities from '#/activities';
|
||||||
import { ApplicationCommandOptionTypes, InteractionTypes } from 'discordeno';
|
import { InteractionTypes } from '@discordeno/types';
|
||||||
import { handleCommandGuildInfo, handleCommandGuildOnline, handleCommandGuildLeaderboard } from './guild-messages';
|
import { handleCommandGuildInfo, handleCommandGuildOnline, handleCommandGuildLeaderboard } from './guild_messages';
|
||||||
import { BotType } from '#/bot';
|
import { createCommandHandler } from '#/discord/botevent/command_parser';
|
||||||
|
import { SLASH_COMMANDS } from '#/discord/botevent/slash_commands';
|
||||||
|
import { InteractionCreatePayload} from '#/discord';
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
interface HandleInteractionCreatePayload {
|
|
||||||
ref: activities.InteractionRef
|
|
||||||
data: Parameters<NonNullable<BotType['events']['interactionCreate']>>[0]['data']
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const workflowHandleApplicationCommand = async (
|
const workflowHandleApplicationCommand = async (
|
||||||
payload: HandleInteractionCreatePayload,
|
payload: InteractionCreatePayload,
|
||||||
) => {
|
) => {
|
||||||
const { ref, data } = payload;
|
const { ref, data } = payload;
|
||||||
|
|
||||||
if (!data || !data.name) {
|
const notFoundHandler = async (content: string) => {
|
||||||
await reply_to_interaction({
|
await reply_to_interaction({
|
||||||
ref,
|
ref,
|
||||||
type: 4,
|
type: 4,
|
||||||
options: {
|
options: {
|
||||||
content: "Invalid command data",
|
content: content,
|
||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
if (!data || !data.name) {
|
||||||
// Build command path
|
await notFoundHandler(`Invalid command data`);
|
||||||
const commandPath: string[] = [data.name];
|
return
|
||||||
if (data.options) {
|
|
||||||
for (const option of data.options) {
|
|
||||||
if (option.type === ApplicationCommandOptionTypes.SubCommand) {
|
|
||||||
commandPath.push(option.name);
|
|
||||||
}
|
}
|
||||||
}
|
const commandHandler = createCommandHandler<typeof SLASH_COMMANDS>({
|
||||||
}
|
notFoundHandler: async () => {
|
||||||
|
await notFoundHandler(`command not found`);
|
||||||
// Route to appropriate child workflow based on command path
|
},
|
||||||
const fullCommand = commandPath.join('.');
|
handler:{
|
||||||
|
guild: {
|
||||||
|
info: async (args: {}) => {
|
||||||
const { workflowId } = workflowInfo();
|
const { workflowId } = workflowInfo();
|
||||||
|
|
||||||
try {
|
|
||||||
switch (fullCommand) {
|
|
||||||
case 'guild.info': {
|
|
||||||
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();
|
||||||
break;
|
},
|
||||||
}
|
online: async (args: {}) => {
|
||||||
|
const { workflowId } = workflowInfo();
|
||||||
case 'guild.online': {
|
|
||||||
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();
|
||||||
break;
|
},
|
||||||
}
|
leaderboard: async (args: {}) => {
|
||||||
|
const { workflowId } = workflowInfo();
|
||||||
case 'guild.leaderboard': {
|
|
||||||
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();
|
||||||
break;
|
},
|
||||||
}
|
},
|
||||||
|
admin: {
|
||||||
case 'admin.set_wynn_guild': {
|
set_wynn_guild: async (args: {}) => {
|
||||||
await reply_to_interaction({
|
await reply_to_interaction({
|
||||||
ref,
|
ref,
|
||||||
type: 4,
|
type: 4,
|
||||||
@ -86,34 +71,16 @@ const workflowHandleApplicationCommand = async (
|
|||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
default: {
|
await commandHandler(data);
|
||||||
await reply_to_interaction({
|
|
||||||
ref,
|
|
||||||
type: 4,
|
|
||||||
options: {
|
|
||||||
content: `Command not implemented: ${fullCommand}`,
|
|
||||||
isPrivate: true,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
await reply_to_interaction({
|
|
||||||
ref,
|
|
||||||
type: 4,
|
|
||||||
options: {
|
|
||||||
content: `Error executing command: ${error}`,
|
|
||||||
isPrivate: true,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const workflowHandleInteractionCreate = async (
|
export const workflowHandleInteractionCreate = async (
|
||||||
payload: HandleInteractionCreatePayload,
|
payload: InteractionCreatePayload,
|
||||||
) => {
|
) => {
|
||||||
const {ref, data} = payload
|
const {ref, data} = payload
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
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";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
formGuildInfoMessage,
|
formGuildInfoMessage,
|
||||||
@ -12,7 +13,7 @@ const {
|
|||||||
});
|
});
|
||||||
|
|
||||||
interface CommandPayload {
|
interface CommandPayload {
|
||||||
ref: activities.InteractionRef;
|
ref: InteractionRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleCommandGuildInfo(payload: CommandPayload): Promise<void> {
|
export async function handleCommandGuildInfo(payload: CommandPayload): Promise<void> {
|
||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
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 "./players";
|
export * from "./players";
|
||||||
|
|||||||
@ -74,7 +74,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@discordeno/types@npm:21.0.0":
|
"@discordeno/types@npm:21.0.0, @discordeno/types@npm:^21.0.0":
|
||||||
version: 21.0.0
|
version: 21.0.0
|
||||||
resolution: "@discordeno/types@npm:21.0.0"
|
resolution: "@discordeno/types@npm:21.0.0"
|
||||||
checksum: 10c0/5d47321e75cca4aa60f69d8b146c1c87fae4c4f7065a422a41ef17d34747b6d933d1699567a887beca5e6fc086b5cba0db9ca67b34b81f348afdd9432ea2979d
|
checksum: 10c0/5d47321e75cca4aa60f69d8b146c1c87fae4c4f7065a422a41ef17d34747b6d933d1699567a887beca5e6fc086b5cba0db9ca67b34b81f348afdd9432ea2979d
|
||||||
@ -1324,6 +1324,7 @@ __metadata:
|
|||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "backend@workspace:."
|
resolution: "backend@workspace:."
|
||||||
dependencies:
|
dependencies:
|
||||||
|
"@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"
|
||||||
"@temporalio/client": "npm:^1.11.7"
|
"@temporalio/client": "npm:^1.11.7"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user