dfx 0.42.1 → 0.42.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cache/driver.js.map +1 -1
- package/Cache/memory.js.map +1 -1
- package/Cache/memoryTTL.js.map +1 -1
- package/Cache/prelude.js.map +1 -1
- package/Cache.js.map +1 -1
- package/DiscordConfig.js.map +1 -1
- package/DiscordGateway/DiscordWS.js +1 -1
- package/DiscordGateway/DiscordWS.js.map +1 -1
- package/DiscordGateway/Shard/heartbeats.js.map +1 -1
- package/DiscordGateway/Shard/identify.js.map +1 -1
- package/DiscordGateway/Shard/invalidSession.js.map +1 -1
- package/DiscordGateway/Shard/sendEvents.js.map +1 -1
- package/DiscordGateway/Shard/utils.js.map +1 -1
- package/DiscordGateway/Shard.js.map +1 -1
- package/DiscordGateway/ShardStore.js.map +1 -1
- package/DiscordGateway/Sharder.js.map +1 -1
- package/DiscordGateway/WS.d.ts +3 -3
- package/DiscordGateway/WS.js +25 -18
- package/DiscordGateway/WS.js.map +1 -1
- package/DiscordGateway.js.map +1 -1
- package/DiscordREST/types.js.map +1 -1
- package/DiscordREST/utils.js.map +1 -1
- package/DiscordREST.js.map +1 -1
- package/Helpers/flags.js.map +1 -1
- package/Helpers/intents.js.map +1 -1
- package/Helpers/interactions.js.map +1 -1
- package/Helpers/members.js.map +1 -1
- package/Helpers/permissions.js.map +1 -1
- package/Helpers/ui.js.map +1 -1
- package/Interactions/context.js.map +1 -1
- package/Interactions/definitions.js.map +1 -1
- package/Interactions/gateway.js.map +1 -1
- package/Interactions/handlers.js.map +1 -1
- package/Interactions/index.js.map +1 -1
- package/Interactions/utils.js.map +1 -1
- package/Interactions/webhook.js.map +1 -1
- package/Log.js.map +1 -1
- package/RateLimit/memory.js.map +1 -1
- package/RateLimit/utils.js.map +1 -1
- package/RateLimit.js.map +1 -1
- package/_common.js.map +1 -1
- package/gateway.js.map +1 -1
- package/global.js.map +1 -1
- package/index.js.map +1 -1
- package/package.json +50 -52
- package/src/Cache/driver.ts +31 -0
- package/src/Cache/memory.ts +76 -0
- package/src/Cache/memoryTTL.ts +201 -0
- package/src/Cache/prelude.ts +215 -0
- package/src/Cache.ts +140 -0
- package/src/DiscordConfig.ts +48 -0
- package/src/DiscordGateway/DiscordWS.ts +74 -0
- package/src/DiscordGateway/Shard/heartbeats.ts +42 -0
- package/src/DiscordGateway/Shard/identify.ts +52 -0
- package/src/DiscordGateway/Shard/invalidSession.ts +10 -0
- package/src/DiscordGateway/Shard/sendEvents.ts +37 -0
- package/src/DiscordGateway/Shard/utils.ts +14 -0
- package/src/DiscordGateway/Shard.ts +152 -0
- package/src/DiscordGateway/ShardStore.ts +33 -0
- package/src/DiscordGateway/Sharder.ts +102 -0
- package/src/DiscordGateway/WS.ts +122 -0
- package/src/DiscordGateway.ts +43 -0
- package/src/DiscordREST/types.ts +13 -0
- package/src/DiscordREST/utils.ts +33 -0
- package/src/DiscordREST.ts +203 -0
- package/src/Helpers/flags.ts +68 -0
- package/src/Helpers/intents.ts +34 -0
- package/src/Helpers/interactions.ts +229 -0
- package/src/Helpers/members.ts +14 -0
- package/src/Helpers/permissions.ts +140 -0
- package/src/Helpers/ui.ts +103 -0
- package/src/Interactions/context.ts +132 -0
- package/src/Interactions/definitions.ts +309 -0
- package/src/Interactions/gateway.ts +71 -0
- package/src/Interactions/handlers.ts +130 -0
- package/src/Interactions/index.ts +108 -0
- package/src/Interactions/utils.ts +81 -0
- package/src/Interactions/webhook.ts +110 -0
- package/src/Log.ts +17 -0
- package/src/RateLimit/memory.ts +57 -0
- package/src/RateLimit/utils.ts +27 -0
- package/src/RateLimit.ts +69 -0
- package/src/_common.ts +43 -0
- package/src/gateway.ts +38 -0
- package/src/global.ts +45 -0
- package/src/index.ts +20 -0
- package/src/package.json +52 -0
- package/src/types.ts +6368 -0
- package/src/utils/effect.ts +0 -0
- package/src/utils/hub.ts +47 -0
- package/src/utils/json.d.ts +1 -0
- package/src/utils/tsplus.ts +10 -0
- package/src/webhooks.ts +41 -0
- package/tsconfig.json +23 -0
- package/tsplus.config.json +8 -0
- package/types.js.map +1 -1
- package/utils/effect.js.map +1 -1
- package/utils/hub.js.map +1 -1
- package/utils/tsplus.js.map +1 -1
- package/webhooks.js.map +1 -1
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { Effect, EffectTypeId } from "@effect/io/Effect"
|
|
2
|
+
import {
|
|
3
|
+
FocusedOptionContext,
|
|
4
|
+
ResolvedDataNotFound,
|
|
5
|
+
SubCommandContext,
|
|
6
|
+
} from "./context.js"
|
|
7
|
+
|
|
8
|
+
type DescriptionMissing<A> = A extends {
|
|
9
|
+
type: Exclude<Discord.ApplicationCommandType, 1>
|
|
10
|
+
}
|
|
11
|
+
? false
|
|
12
|
+
: A extends { readonly description: string }
|
|
13
|
+
? false
|
|
14
|
+
: true
|
|
15
|
+
|
|
16
|
+
export type InteractionDefinition<R, E> =
|
|
17
|
+
| GlobalApplicationCommand<R, E>
|
|
18
|
+
| GuildApplicationCommand<R, E>
|
|
19
|
+
| MessageComponent<R, E>
|
|
20
|
+
| ModalSubmit<R, E>
|
|
21
|
+
| Autocomplete<R, E>
|
|
22
|
+
|
|
23
|
+
export class GlobalApplicationCommand<R, E> {
|
|
24
|
+
readonly _tag = "GlobalApplicationCommand"
|
|
25
|
+
constructor(
|
|
26
|
+
readonly command: Discord.CreateGlobalApplicationCommandParams,
|
|
27
|
+
readonly handle: CommandHandler<R, E>,
|
|
28
|
+
) {}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const global = <
|
|
32
|
+
R,
|
|
33
|
+
E,
|
|
34
|
+
const A extends DeepReadonly<Discord.CreateGlobalApplicationCommandParams>,
|
|
35
|
+
>(
|
|
36
|
+
command: A,
|
|
37
|
+
handle: DescriptionMissing<A> extends true
|
|
38
|
+
? "command description is missing"
|
|
39
|
+
: CommandHandler<R, E, A>,
|
|
40
|
+
) =>
|
|
41
|
+
new GlobalApplicationCommand<
|
|
42
|
+
Exclude<R, Discord.Interaction | Discord.ApplicationCommandDatum>,
|
|
43
|
+
E
|
|
44
|
+
>(command as any, handle as any)
|
|
45
|
+
|
|
46
|
+
export class GuildApplicationCommand<R, E> {
|
|
47
|
+
readonly _tag = "GuildApplicationCommand"
|
|
48
|
+
constructor(
|
|
49
|
+
readonly command: Discord.CreateGuildApplicationCommandParams,
|
|
50
|
+
readonly handle: CommandHandler<R, E>,
|
|
51
|
+
) {}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const guild = <
|
|
55
|
+
R,
|
|
56
|
+
E,
|
|
57
|
+
const A extends DeepReadonly<Discord.CreateGuildApplicationCommandParams>,
|
|
58
|
+
>(
|
|
59
|
+
command: A,
|
|
60
|
+
handle: DescriptionMissing<A> extends true
|
|
61
|
+
? "command description is missing"
|
|
62
|
+
: CommandHandler<R, E, A>,
|
|
63
|
+
) =>
|
|
64
|
+
new GuildApplicationCommand<
|
|
65
|
+
Exclude<R, Discord.Interaction | Discord.ApplicationCommandDatum>,
|
|
66
|
+
E
|
|
67
|
+
>(command as any, handle as any)
|
|
68
|
+
|
|
69
|
+
export class MessageComponent<R, E> {
|
|
70
|
+
readonly _tag = "MessageComponent"
|
|
71
|
+
constructor(
|
|
72
|
+
readonly predicate: (customId: string) => Effect<R, E, boolean>,
|
|
73
|
+
readonly handle: Effect<R, E, Discord.InteractionResponse>,
|
|
74
|
+
) {}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const messageComponent = <R1, R2, E1, E2>(
|
|
78
|
+
pred: (customId: string) => Effect<R1, E1, boolean>,
|
|
79
|
+
handle: CommandHandler<R2, E2, Discord.InteractionResponse>,
|
|
80
|
+
) =>
|
|
81
|
+
new MessageComponent<
|
|
82
|
+
Exclude<R1 | R2, Discord.Interaction | Discord.MessageComponentDatum>,
|
|
83
|
+
E1 | E2
|
|
84
|
+
>(pred as any, handle as any)
|
|
85
|
+
|
|
86
|
+
export class ModalSubmit<R, E> {
|
|
87
|
+
readonly _tag = "ModalSubmit"
|
|
88
|
+
constructor(
|
|
89
|
+
readonly predicate: (customId: string) => Effect<R, E, boolean>,
|
|
90
|
+
readonly handle: Effect<R, E, Discord.InteractionResponse>,
|
|
91
|
+
) {}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const modalSubmit = <R1, R2, E1, E2>(
|
|
95
|
+
pred: (customId: string) => Effect<R1, E1, boolean>,
|
|
96
|
+
handle: Effect<R2, E2, Discord.InteractionResponse>,
|
|
97
|
+
) =>
|
|
98
|
+
new ModalSubmit<
|
|
99
|
+
Exclude<R1 | R2, Discord.Interaction | Discord.ModalSubmitDatum>,
|
|
100
|
+
E1 | E2
|
|
101
|
+
>(pred as any, handle as any)
|
|
102
|
+
|
|
103
|
+
export class Autocomplete<R, E> {
|
|
104
|
+
readonly _tag = "Autocomplete"
|
|
105
|
+
constructor(
|
|
106
|
+
readonly predicate: (
|
|
107
|
+
data: Discord.ApplicationCommandDatum,
|
|
108
|
+
focusedOption: Discord.ApplicationCommandInteractionDataOption,
|
|
109
|
+
) => Effect<R, E, boolean>,
|
|
110
|
+
readonly handle: Effect<R, E, Discord.InteractionResponse>,
|
|
111
|
+
) {}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const autocomplete = <R1, R2, E1, E2>(
|
|
115
|
+
pred: (
|
|
116
|
+
data: Discord.ApplicationCommandDatum,
|
|
117
|
+
focusedOption: Discord.ApplicationCommandInteractionDataOption,
|
|
118
|
+
) => Effect<R1, E1, boolean>,
|
|
119
|
+
handle: Effect<R2, E2, Discord.InteractionResponse>,
|
|
120
|
+
) =>
|
|
121
|
+
new Autocomplete<
|
|
122
|
+
Exclude<
|
|
123
|
+
R1 | R2,
|
|
124
|
+
| Discord.Interaction
|
|
125
|
+
| Discord.ApplicationCommandDatum
|
|
126
|
+
| FocusedOptionContext
|
|
127
|
+
>,
|
|
128
|
+
E1 | E2
|
|
129
|
+
>(pred as any, handle as any)
|
|
130
|
+
|
|
131
|
+
// ==== Command handler helpers
|
|
132
|
+
type DeepReadonly<T> = T extends (infer R)[]
|
|
133
|
+
? DeepReadonlyArray<R>
|
|
134
|
+
: T extends Function
|
|
135
|
+
? T
|
|
136
|
+
: T extends object
|
|
137
|
+
? DeepReadonlyObject<T>
|
|
138
|
+
: T
|
|
139
|
+
|
|
140
|
+
interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}
|
|
141
|
+
|
|
142
|
+
type DeepReadonlyObject<T> = {
|
|
143
|
+
readonly [P in keyof T]: DeepReadonly<T[P]>
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
type CommandHandler<R, E, A = any> =
|
|
147
|
+
| Effect<R, E, Discord.InteractionResponse>
|
|
148
|
+
| CommandHandlerFn<R, E, A>
|
|
149
|
+
|
|
150
|
+
export interface CommandHelper<A> {
|
|
151
|
+
resolve: <T>(
|
|
152
|
+
name: AllResolvables<A>["name"],
|
|
153
|
+
f: (id: Discord.Snowflake, data: Discord.ResolvedDatum) => T | undefined,
|
|
154
|
+
) => Effect<Discord.Interaction, ResolvedDataNotFound, T>
|
|
155
|
+
|
|
156
|
+
option: (
|
|
157
|
+
name: AllCommandOptions<A>["name"],
|
|
158
|
+
) => Effect<
|
|
159
|
+
Discord.ApplicationCommandDatum,
|
|
160
|
+
never,
|
|
161
|
+
Maybe<Discord.ApplicationCommandInteractionDataOption>
|
|
162
|
+
>
|
|
163
|
+
|
|
164
|
+
optionValue: <N extends AllRequiredCommandOptions<A>["name"]>(
|
|
165
|
+
name: N,
|
|
166
|
+
) => Effect<Discord.ApplicationCommandDatum, never, CommandValue<A, N>>
|
|
167
|
+
|
|
168
|
+
optionValueOptional: <N extends AllCommandOptions<A>["name"]>(
|
|
169
|
+
name: N,
|
|
170
|
+
) => Effect<Discord.ApplicationCommandDatum, never, Maybe<CommandValue<A, N>>>
|
|
171
|
+
|
|
172
|
+
subCommands: <
|
|
173
|
+
NER extends SubCommandNames<A> extends never
|
|
174
|
+
? never
|
|
175
|
+
: Record<
|
|
176
|
+
SubCommandNames<A>,
|
|
177
|
+
Effect<any, any, Discord.InteractionResponse>
|
|
178
|
+
>,
|
|
179
|
+
>(
|
|
180
|
+
commands: NER,
|
|
181
|
+
) => Effect<
|
|
182
|
+
| Exclude<
|
|
183
|
+
[NER[keyof NER]] extends [
|
|
184
|
+
{ [EffectTypeId]: { _R: (_: never) => infer R } },
|
|
185
|
+
]
|
|
186
|
+
? R
|
|
187
|
+
: never,
|
|
188
|
+
SubCommandContext
|
|
189
|
+
>
|
|
190
|
+
| Discord.Interaction
|
|
191
|
+
| Discord.ApplicationCommandDatum,
|
|
192
|
+
[NER[keyof NER]] extends [{ [EffectTypeId]: { _E: (_: never) => infer E } }]
|
|
193
|
+
? E
|
|
194
|
+
: never,
|
|
195
|
+
Discord.InteractionResponse
|
|
196
|
+
>
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
type CommandHandlerFn<R, E, A> = (
|
|
200
|
+
i: CommandHelper<A>,
|
|
201
|
+
) => Effect<R, E, Discord.InteractionResponse>
|
|
202
|
+
|
|
203
|
+
interface CommandOption {
|
|
204
|
+
readonly type: any
|
|
205
|
+
readonly name: string
|
|
206
|
+
readonly options?: ReadonlyArray<CommandOption>
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// == Sub commands
|
|
210
|
+
type SubCommands<A> = A extends {
|
|
211
|
+
readonly type: Discord.ApplicationCommandOptionType.SUB_COMMAND
|
|
212
|
+
readonly options?: ReadonlyArray<CommandOption>
|
|
213
|
+
}
|
|
214
|
+
? A
|
|
215
|
+
: A extends { readonly options: ReadonlyArray<CommandOption> }
|
|
216
|
+
? SubCommands<A["options"][number]>
|
|
217
|
+
: never
|
|
218
|
+
|
|
219
|
+
type SubCommandNames<A> = Option<SubCommands<A>>["name"]
|
|
220
|
+
|
|
221
|
+
// == Command options
|
|
222
|
+
type CommandOptionType = Exclude<
|
|
223
|
+
Discord.ApplicationCommandOptionType,
|
|
224
|
+
| Discord.ApplicationCommandOptionType.SUB_COMMAND
|
|
225
|
+
| Discord.ApplicationCommandOptionType.SUB_COMMAND_GROUP
|
|
226
|
+
>
|
|
227
|
+
|
|
228
|
+
type CommandOptions<A> = OptionsWithLiteral<
|
|
229
|
+
A,
|
|
230
|
+
{
|
|
231
|
+
readonly type: CommandOptionType
|
|
232
|
+
}
|
|
233
|
+
>
|
|
234
|
+
|
|
235
|
+
type SubCommandOptions<A> = Extract<
|
|
236
|
+
Option<Exclude<SubCommands<A>["options"], undefined>[number]>,
|
|
237
|
+
{
|
|
238
|
+
readonly type: CommandOptionType
|
|
239
|
+
}
|
|
240
|
+
>
|
|
241
|
+
|
|
242
|
+
type AllCommandOptions<A> = CommandOptions<A> | SubCommandOptions<A>
|
|
243
|
+
|
|
244
|
+
type CommandWithName<A, N> = Extract<AllCommandOptions<A>, { readonly name: N }>
|
|
245
|
+
|
|
246
|
+
type OptionTypeValue = {
|
|
247
|
+
[Discord.ApplicationCommandOptionType.BOOLEAN]: boolean
|
|
248
|
+
[Discord.ApplicationCommandOptionType.INTEGER]: number
|
|
249
|
+
[Discord.ApplicationCommandOptionType.NUMBER]: number
|
|
250
|
+
}
|
|
251
|
+
type CommandValue<A, N> = CommandWithName<
|
|
252
|
+
A,
|
|
253
|
+
N
|
|
254
|
+
>["type"] extends keyof OptionTypeValue
|
|
255
|
+
? OptionTypeValue[CommandWithName<A, N>["type"]]
|
|
256
|
+
: string
|
|
257
|
+
|
|
258
|
+
// == Required options
|
|
259
|
+
type RequiredCommandOptions<A> = OptionsWithLiteral<
|
|
260
|
+
A,
|
|
261
|
+
{
|
|
262
|
+
readonly type: CommandOptionType
|
|
263
|
+
readonly required: true
|
|
264
|
+
}
|
|
265
|
+
>
|
|
266
|
+
|
|
267
|
+
type RequiredSubCommandOptions<A> = Extract<
|
|
268
|
+
SubCommandOptions<A>,
|
|
269
|
+
{ readonly required: true }
|
|
270
|
+
>
|
|
271
|
+
|
|
272
|
+
type AllRequiredCommandOptions<A> =
|
|
273
|
+
| RequiredCommandOptions<A>
|
|
274
|
+
| RequiredSubCommandOptions<A>
|
|
275
|
+
|
|
276
|
+
// == Resolveables
|
|
277
|
+
type ResolvableType =
|
|
278
|
+
| Discord.ApplicationCommandOptionType.ROLE
|
|
279
|
+
| Discord.ApplicationCommandOptionType.USER
|
|
280
|
+
| Discord.ApplicationCommandOptionType.MENTIONABLE
|
|
281
|
+
| Discord.ApplicationCommandOptionType.CHANNEL
|
|
282
|
+
|
|
283
|
+
type Resolvables<A> = OptionsWithLiteral<A, { readonly type: ResolvableType }>
|
|
284
|
+
type SubCommandResolvables<A> = Extract<
|
|
285
|
+
Option<Exclude<SubCommands<A>["options"], undefined>[number]>,
|
|
286
|
+
{
|
|
287
|
+
readonly type: ResolvableType
|
|
288
|
+
}
|
|
289
|
+
>
|
|
290
|
+
type AllResolvables<A> = Resolvables<A> | SubCommandResolvables<A>
|
|
291
|
+
|
|
292
|
+
// == Utilities
|
|
293
|
+
type StringLiteral<T> = T extends string
|
|
294
|
+
? string extends T
|
|
295
|
+
? never
|
|
296
|
+
: T
|
|
297
|
+
: never
|
|
298
|
+
|
|
299
|
+
type Option<A> = A extends { readonly name: infer N }
|
|
300
|
+
? N extends StringLiteral<N>
|
|
301
|
+
? A
|
|
302
|
+
: never
|
|
303
|
+
: never
|
|
304
|
+
|
|
305
|
+
type OptionsWithLiteral<A, T> = A extends {
|
|
306
|
+
readonly options: ReadonlyArray<CommandOption>
|
|
307
|
+
}
|
|
308
|
+
? Extract<A["options"][number], Option<A["options"][number]> & T>
|
|
309
|
+
: never
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as Http from "@effect-http/client"
|
|
2
|
+
import { DiscordGateway } from "dfx/DiscordGateway"
|
|
3
|
+
import { DiscordREST, DiscordRESTError } from "dfx/DiscordREST"
|
|
4
|
+
import { DefinitionNotFound, handlers } from "./handlers.js"
|
|
5
|
+
import { Interaction, InteractionBuilder } from "./index.js"
|
|
6
|
+
import { splitDefinitions } from "./utils.js"
|
|
7
|
+
|
|
8
|
+
export interface RunOpts {
|
|
9
|
+
sync?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @tsplus pipeable dfx/InteractionBuilder runGateway
|
|
14
|
+
*/
|
|
15
|
+
export const run =
|
|
16
|
+
<R, R2, E, E2>(
|
|
17
|
+
postHandler: (
|
|
18
|
+
effect: Effect<
|
|
19
|
+
R | DiscordREST | Discord.Interaction,
|
|
20
|
+
E | DiscordRESTError | DefinitionNotFound,
|
|
21
|
+
void
|
|
22
|
+
>,
|
|
23
|
+
) => Effect<R2, E2, void>,
|
|
24
|
+
{ sync = true }: RunOpts = {},
|
|
25
|
+
) =>
|
|
26
|
+
(
|
|
27
|
+
ix: InteractionBuilder<R, E>,
|
|
28
|
+
): Effect<
|
|
29
|
+
DiscordREST | DiscordGateway | Exclude<R2, Discord.Interaction>,
|
|
30
|
+
E2 | DiscordRESTError | Http.ResponseDecodeError,
|
|
31
|
+
never
|
|
32
|
+
> =>
|
|
33
|
+
Do($ => {
|
|
34
|
+
const { GlobalApplicationCommand, GuildApplicationCommand } =
|
|
35
|
+
splitDefinitions(ix.definitions)
|
|
36
|
+
|
|
37
|
+
const gateway = $(DiscordGateway)
|
|
38
|
+
const rest = $(DiscordREST)
|
|
39
|
+
|
|
40
|
+
const application = $(
|
|
41
|
+
rest.getCurrentBotApplicationInformation().flatMap(a => a.json),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const globalSync = rest.bulkOverwriteGlobalApplicationCommands(
|
|
45
|
+
application.id,
|
|
46
|
+
{ body: Http.body.json(GlobalApplicationCommand.map(a => a.command)) },
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
const guildSync = GuildApplicationCommand.length
|
|
50
|
+
? gateway.handleDispatch("GUILD_CREATE", a =>
|
|
51
|
+
rest.bulkOverwriteGuildApplicationCommands(
|
|
52
|
+
application.id,
|
|
53
|
+
a.id,
|
|
54
|
+
GuildApplicationCommand.map(a => a.command) as any,
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
: Effect.never()
|
|
58
|
+
|
|
59
|
+
const handle = handlers(ix.definitions)
|
|
60
|
+
|
|
61
|
+
const run = gateway.handleDispatch("INTERACTION_CREATE", i =>
|
|
62
|
+
pipe(
|
|
63
|
+
handle[i.type](i).tap(r =>
|
|
64
|
+
rest.createInteractionResponse(i.id, i.token, r),
|
|
65
|
+
),
|
|
66
|
+
postHandler,
|
|
67
|
+
).provideService(Interaction, i),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return $(sync ? run.zipParRight(globalSync).zipParRight(guildSync) : run)
|
|
71
|
+
})
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as Arr from "@effect/data/ReadonlyArray"
|
|
2
|
+
import * as IxHelpers from "dfx/Helpers/interactions"
|
|
3
|
+
import * as Ctx from "./context.js"
|
|
4
|
+
import * as D from "./definitions.js"
|
|
5
|
+
import { splitDefinitions } from "./utils.js"
|
|
6
|
+
|
|
7
|
+
export class DefinitionNotFound {
|
|
8
|
+
readonly _tag = "DefinitionNotFound"
|
|
9
|
+
constructor(readonly interaction: Discord.Interaction) {}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type Handler<R, E> = Effect<
|
|
13
|
+
R | Discord.Interaction,
|
|
14
|
+
E | DefinitionNotFound,
|
|
15
|
+
Discord.InteractionResponse
|
|
16
|
+
>
|
|
17
|
+
|
|
18
|
+
const context: D.CommandHelper<any> = {
|
|
19
|
+
resolve: Ctx.resolved,
|
|
20
|
+
option: Ctx.option,
|
|
21
|
+
optionValue: Ctx.optionValue,
|
|
22
|
+
optionValueOptional: Ctx.optionValueOptional,
|
|
23
|
+
subCommands: Ctx.handleSubCommands,
|
|
24
|
+
} as any
|
|
25
|
+
|
|
26
|
+
export const handlers = <R, E>(
|
|
27
|
+
definitions: D.InteractionDefinition<R, E>[],
|
|
28
|
+
): Record<
|
|
29
|
+
Discord.InteractionType,
|
|
30
|
+
(i: Discord.Interaction) => Handler<R, E>
|
|
31
|
+
> => {
|
|
32
|
+
const { Commands, Autocomplete, MessageComponent, ModalSubmit } =
|
|
33
|
+
splitDefinitions(definitions)
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
[Discord.InteractionType.PING]: () =>
|
|
37
|
+
Effect.succeed({
|
|
38
|
+
type: Discord.InteractionCallbackType.PONG,
|
|
39
|
+
} as any),
|
|
40
|
+
|
|
41
|
+
[Discord.InteractionType.APPLICATION_COMMAND]: i => {
|
|
42
|
+
const data = i.data as Discord.ApplicationCommandDatum
|
|
43
|
+
|
|
44
|
+
return Maybe.fromNullable(Commands[data.name])
|
|
45
|
+
.match(
|
|
46
|
+
() => Effect.fail(new DefinitionNotFound(i)) as Handler<R, E>,
|
|
47
|
+
command =>
|
|
48
|
+
Effect.isEffect(command.handle)
|
|
49
|
+
? command.handle
|
|
50
|
+
: command.handle(context),
|
|
51
|
+
)
|
|
52
|
+
.provideService(Ctx.ApplicationCommand, data)
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
[Discord.InteractionType.MODAL_SUBMIT]: (i: Discord.Interaction) => {
|
|
56
|
+
const data = i.data as Discord.ModalSubmitDatum
|
|
57
|
+
|
|
58
|
+
return pipe(
|
|
59
|
+
ModalSubmit,
|
|
60
|
+
Arr.map(a =>
|
|
61
|
+
Effect.all({
|
|
62
|
+
command: Effect.succeed(a),
|
|
63
|
+
match: a.predicate(data.custom_id),
|
|
64
|
+
}),
|
|
65
|
+
),
|
|
66
|
+
_ =>
|
|
67
|
+
Effect.allPar(_)
|
|
68
|
+
.flatMap(_ =>
|
|
69
|
+
Arr.findFirst(_, _ => _.match).match(
|
|
70
|
+
() => Effect.fail(new DefinitionNotFound(i)) as Handler<R, E>,
|
|
71
|
+
a => a.command.handle,
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
.provideService(Ctx.ModalSubmitData, data),
|
|
75
|
+
)
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
[Discord.InteractionType.MESSAGE_COMPONENT]: i => {
|
|
79
|
+
const data = i.data as Discord.MessageComponentDatum
|
|
80
|
+
|
|
81
|
+
return pipe(
|
|
82
|
+
MessageComponent,
|
|
83
|
+
Arr.map(a =>
|
|
84
|
+
Effect.all({
|
|
85
|
+
command: Effect.succeed(a),
|
|
86
|
+
match: a.predicate(data.custom_id),
|
|
87
|
+
}),
|
|
88
|
+
),
|
|
89
|
+
_ =>
|
|
90
|
+
Effect.allPar(_)
|
|
91
|
+
.flatMap(commands =>
|
|
92
|
+
Arr.findFirst(commands, _ => _.match).match(
|
|
93
|
+
() => Effect.fail(new DefinitionNotFound(i)) as Handler<R, E>,
|
|
94
|
+
_ => _.command.handle,
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
.provideService(Ctx.MessageComponentData, data),
|
|
98
|
+
)
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
[Discord.InteractionType.APPLICATION_COMMAND_AUTOCOMPLETE]: i => {
|
|
102
|
+
const data = i.data as Discord.ApplicationCommandDatum
|
|
103
|
+
|
|
104
|
+
return IxHelpers.focusedOption(data)
|
|
105
|
+
.map(focusedOption =>
|
|
106
|
+
pipe(
|
|
107
|
+
Autocomplete,
|
|
108
|
+
Arr.map(_ =>
|
|
109
|
+
Effect.all({
|
|
110
|
+
command: Effect.succeed(_),
|
|
111
|
+
match: _.predicate(data, focusedOption),
|
|
112
|
+
}),
|
|
113
|
+
),
|
|
114
|
+
_ =>
|
|
115
|
+
Effect.allPar(_)
|
|
116
|
+
.flatMap(_ =>
|
|
117
|
+
Arr.findFirst(_, _ => _.match).match(
|
|
118
|
+
() =>
|
|
119
|
+
Effect.fail(new DefinitionNotFound(i)) as Handler<R, E>,
|
|
120
|
+
_ => _.command.handle,
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
.provideService(Ctx.ApplicationCommand, data)
|
|
124
|
+
.provideService(Ctx.FocusedOptionContext, { focusedOption }),
|
|
125
|
+
),
|
|
126
|
+
)
|
|
127
|
+
.getOrElse(() => Effect.fail(new DefinitionNotFound(i)))
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { DiscordREST } from "dfx"
|
|
2
|
+
import { Discord, Effect } from "dfx/_common"
|
|
3
|
+
import * as D from "./definitions.js"
|
|
4
|
+
import * as Http from "@effect-http/client"
|
|
5
|
+
|
|
6
|
+
export { response } from "../Helpers/interactions.js"
|
|
7
|
+
export * as helpers from "../Helpers/interactions.js"
|
|
8
|
+
export * from "./context.js"
|
|
9
|
+
export {
|
|
10
|
+
autocomplete,
|
|
11
|
+
global,
|
|
12
|
+
guild,
|
|
13
|
+
InteractionDefinition,
|
|
14
|
+
messageComponent,
|
|
15
|
+
modalSubmit,
|
|
16
|
+
} from "./definitions.js"
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @tsplus type dfx/InteractionBuilder
|
|
20
|
+
*/
|
|
21
|
+
export class InteractionBuilder<R, E> {
|
|
22
|
+
constructor(readonly definitions: D.InteractionDefinition<R, E>[]) {}
|
|
23
|
+
|
|
24
|
+
add<R1, E1>(definition: D.InteractionDefinition<R1, E1>) {
|
|
25
|
+
return new InteractionBuilder<R | R1, E | E1>([
|
|
26
|
+
...this.definitions,
|
|
27
|
+
definition,
|
|
28
|
+
])
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
concat<R1, E1>(builder: InteractionBuilder<R1, E1>) {
|
|
32
|
+
return new InteractionBuilder<R | R1, E | E1>([
|
|
33
|
+
...this.definitions,
|
|
34
|
+
...builder.definitions,
|
|
35
|
+
])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get syncGlobal() {
|
|
39
|
+
const commands = this.definitions
|
|
40
|
+
.filter(
|
|
41
|
+
(c): c is D.GlobalApplicationCommand<R, E> =>
|
|
42
|
+
c._tag === "GlobalApplicationCommand",
|
|
43
|
+
)
|
|
44
|
+
.map(c => c.command)
|
|
45
|
+
|
|
46
|
+
return DiscordREST.flatMap(rest =>
|
|
47
|
+
rest
|
|
48
|
+
.getCurrentBotApplicationInformation()
|
|
49
|
+
.flatMap(r => r.json)
|
|
50
|
+
.flatMap(app =>
|
|
51
|
+
rest.bulkOverwriteGlobalApplicationCommands(app.id, {
|
|
52
|
+
body: Http.body.json(commands),
|
|
53
|
+
}),
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
syncGuild(appId: Discord.Snowflake, guildId: Discord.Snowflake) {
|
|
59
|
+
const commands = this.definitions
|
|
60
|
+
.filter(
|
|
61
|
+
(c): c is D.GuildApplicationCommand<R, E> =>
|
|
62
|
+
c._tag === "GuildApplicationCommand",
|
|
63
|
+
)
|
|
64
|
+
.map(c => c.command)
|
|
65
|
+
|
|
66
|
+
return DiscordREST.flatMap(rest =>
|
|
67
|
+
rest.bulkOverwriteGuildApplicationCommands(
|
|
68
|
+
appId,
|
|
69
|
+
guildId,
|
|
70
|
+
commands as any,
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const builder = new InteractionBuilder<never, never>([])
|
|
77
|
+
|
|
78
|
+
// Filters
|
|
79
|
+
export const id = (query: string) => (customId: string) =>
|
|
80
|
+
Effect.succeed(query === customId)
|
|
81
|
+
|
|
82
|
+
export const idStartsWith = (query: string) => (customId: string) =>
|
|
83
|
+
Effect.succeed(customId.startsWith(query))
|
|
84
|
+
|
|
85
|
+
export const idRegex = (query: RegExp) => (customId: string) =>
|
|
86
|
+
Effect.succeed(query.test(customId))
|
|
87
|
+
|
|
88
|
+
export const option =
|
|
89
|
+
(command: string, optionName: string) =>
|
|
90
|
+
(
|
|
91
|
+
data: Pick<Discord.ApplicationCommandDatum, "name">,
|
|
92
|
+
focusedOption: Pick<
|
|
93
|
+
Discord.ApplicationCommandInteractionDataOption,
|
|
94
|
+
"name"
|
|
95
|
+
>,
|
|
96
|
+
) =>
|
|
97
|
+
Effect.succeed(data.name === command && focusedOption.name === optionName)
|
|
98
|
+
|
|
99
|
+
export const optionOnly =
|
|
100
|
+
(optionName: string) =>
|
|
101
|
+
(
|
|
102
|
+
_: unknown,
|
|
103
|
+
focusedOption: Pick<
|
|
104
|
+
Discord.ApplicationCommandInteractionDataOption,
|
|
105
|
+
"name"
|
|
106
|
+
>,
|
|
107
|
+
) =>
|
|
108
|
+
Effect.succeed(focusedOption.name === optionName)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as D from "./definitions.js"
|
|
2
|
+
|
|
3
|
+
export const splitDefinitions = <R, E>(
|
|
4
|
+
definitions: D.InteractionDefinition<R, E>[],
|
|
5
|
+
) => {
|
|
6
|
+
const grouped = definitions.reduce<{
|
|
7
|
+
[K in D.InteractionDefinition<R, E>["_tag"]]: Extract<
|
|
8
|
+
D.InteractionDefinition<R, E>,
|
|
9
|
+
{ _tag: K }
|
|
10
|
+
>[]
|
|
11
|
+
}>(
|
|
12
|
+
(acc, a) => ({
|
|
13
|
+
...acc,
|
|
14
|
+
[a._tag]: [...(acc[a._tag] ?? []), a],
|
|
15
|
+
}),
|
|
16
|
+
{
|
|
17
|
+
Autocomplete: [],
|
|
18
|
+
GlobalApplicationCommand: [],
|
|
19
|
+
GuildApplicationCommand: [],
|
|
20
|
+
MessageComponent: [],
|
|
21
|
+
ModalSubmit: [],
|
|
22
|
+
},
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const Commands = [
|
|
26
|
+
...grouped.GlobalApplicationCommand,
|
|
27
|
+
...grouped.GuildApplicationCommand,
|
|
28
|
+
].reduce(
|
|
29
|
+
(acc, a) => ({
|
|
30
|
+
...acc,
|
|
31
|
+
[a.command.name]: a,
|
|
32
|
+
}),
|
|
33
|
+
{} as Record<
|
|
34
|
+
string,
|
|
35
|
+
D.GlobalApplicationCommand<R, E> | D.GuildApplicationCommand<R, E>
|
|
36
|
+
>,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
...grouped,
|
|
41
|
+
Commands,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const MAP_HEX: Record<string, number> = {
|
|
45
|
+
0: 0,
|
|
46
|
+
1: 1,
|
|
47
|
+
2: 2,
|
|
48
|
+
3: 3,
|
|
49
|
+
4: 4,
|
|
50
|
+
5: 5,
|
|
51
|
+
6: 6,
|
|
52
|
+
7: 7,
|
|
53
|
+
8: 8,
|
|
54
|
+
9: 9,
|
|
55
|
+
a: 10,
|
|
56
|
+
b: 11,
|
|
57
|
+
c: 12,
|
|
58
|
+
d: 13,
|
|
59
|
+
e: 14,
|
|
60
|
+
f: 15,
|
|
61
|
+
A: 10,
|
|
62
|
+
B: 11,
|
|
63
|
+
C: 12,
|
|
64
|
+
D: 13,
|
|
65
|
+
E: 14,
|
|
66
|
+
F: 15,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function fromHex(hexString: string) {
|
|
70
|
+
const bytes = new Uint8Array(Math.floor((hexString || "").length / 2))
|
|
71
|
+
let i
|
|
72
|
+
for (i = 0; i < bytes.length; i++) {
|
|
73
|
+
const a = MAP_HEX[hexString[i * 2]]
|
|
74
|
+
const b = MAP_HEX[hexString[i * 2 + 1]]
|
|
75
|
+
if (a === undefined || b === undefined) {
|
|
76
|
+
break
|
|
77
|
+
}
|
|
78
|
+
bytes[i] = (a << 4) | b
|
|
79
|
+
}
|
|
80
|
+
return i === bytes.length ? bytes : bytes.slice(0, i)
|
|
81
|
+
}
|