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.
Files changed (100) hide show
  1. package/Cache/driver.js.map +1 -1
  2. package/Cache/memory.js.map +1 -1
  3. package/Cache/memoryTTL.js.map +1 -1
  4. package/Cache/prelude.js.map +1 -1
  5. package/Cache.js.map +1 -1
  6. package/DiscordConfig.js.map +1 -1
  7. package/DiscordGateway/DiscordWS.js +1 -1
  8. package/DiscordGateway/DiscordWS.js.map +1 -1
  9. package/DiscordGateway/Shard/heartbeats.js.map +1 -1
  10. package/DiscordGateway/Shard/identify.js.map +1 -1
  11. package/DiscordGateway/Shard/invalidSession.js.map +1 -1
  12. package/DiscordGateway/Shard/sendEvents.js.map +1 -1
  13. package/DiscordGateway/Shard/utils.js.map +1 -1
  14. package/DiscordGateway/Shard.js.map +1 -1
  15. package/DiscordGateway/ShardStore.js.map +1 -1
  16. package/DiscordGateway/Sharder.js.map +1 -1
  17. package/DiscordGateway/WS.d.ts +3 -3
  18. package/DiscordGateway/WS.js +25 -18
  19. package/DiscordGateway/WS.js.map +1 -1
  20. package/DiscordGateway.js.map +1 -1
  21. package/DiscordREST/types.js.map +1 -1
  22. package/DiscordREST/utils.js.map +1 -1
  23. package/DiscordREST.js.map +1 -1
  24. package/Helpers/flags.js.map +1 -1
  25. package/Helpers/intents.js.map +1 -1
  26. package/Helpers/interactions.js.map +1 -1
  27. package/Helpers/members.js.map +1 -1
  28. package/Helpers/permissions.js.map +1 -1
  29. package/Helpers/ui.js.map +1 -1
  30. package/Interactions/context.js.map +1 -1
  31. package/Interactions/definitions.js.map +1 -1
  32. package/Interactions/gateway.js.map +1 -1
  33. package/Interactions/handlers.js.map +1 -1
  34. package/Interactions/index.js.map +1 -1
  35. package/Interactions/utils.js.map +1 -1
  36. package/Interactions/webhook.js.map +1 -1
  37. package/Log.js.map +1 -1
  38. package/RateLimit/memory.js.map +1 -1
  39. package/RateLimit/utils.js.map +1 -1
  40. package/RateLimit.js.map +1 -1
  41. package/_common.js.map +1 -1
  42. package/gateway.js.map +1 -1
  43. package/global.js.map +1 -1
  44. package/index.js.map +1 -1
  45. package/package.json +50 -52
  46. package/src/Cache/driver.ts +31 -0
  47. package/src/Cache/memory.ts +76 -0
  48. package/src/Cache/memoryTTL.ts +201 -0
  49. package/src/Cache/prelude.ts +215 -0
  50. package/src/Cache.ts +140 -0
  51. package/src/DiscordConfig.ts +48 -0
  52. package/src/DiscordGateway/DiscordWS.ts +74 -0
  53. package/src/DiscordGateway/Shard/heartbeats.ts +42 -0
  54. package/src/DiscordGateway/Shard/identify.ts +52 -0
  55. package/src/DiscordGateway/Shard/invalidSession.ts +10 -0
  56. package/src/DiscordGateway/Shard/sendEvents.ts +37 -0
  57. package/src/DiscordGateway/Shard/utils.ts +14 -0
  58. package/src/DiscordGateway/Shard.ts +152 -0
  59. package/src/DiscordGateway/ShardStore.ts +33 -0
  60. package/src/DiscordGateway/Sharder.ts +102 -0
  61. package/src/DiscordGateway/WS.ts +122 -0
  62. package/src/DiscordGateway.ts +43 -0
  63. package/src/DiscordREST/types.ts +13 -0
  64. package/src/DiscordREST/utils.ts +33 -0
  65. package/src/DiscordREST.ts +203 -0
  66. package/src/Helpers/flags.ts +68 -0
  67. package/src/Helpers/intents.ts +34 -0
  68. package/src/Helpers/interactions.ts +229 -0
  69. package/src/Helpers/members.ts +14 -0
  70. package/src/Helpers/permissions.ts +140 -0
  71. package/src/Helpers/ui.ts +103 -0
  72. package/src/Interactions/context.ts +132 -0
  73. package/src/Interactions/definitions.ts +309 -0
  74. package/src/Interactions/gateway.ts +71 -0
  75. package/src/Interactions/handlers.ts +130 -0
  76. package/src/Interactions/index.ts +108 -0
  77. package/src/Interactions/utils.ts +81 -0
  78. package/src/Interactions/webhook.ts +110 -0
  79. package/src/Log.ts +17 -0
  80. package/src/RateLimit/memory.ts +57 -0
  81. package/src/RateLimit/utils.ts +27 -0
  82. package/src/RateLimit.ts +69 -0
  83. package/src/_common.ts +43 -0
  84. package/src/gateway.ts +38 -0
  85. package/src/global.ts +45 -0
  86. package/src/index.ts +20 -0
  87. package/src/package.json +52 -0
  88. package/src/types.ts +6368 -0
  89. package/src/utils/effect.ts +0 -0
  90. package/src/utils/hub.ts +47 -0
  91. package/src/utils/json.d.ts +1 -0
  92. package/src/utils/tsplus.ts +10 -0
  93. package/src/webhooks.ts +41 -0
  94. package/tsconfig.json +23 -0
  95. package/tsplus.config.json +8 -0
  96. package/types.js.map +1 -1
  97. package/utils/effect.js.map +1 -1
  98. package/utils/hub.js.map +1 -1
  99. package/utils/tsplus.js.map +1 -1
  100. 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
+ }