@impulsedev/chameleon 1.6.0 → 2.0.0
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/README.md +7 -7
- package/diff.txt +3086 -0
- package/dist/builders-CCZQJXF6.js +60 -0
- package/dist/chunk-F2RRJ7OJ.js +850 -0
- package/dist/index.d.ts +171 -142
- package/dist/index.js +451 -925
- package/media/discordchameleon.png +0 -0
- package/package.json +1 -1
package/diff.txt
ADDED
|
@@ -0,0 +1,3086 @@
|
|
|
1
|
+
diff --git a/src/builders/components.ts b/src/builders/components.ts
|
|
2
|
+
index 0e46b7e..a34a97b 100644
|
|
3
|
+
--- a/src/builders/components.ts
|
|
4
|
+
+++ b/src/builders/components.ts
|
|
5
|
+
@@ -4,6 +4,7 @@ import {
|
|
6
|
+
ComponentType,
|
|
7
|
+
ButtonStyle,
|
|
8
|
+
} from '../types/components/index.js'
|
|
9
|
+
+import { toSnakeCase } from '../utils/object.js'
|
|
10
|
+
|
|
11
|
+
function serializeEmoji(emoji?: Partial<Emoji>) {
|
|
12
|
+
|
|
13
|
+
@@ -59,7 +60,7 @@ export class ButtonBuilder {
|
|
14
|
+
} as MessageComponent
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
- toJSON(): any {
|
|
18
|
+
+ toJSON(): Record<string, unknown> {
|
|
19
|
+
return {
|
|
20
|
+
type: ComponentType.BUTTON,
|
|
21
|
+
custom_id: this.data.customId,
|
|
22
|
+
@@ -136,7 +137,7 @@ export class SelectMenuBuilder {
|
|
23
|
+
} as MessageComponent
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
- toJSON(): any {
|
|
27
|
+
+ toJSON(): Record<string, unknown> {
|
|
28
|
+
return {
|
|
29
|
+
type: this.data.type ?? ComponentType.STRING_SELECT,
|
|
30
|
+
custom_id: this.data.customId,
|
|
31
|
+
@@ -217,7 +218,7 @@ export class TextInputBuilder {
|
|
32
|
+
} as MessageComponent
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
- toJSON(): any {
|
|
36
|
+
+ toJSON(): Record<string, unknown> {
|
|
37
|
+
return {
|
|
38
|
+
type: ComponentType.TEXT_INPUT,
|
|
39
|
+
custom_id: this.data.customId,
|
|
40
|
+
@@ -243,10 +244,10 @@ export class ActionRowBuilder {
|
|
41
|
+
component:
|
|
42
|
+
| MessageComponent
|
|
43
|
+
| { build(): MessageComponent }
|
|
44
|
+
- | { toJSON(): any },
|
|
45
|
+
+ | { toJSON(): Record<string, unknown> },
|
|
46
|
+
): this {
|
|
47
|
+
|
|
48
|
+
- this.data.components!.push(component as any)
|
|
49
|
+
+ this.data.components!.push(component as MessageComponent)
|
|
50
|
+
|
|
51
|
+
return this
|
|
52
|
+
}
|
|
53
|
+
@@ -255,7 +256,7 @@ export class ActionRowBuilder {
|
|
54
|
+
...components: (
|
|
55
|
+
| MessageComponent
|
|
56
|
+
| { build(): MessageComponent }
|
|
57
|
+
- | { toJSON(): any }
|
|
58
|
+
+ | { toJSON(): unknown }
|
|
59
|
+
)[]
|
|
60
|
+
): this {
|
|
61
|
+
|
|
62
|
+
@@ -269,11 +270,19 @@ export class ActionRowBuilder {
|
|
63
|
+
build(): MessageComponent {
|
|
64
|
+
return {
|
|
65
|
+
...this.data,
|
|
66
|
+
- components: [...this.data.components!],
|
|
67
|
+
+ components: this.data.components!.map(component => {
|
|
68
|
+
+ if (
|
|
69
|
+
+ component &&
|
|
70
|
+
+ typeof (component as unknown as { build(): MessageComponent }).build === 'function'
|
|
71
|
+
+ ) {
|
|
72
|
+
+ return (component as unknown as { build(): MessageComponent }).build()
|
|
73
|
+
+ }
|
|
74
|
+
+ return component
|
|
75
|
+
+ }),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
- toJSON(): any {
|
|
80
|
+
+ toJSON(): Record<string, unknown> {
|
|
81
|
+
return {
|
|
82
|
+
type: ComponentType.ACTION_ROW,
|
|
83
|
+
|
|
84
|
+
@@ -281,9 +290,9 @@ export class ActionRowBuilder {
|
|
85
|
+
|
|
86
|
+
if (
|
|
87
|
+
component &&
|
|
88
|
+
- typeof (component as any).toJSON === 'function'
|
|
89
|
+
+ typeof (component as { toJSON?(): Record<string, unknown> }).toJSON === 'function'
|
|
90
|
+
) {
|
|
91
|
+
- return (component as any).toJSON()
|
|
92
|
+
+ return (component as unknown as { toJSON(): Record<string, unknown> }).toJSON()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return component
|
|
96
|
+
@@ -296,7 +305,7 @@ export class ModalBuilder {
|
|
97
|
+
|
|
98
|
+
private _title = ''
|
|
99
|
+
private _customId = ''
|
|
100
|
+
- private _components: any[] = []
|
|
101
|
+
+ private _components: (MessageComponent | { build(): MessageComponent } | { toJSON(): Record<string, unknown> })[] = []
|
|
102
|
+
|
|
103
|
+
setTitle(title: string): this {
|
|
104
|
+
this._title = title
|
|
105
|
+
@@ -312,7 +321,7 @@ export class ModalBuilder {
|
|
106
|
+
component:
|
|
107
|
+
| MessageComponent
|
|
108
|
+
| { build(): MessageComponent }
|
|
109
|
+
- | { toJSON(): any },
|
|
110
|
+
+ | { toJSON(): Record<string, unknown> },
|
|
111
|
+
): this {
|
|
112
|
+
|
|
113
|
+
this._components.push(component)
|
|
114
|
+
@@ -320,13 +329,7 @@ export class ModalBuilder {
|
|
115
|
+
return this
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
- addComponents(
|
|
119
|
+
- ...components: (
|
|
120
|
+
- | MessageComponent
|
|
121
|
+
- | { build(): MessageComponent }
|
|
122
|
+
- | { toJSON(): any }
|
|
123
|
+
- )[]
|
|
124
|
+
- ): this {
|
|
125
|
+
+ addComponents(...components: (MessageComponent | { build(): MessageComponent } | { toJSON(): unknown })[]) {
|
|
126
|
+
|
|
127
|
+
for (const component of components) {
|
|
128
|
+
this.addComponent(component)
|
|
129
|
+
@@ -339,7 +342,15 @@ export class ModalBuilder {
|
|
130
|
+
return {
|
|
131
|
+
title: this._title,
|
|
132
|
+
custom_id: this._customId,
|
|
133
|
+
- components: [...this._components],
|
|
134
|
+
+ components: this._components.map(component => {
|
|
135
|
+
+ if (
|
|
136
|
+
+ component &&
|
|
137
|
+
+ typeof (component as unknown as { build(): MessageComponent }).build === 'function'
|
|
138
|
+
+ ) {
|
|
139
|
+
+ return (component as unknown as { build(): MessageComponent }).build()
|
|
140
|
+
+ }
|
|
141
|
+
+ return component
|
|
142
|
+
+ }),
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@@ -352,13 +363,27 @@ export class ModalBuilder {
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
component &&
|
|
150
|
+
- typeof component.toJSON === 'function'
|
|
151
|
+
+ typeof (component as { toJSON?(): Record<string, unknown> }).toJSON === 'function'
|
|
152
|
+
) {
|
|
153
|
+
- return component.toJSON()
|
|
154
|
+
+ return (component as { toJSON(): Record<string, unknown> }).toJSON()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return component
|
|
158
|
+
}),
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
+}
|
|
162
|
+
+
|
|
163
|
+
+export function serializeComponent(component: MessageComponent | { build?(): MessageComponent } | { toJSON?(): Record<string, unknown> } | Record<string, unknown>): Record<string, unknown> {
|
|
164
|
+
+
|
|
165
|
+
+ if (!component) return {}
|
|
166
|
+
+ if (typeof (component as { toJSON?(): Record<string, unknown> }).toJSON === 'function') {
|
|
167
|
+
+ return (component as { toJSON(): Record<string, unknown> }).toJSON()
|
|
168
|
+
+ }
|
|
169
|
+
+
|
|
170
|
+
+ if (typeof (component as { build?(): MessageComponent }).build === 'function') {
|
|
171
|
+
+ return toSnakeCase((component as { build(): MessageComponent }).build()) as Record<string, unknown>
|
|
172
|
+
+ }
|
|
173
|
+
+
|
|
174
|
+
+ return toSnakeCase(component) as Record<string, unknown>
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
diff --git a/src/builders/embed.ts b/src/builders/embed.ts
|
|
178
|
+
index 14036f8..972ba08 100644
|
|
179
|
+
--- a/src/builders/embed.ts
|
|
180
|
+
+++ b/src/builders/embed.ts
|
|
181
|
+
@@ -1,4 +1,4 @@
|
|
182
|
+
-import type { Embed, EmbedField, EmbedFooter, EmbedAuthor, EmbedImage } from '../types/message/index.js'
|
|
183
|
+
+import type { Embed, EmbedField } from '../types/message/index.js'
|
|
184
|
+
|
|
185
|
+
export const Colors = {
|
|
186
|
+
Blue: 0x1e90ff,
|
|
187
|
+
@@ -144,12 +144,18 @@ export class EmbedBuilder {
|
|
188
|
+
if (e.image?.url) {
|
|
189
|
+
payload.image = {
|
|
190
|
+
url: e.image.url,
|
|
191
|
+
+ ...(e.image.proxyUrl ? { proxy_url: e.image.proxyUrl } : {}),
|
|
192
|
+
+ ...(typeof e.image.width === 'number' ? { width: e.image.width } : {}),
|
|
193
|
+
+ ...(typeof e.image.height === 'number' ? { height: e.image.height } : {})
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (e.thumbnail?.url) {
|
|
198
|
+
payload.thumbnail = {
|
|
199
|
+
url: e.thumbnail.url,
|
|
200
|
+
+ ...(e.thumbnail.proxyUrl ? { proxy_url: e.thumbnail.proxyUrl } : {}),
|
|
201
|
+
+ ...(typeof e.thumbnail.width === 'number' ? { width: e.thumbnail.width } : {}),
|
|
202
|
+
+ ...(typeof e.thumbnail.height === 'number' ? { height: e.thumbnail.height } : {})
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
diff --git a/src/builders/entities.ts b/src/builders/entities.ts
|
|
207
|
+
new file mode 100644
|
|
208
|
+
index 0000000..3eeeddf
|
|
209
|
+
--- /dev/null
|
|
210
|
+
+++ b/src/builders/entities.ts
|
|
211
|
+
@@ -0,0 +1,274 @@
|
|
212
|
+
+import type { StageInstance } from '../types/stage/index.js'
|
|
213
|
+
+import type { GuildScheduledEvent } from '../types/scheduled/index.js'
|
|
214
|
+
+import type { AutoModerationRule, AutoModerationAction, AutoModerationActionMetadata, AutoModerationTriggerMetadata } from '../types/automod/index.js'
|
|
215
|
+
+import type { Integration } from '../types/integration/index.js'
|
|
216
|
+
+import type { Emoji, Sticker, StickerItem } from '../types/expressions/index.js'
|
|
217
|
+
+import type { Voice } from '../types/voice/index.js'
|
|
218
|
+
+import type { Entitlement } from '../types/entitlement/index.js'
|
|
219
|
+
+import type { Interaction, InteractionData } from '../types/interaction/index.js'
|
|
220
|
+
+import type { User } from '../types/user/index.js'
|
|
221
|
+
+import type { Member } from '../types/guild/index.js'
|
|
222
|
+
+import type { TongueStore } from '../client/store.js'
|
|
223
|
+
+import { buildUser, buildMember } from './index.js'
|
|
224
|
+
+
|
|
225
|
+
+export function buildStageInstance(raw: Record<string, unknown>): StageInstance {
|
|
226
|
+
+
|
|
227
|
+
+ return {
|
|
228
|
+
+ id: raw.id as string,
|
|
229
|
+
+ guildId: (raw.guild_id as string) ?? '',
|
|
230
|
+
+ channelId: (raw.channel_id as string) ?? '',
|
|
231
|
+
+ topic: (raw.topic as string) ?? '',
|
|
232
|
+
+ privacyLevel: (raw.privacy_level as number) ?? 2,
|
|
233
|
+
+ discoverableDisabled: (raw.discoverable_disabled as boolean) ?? false,
|
|
234
|
+
+ guildScheduledEventId: (raw.guild_scheduled_event_id as string | null) ?? null,
|
|
235
|
+
+ }
|
|
236
|
+
+}
|
|
237
|
+
+
|
|
238
|
+
+export function buildScheduledEvent(raw: Record<string, unknown>): GuildScheduledEvent {
|
|
239
|
+
+
|
|
240
|
+
+ let creator: User | undefined
|
|
241
|
+
+
|
|
242
|
+
+ if (raw.creator) {
|
|
243
|
+
+ creator = buildUser(raw.creator as Record<string, unknown>)
|
|
244
|
+
+ }
|
|
245
|
+
+
|
|
246
|
+
+ return {
|
|
247
|
+
+ id: raw.id as string,
|
|
248
|
+
+ guildId: (raw.guild_id as string) ?? '',
|
|
249
|
+
+ channelId: (raw.channel_id as string | null) ?? null,
|
|
250
|
+
+ creatorId: (raw.creator_id as string | null) ?? null,
|
|
251
|
+
+ name: (raw.name as string) ?? '',
|
|
252
|
+
+ description: (raw.description as string | null) ?? null,
|
|
253
|
+
+ scheduledStartTime: raw.scheduled_start_time ? Date.parse(raw.scheduled_start_time as string) : 0,
|
|
254
|
+
+ scheduledEndTime: raw.scheduled_end_time ? Date.parse(raw.scheduled_end_time as string) : null,
|
|
255
|
+
+ privacyLevel: (raw.privacy_level as number) ?? 2,
|
|
256
|
+
+ status: (raw.status as number) ?? 1,
|
|
257
|
+
+ entityType: (raw.entity_type as number) ?? 1,
|
|
258
|
+
+ entityId: (raw.entity_id as string | null) ?? null,
|
|
259
|
+
+ entityMetadata: raw.entity_metadata
|
|
260
|
+
+ ? { ...((raw.entity_metadata as Record<string, unknown>).location !== undefined ? { location: (raw.entity_metadata as Record<string, unknown>).location as string } : {}) }
|
|
261
|
+
+ : null,
|
|
262
|
+
+ ...(creator ? { creator } : {}),
|
|
263
|
+
+ ...(raw.user_count !== undefined ? { userCount: raw.user_count as number } : {}),
|
|
264
|
+
+ ...(raw.image !== undefined ? { image: raw.image as string | null } : {}),
|
|
265
|
+
+ }
|
|
266
|
+
+}
|
|
267
|
+
+
|
|
268
|
+
+function buildAutoModActionMetadata(raw: Record<string, unknown>): AutoModerationActionMetadata {
|
|
269
|
+
+
|
|
270
|
+
+ return {
|
|
271
|
+
+ ...(raw.channel_id !== undefined ? { channelId: raw.channel_id as string } : {}),
|
|
272
|
+
+ ...(raw.duration_seconds !== undefined ? { durationSeconds: raw.duration_seconds as number } : {}),
|
|
273
|
+
+ ...(raw.custom_message !== undefined ? { customMessage: raw.custom_message as string } : {}),
|
|
274
|
+
+ }
|
|
275
|
+
+}
|
|
276
|
+
+
|
|
277
|
+
+function buildAutoModAction(raw: Record<string, unknown>): AutoModerationAction {
|
|
278
|
+
+
|
|
279
|
+
+ return {
|
|
280
|
+
+ type: raw.type as number,
|
|
281
|
+
+ ...(raw.metadata ? { metadata: buildAutoModActionMetadata(raw.metadata as Record<string, unknown>) } : {}),
|
|
282
|
+
+ }
|
|
283
|
+
+}
|
|
284
|
+
+
|
|
285
|
+
+function buildAutoModTriggerMetadata(raw: Record<string, unknown>): AutoModerationTriggerMetadata {
|
|
286
|
+
+
|
|
287
|
+
+ return {
|
|
288
|
+
+ ...(raw.keyword_filter !== undefined ? { keywordFilter: raw.keyword_filter as string[] } : {}),
|
|
289
|
+
+ ...(raw.regex_patterns !== undefined ? { regexPatterns: raw.regex_patterns as string[] } : {}),
|
|
290
|
+
+ ...(raw.presets !== undefined ? { presets: raw.presets as number[] } : {}),
|
|
291
|
+
+ ...(raw.allow_list !== undefined ? { allowList: raw.allow_list as string[] } : {}),
|
|
292
|
+
+ ...(raw.mention_total_limit !== undefined ? { mentionTotalLimit: raw.mention_total_limit as number } : {}),
|
|
293
|
+
+ ...(raw.mention_raid_protection_enabled !== undefined ? { mentionRaidProtectionEnabled: raw.mention_raid_protection_enabled as boolean } : {}),
|
|
294
|
+
+ }
|
|
295
|
+
+}
|
|
296
|
+
+
|
|
297
|
+
+export function buildAutoModRule(raw: Record<string, unknown>): AutoModerationRule {
|
|
298
|
+
+
|
|
299
|
+
+ return {
|
|
300
|
+
+ id: raw.id as string,
|
|
301
|
+
+ guildId: (raw.guild_id as string) ?? '',
|
|
302
|
+
+ name: (raw.name as string) ?? '',
|
|
303
|
+
+ creatorId: (raw.creator_id as string) ?? '',
|
|
304
|
+
+ eventType: (raw.event_type as number) ?? 1,
|
|
305
|
+
+ triggerType: (raw.trigger_type as number) ?? 1,
|
|
306
|
+
+ triggerMetadata: raw.trigger_metadata
|
|
307
|
+
+ ? buildAutoModTriggerMetadata(raw.trigger_metadata as Record<string, unknown>)
|
|
308
|
+
+ : {},
|
|
309
|
+
+ actions: Array.isArray(raw.actions)
|
|
310
|
+
+ ? (raw.actions as Record<string, unknown>[]).map(a => buildAutoModAction(a))
|
|
311
|
+
+ : [],
|
|
312
|
+
+ enabled: (raw.enabled as boolean) ?? false,
|
|
313
|
+
+ exemptRoles: (raw.exempt_roles as string[]) ?? [],
|
|
314
|
+
+ exemptChannels: (raw.exempt_channels as string[]) ?? [],
|
|
315
|
+
+ }
|
|
316
|
+
+}
|
|
317
|
+
+
|
|
318
|
+
+export function buildIntegration(raw: Record<string, unknown>): Integration {
|
|
319
|
+
+
|
|
320
|
+
+ let user: User | undefined
|
|
321
|
+
+
|
|
322
|
+
+ if (raw.user) {
|
|
323
|
+
+ user = buildUser(raw.user as Record<string, unknown>)
|
|
324
|
+
+ }
|
|
325
|
+
+
|
|
326
|
+
+ return {
|
|
327
|
+
+ id: raw.id as string,
|
|
328
|
+
+ name: (raw.name as string) ?? '',
|
|
329
|
+
+ type: (raw.type as string) ?? '',
|
|
330
|
+
+ enabled: (raw.enabled as boolean) ?? false,
|
|
331
|
+
+ ...(raw.syncing !== undefined ? { syncing: raw.syncing as boolean } : {}),
|
|
332
|
+
+ ...(raw.role_id !== undefined ? { roleId: raw.role_id as string } : {}),
|
|
333
|
+
+ ...(raw.enable_emoticons !== undefined ? { enableEmoticons: raw.enable_emoticons as boolean } : {}),
|
|
334
|
+
+ ...(raw.expire_behavior !== undefined ? { expireBehavior: raw.expire_behavior as number } : {}),
|
|
335
|
+
+ ...(raw.expire_grace_period !== undefined ? { expireGracePeriod: raw.expire_grace_period as number } : {}),
|
|
336
|
+
+ ...(user ? { user } : {}),
|
|
337
|
+
+ account: raw.account
|
|
338
|
+
+ ? { id: (raw.account as Record<string, unknown>).id as string, name: (raw.account as Record<string, unknown>).name as string }
|
|
339
|
+
+ : { id: '', name: '' },
|
|
340
|
+
+ ...(raw.synced_at ? { syncedAt: Date.parse(raw.synced_at as string) } : {}),
|
|
341
|
+
+ ...(raw.subscriber_count !== undefined ? { subscriberCount: raw.subscriber_count as number } : {}),
|
|
342
|
+
+ ...(raw.revoked !== undefined ? { revoked: raw.revoked as boolean } : {}),
|
|
343
|
+
+ ...(raw.application !== undefined ? { application: raw.application as import('../types/application/index.js').Application } : {}),
|
|
344
|
+
+ ...(raw.scopes !== undefined ? { scopes: raw.scopes as string[] } : {}),
|
|
345
|
+
+ }
|
|
346
|
+
+}
|
|
347
|
+
+
|
|
348
|
+
+export function buildEmoji(raw: Record<string, unknown>): Emoji {
|
|
349
|
+
+
|
|
350
|
+
+ let user: User | undefined
|
|
351
|
+
+
|
|
352
|
+
+ if (raw.user) {
|
|
353
|
+
+ user = buildUser(raw.user as Record<string, unknown>)
|
|
354
|
+
+ }
|
|
355
|
+
+
|
|
356
|
+
+ return {
|
|
357
|
+
+ id: (raw.id as string | null) ?? null,
|
|
358
|
+
+ name: (raw.name as string | null) ?? null,
|
|
359
|
+
+ ...(raw.roles !== undefined ? { roles: raw.roles as string[] } : {}),
|
|
360
|
+
+ ...(user ? { user } : {}),
|
|
361
|
+
+ ...(raw.require_colons !== undefined ? { requireColons: raw.require_colons as boolean } : {}),
|
|
362
|
+
+ ...(raw.managed !== undefined ? { managed: raw.managed as boolean } : {}),
|
|
363
|
+
+ ...(raw.animated !== undefined ? { animated: raw.animated as boolean } : {}),
|
|
364
|
+
+ ...(raw.available !== undefined ? { available: raw.available as boolean } : {}),
|
|
365
|
+
+ }
|
|
366
|
+
+}
|
|
367
|
+
+
|
|
368
|
+
+export function buildSticker(raw: Record<string, unknown>): Sticker {
|
|
369
|
+
+
|
|
370
|
+
+ let user: User | undefined
|
|
371
|
+
+
|
|
372
|
+
+ if (raw.user) {
|
|
373
|
+
+ user = buildUser(raw.user as Record<string, unknown>)
|
|
374
|
+
+ }
|
|
375
|
+
+
|
|
376
|
+
+ return {
|
|
377
|
+
+ id: raw.id as string,
|
|
378
|
+
+ ...(raw.pack_id !== undefined ? { packId: raw.pack_id as string } : {}),
|
|
379
|
+
+ name: (raw.name as string) ?? '',
|
|
380
|
+
+ description: (raw.description as string | null) ?? null,
|
|
381
|
+
+ tags: (raw.tags as string) ?? '',
|
|
382
|
+
+ type: (raw.type as number) ?? 1,
|
|
383
|
+
+ formatType: (raw.format_type as number) ?? 1,
|
|
384
|
+
+ ...(raw.available !== undefined ? { available: raw.available as boolean } : {}),
|
|
385
|
+
+ ...(raw.guild_id !== undefined ? { guildId: raw.guild_id as string } : {}),
|
|
386
|
+
+ ...(user ? { user } : {}),
|
|
387
|
+
+ ...(raw.sort_value !== undefined ? { sortValue: raw.sort_value as number } : {}),
|
|
388
|
+
+ }
|
|
389
|
+
+}
|
|
390
|
+
+
|
|
391
|
+
+export function buildStickerItem(raw: Record<string, unknown>): StickerItem {
|
|
392
|
+
+
|
|
393
|
+
+ return {
|
|
394
|
+
+ id: raw.id as string,
|
|
395
|
+
+ name: (raw.name as string) ?? '',
|
|
396
|
+
+ formatType: (raw.format_type as number) ?? 1,
|
|
397
|
+
+ }
|
|
398
|
+
+}
|
|
399
|
+
+
|
|
400
|
+
+export function buildVoiceState(raw: Record<string, unknown>, cache?: TongueStore): Voice {
|
|
401
|
+
+
|
|
402
|
+
+ let member: Member | undefined
|
|
403
|
+
+
|
|
404
|
+
+ if (raw.member && cache && raw.guild_id) {
|
|
405
|
+
+ member = buildMember(raw.member as Record<string, unknown>, raw.guild_id as string, cache)
|
|
406
|
+
+ }
|
|
407
|
+
+
|
|
408
|
+
+ return {
|
|
409
|
+
+ ...(raw.guild_id !== undefined ? { guildId: raw.guild_id as string } : {}),
|
|
410
|
+
+ channelId: (raw.channel_id as string) ?? '',
|
|
411
|
+
+ userId: (raw.user_id as string) ?? '',
|
|
412
|
+
+ ...(member ? { member } : {}),
|
|
413
|
+
+ sessionId: (raw.session_id as string) ?? '',
|
|
414
|
+
+ deaf: (raw.deaf as boolean) ?? false,
|
|
415
|
+
+ mute: (raw.mute as boolean) ?? false,
|
|
416
|
+
+ selfDeaf: (raw.self_deaf as boolean) ?? false,
|
|
417
|
+
+ selfMute: (raw.self_mute as boolean) ?? false,
|
|
418
|
+
+ selfStream: (raw.self_stream as boolean) ?? false,
|
|
419
|
+
+ selfVideo: (raw.self_video as boolean) ?? false,
|
|
420
|
+
+ suppress: (raw.suppress as boolean) ?? false,
|
|
421
|
+
+ requestToSpeakTimestamp: raw.request_to_speak_timestamp
|
|
422
|
+
+ ? Date.parse(raw.request_to_speak_timestamp as string)
|
|
423
|
+
+ : null,
|
|
424
|
+
+ }
|
|
425
|
+
+}
|
|
426
|
+
+
|
|
427
|
+
+export function buildEntitlement(raw: Record<string, unknown>): Entitlement {
|
|
428
|
+
+
|
|
429
|
+
+ return {
|
|
430
|
+
+ id: raw.id as string,
|
|
431
|
+
+ skuId: (raw.sku_id as string) ?? '',
|
|
432
|
+
+ applicationId: (raw.application_id as string) ?? '',
|
|
433
|
+
+ ...(raw.user_id !== undefined ? { userId: raw.user_id as string } : {}),
|
|
434
|
+
+ type: (raw.type as number) ?? 1,
|
|
435
|
+
+ deleted: (raw.deleted as boolean) ?? false,
|
|
436
|
+
+ ...(raw.starts_at !== undefined ? { startsAt: raw.starts_at ? Date.parse(raw.starts_at as string) : null } : {}),
|
|
437
|
+
+ ...(raw.ends_at !== undefined ? { endsAt: raw.ends_at ? Date.parse(raw.ends_at as string) : null } : {}),
|
|
438
|
+
+ ...(raw.guild_id !== undefined ? { guildId: raw.guild_id as string } : {}),
|
|
439
|
+
+ ...(raw.consumed !== undefined ? { consumed: raw.consumed as boolean } : {}),
|
|
440
|
+
+ }
|
|
441
|
+
+}
|
|
442
|
+
+
|
|
443
|
+
+export function buildInteraction(raw: Record<string, unknown>, cache?: TongueStore): Interaction {
|
|
444
|
+
+
|
|
445
|
+
+ let user: User | undefined
|
|
446
|
+
+ let member: Member | undefined
|
|
447
|
+
+
|
|
448
|
+
+ if (raw.member && raw.guild_id) {
|
|
449
|
+
+
|
|
450
|
+
+ const memberRaw = raw.member as Record<string, unknown>
|
|
451
|
+
+
|
|
452
|
+
+ if (cache) {
|
|
453
|
+
+ member = buildMember(memberRaw, raw.guild_id as string, cache)
|
|
454
|
+
+ }
|
|
455
|
+
+ if (memberRaw.user) {
|
|
456
|
+
+ user = buildUser(memberRaw.user as Record<string, unknown>)
|
|
457
|
+
+ }
|
|
458
|
+
+ }
|
|
459
|
+
+
|
|
460
|
+
+ if (!user && raw.user) {
|
|
461
|
+
+ user = buildUser(raw.user as Record<string, unknown>)
|
|
462
|
+
+ }
|
|
463
|
+
+
|
|
464
|
+
+ return {
|
|
465
|
+
+ id: raw.id as string,
|
|
466
|
+
+ applicationId: (raw.application_id as string) ?? '',
|
|
467
|
+
+ type: (raw.type as number) ?? 1,
|
|
468
|
+
+ ...(raw.data !== undefined ? { data: raw.data as InteractionData } : {}),
|
|
469
|
+
+ ...(raw.guild_id !== undefined ? { guildId: raw.guild_id as string } : {}),
|
|
470
|
+
+ ...(raw.channel_id !== undefined ? { channelId: raw.channel_id as string } : {}),
|
|
471
|
+
+ ...(member ? { member } : {}),
|
|
472
|
+
+ ...(user ? { user } : {}),
|
|
473
|
+
+ token: (raw.token as string) ?? '',
|
|
474
|
+
+ version: (raw.version as number) ?? 1,
|
|
475
|
+
+ ...(raw.message !== undefined ? { message: raw.message as import('../types/message/index.js').Message } : {}),
|
|
476
|
+
+ ...(raw.app_permissions !== undefined ? { appPermissions: raw.app_permissions as string } : {}),
|
|
477
|
+
+ ...(raw.locale !== undefined ? { locale: raw.locale as string } : {}),
|
|
478
|
+
+ ...(raw.guild_locale !== undefined ? { guildLocale: raw.guild_locale as string } : {}),
|
|
479
|
+
+ entitlements: Array.isArray(raw.entitlements)
|
|
480
|
+
+ ? (raw.entitlements as Record<string, unknown>[]).map(e => buildEntitlement(e))
|
|
481
|
+
+ : [],
|
|
482
|
+
+ authorizingIntegrationOwners: (raw.authorizing_integration_owners as Record<string, string>) ?? {},
|
|
483
|
+
+ ...(raw.context !== undefined ? { context: raw.context as number } : {}),
|
|
484
|
+
+ }
|
|
485
|
+
+}
|
|
486
|
+
|
|
487
|
+
diff --git a/src/builders/index.ts b/src/builders/index.ts
|
|
488
|
+
index bfa738c..9c94a96 100644
|
|
489
|
+
--- a/src/builders/index.ts
|
|
490
|
+
+++ b/src/builders/index.ts
|
|
491
|
+
@@ -4,9 +4,12 @@ import type { Guild, Member, Role } from '../types/guild/index.js'
|
|
492
|
+
import type { Message } from '../types/message/index.js'
|
|
493
|
+
import type { Emoji } from '../types/expressions/index.js'
|
|
494
|
+
import type { TongueStore } from '../client/store.js'
|
|
495
|
+
+import type { Client } from '../client/client.js'
|
|
496
|
+
+import type { ChameleonAPIResult } from '../rest/types.js'
|
|
497
|
+
|
|
498
|
+
export * from './embed.js'
|
|
499
|
+
export * from './components.js'
|
|
500
|
+
+export * from './entities.js'
|
|
501
|
+
|
|
502
|
+
export function buildUser(raw: Record<string, unknown>): User {
|
|
503
|
+
return {
|
|
504
|
+
@@ -28,6 +31,7 @@ export function buildUser(raw: Record<string, unknown>): User {
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export function buildChannel(raw: Record<string, unknown>, guildId?: string): Channel {
|
|
508
|
+
+
|
|
509
|
+
return {
|
|
510
|
+
id: raw.id as string,
|
|
511
|
+
type: raw.type as number,
|
|
512
|
+
@@ -46,6 +50,7 @@ export function buildChannel(raw: Record<string, unknown>, guildId?: string): Ch
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export function buildGuild(raw: Record<string, unknown>): Guild {
|
|
516
|
+
+
|
|
517
|
+
return {
|
|
518
|
+
id: raw.id as string,
|
|
519
|
+
name: raw.name as string,
|
|
520
|
+
@@ -81,6 +86,7 @@ export function buildGuild(raw: Record<string, unknown>): Guild {
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export function buildRole(raw: Record<string, unknown>): Role {
|
|
524
|
+
+
|
|
525
|
+
return {
|
|
526
|
+
id: raw.id as string,
|
|
527
|
+
name: raw.name as string,
|
|
528
|
+
@@ -157,18 +163,22 @@ export function buildMessage(raw: Record<string, unknown>, cache: TongueStore, o
|
|
529
|
+
return msg
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
-export function resolveChannel(channelId: string, cache: TongueStore): Channel | { id: string } {
|
|
533
|
+
- return cache.channels.get(channelId) ?? { id: channelId }
|
|
534
|
+
+export function resolveChannel(channelId: string, client: Client): Channel | { id: string, fetch: () => Promise<ChameleonAPIResult<Channel>> } {
|
|
535
|
+
+ return client.cache.channels.get(channelId) ?? { id: channelId, fetch: () => client.channels.fetch(channelId) }
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
-export function resolveGuild(guildId: string, cache: TongueStore): Guild | { id: string } {
|
|
539
|
+
- return cache.guilds.get(guildId) ?? { id: guildId }
|
|
540
|
+
+export function resolveGuild(guildId: string, client: Client): Guild | { id: string, fetch: () => Promise<ChameleonAPIResult<Guild>> } {
|
|
541
|
+
+ return client.cache.guilds.get(guildId) ?? { id: guildId, fetch: () => client.guilds.fetch(guildId) }
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
-export function resolveUser(userId: string, cache: TongueStore): User | { id: string } {
|
|
545
|
+
- return cache.users.get(userId) ?? { id: userId }
|
|
546
|
+
+export function resolveUser(userId: string, client: Client): User | { id: string, fetch: () => Promise<ChameleonAPIResult<User>> } {
|
|
547
|
+
+ return client.cache.users.get(userId) ?? { id: userId, fetch: () => client.users.fetch(userId) }
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
-export function resolveRole(roleId: string, cache: TongueStore): Role | { id: string } {
|
|
551
|
+
- return cache.roles.get(roleId) ?? { id: roleId }
|
|
552
|
+
+export function resolveRole(roleId: string, client: Client, guildId?: string): Role | { id: string, fetch?: () => Promise<ChameleonAPIResult<Role>> } {
|
|
553
|
+
+
|
|
554
|
+
+ const stub: { id: string, fetch?: () => Promise<ChameleonAPIResult<Role>> } = { id: roleId }
|
|
555
|
+
+ if (guildId) stub.fetch = () => client.guilds.roles(guildId).fetch(roleId)
|
|
556
|
+
+
|
|
557
|
+
+ return client.cache.roles.get(roleId) ?? stub
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
diff --git a/src/client/client.ts b/src/client/client.ts
|
|
561
|
+
index a81d496..97c56e4 100644
|
|
562
|
+
--- a/src/client/client.ts
|
|
563
|
+
+++ b/src/client/client.ts
|
|
564
|
+
@@ -6,7 +6,7 @@ import type { User } from '../types/user/index.ts'
|
|
565
|
+
import type { Guild } from '../types/guild/index.ts'
|
|
566
|
+
import { IntentBits, type IntentResolvable } from '../types/types.ts'
|
|
567
|
+
import { INTERACTION_TYPES } from '../utils/constants.ts'
|
|
568
|
+
-import { buildUser, buildChannel, buildGuild, buildRole, buildMember, buildMessage, resolveChannel } from '../builders/index.ts'
|
|
569
|
+
+import { buildUser, buildChannel, buildGuild, buildRole, buildMember, buildMessage, resolveChannel, buildStageInstance, buildScheduledEvent, buildAutoModRule, buildIntegration, buildVoiceState, buildEntitlement, buildInteraction, buildEmoji, buildSticker } from '../builders/index.ts'
|
|
570
|
+
import { CommandManager } from '../commands/index.ts'
|
|
571
|
+
import { ComponentManager } from '../components/index.ts'
|
|
572
|
+
import { UserManager, GuildManager, ChannelManager, MessageManager, CollectorManager } from '../managers/index.ts'
|
|
573
|
+
@@ -355,26 +355,30 @@ export class Client<TIntents extends readonly IntentResolvable[] = readonly Inte
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (Array.isArray(d.emojis)) {
|
|
577
|
+
- for (const raw of d.emojis as import('../types/expressions/index.ts').Emoji[]) {
|
|
578
|
+
- this.cache.emojis.set(raw.id as string, raw)
|
|
579
|
+
+ for (const raw of d.emojis as Record<string, unknown>[]) {
|
|
580
|
+
+ const emoji = buildEmoji(raw)
|
|
581
|
+
+ if (emoji.id) this.cache.emojis.set(emoji.id, emoji)
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (Array.isArray(d.stickers)) {
|
|
586
|
+
- for (const raw of d.stickers as import('../types/expressions/index.ts').Sticker[]) {
|
|
587
|
+
- this.cache.stickers.set(raw.id, raw)
|
|
588
|
+
+ for (const raw of d.stickers as Record<string, unknown>[]) {
|
|
589
|
+
+ const sticker = buildSticker(raw)
|
|
590
|
+
+ this.cache.stickers.set(sticker.id, sticker)
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (Array.isArray(d.stage_instances)) {
|
|
595
|
+
- for (const raw of d.stage_instances as import('../types/stage/index.ts').StageInstance[]) {
|
|
596
|
+
- this.cache.stageInstances.set(raw.id, raw)
|
|
597
|
+
+ for (const raw of d.stage_instances as Record<string, unknown>[]) {
|
|
598
|
+
+ const stage = buildStageInstance(raw)
|
|
599
|
+
+ this.cache.stageInstances.set(stage.id, stage)
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (Array.isArray(d.guild_scheduled_events)) {
|
|
604
|
+
- for (const raw of d.guild_scheduled_events as import('../types/scheduled/index.ts').GuildScheduledEvent[]) {
|
|
605
|
+
- this.cache.scheduledEvents.set(raw.id, raw)
|
|
606
|
+
+ for (const raw of d.guild_scheduled_events as Record<string, unknown>[]) {
|
|
607
|
+
+ const event = buildScheduledEvent(raw)
|
|
608
|
+
+ this.cache.scheduledEvents.set(event.id, event)
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
@@ -587,7 +591,7 @@ export class Client<TIntents extends readonly IntentResolvable[] = readonly Inte
|
|
613
|
+
this.dispatch('MESSAGE_CREATE', {
|
|
614
|
+
type: 'MESSAGE_CREATE',
|
|
615
|
+
message,
|
|
616
|
+
- channel: resolveChannel(message.channelId, this.cache) as import('../events/index.ts').PartialChannel
|
|
617
|
+
+ channel: resolveChannel(message.channelId, this) as import('../events/index.ts').PartialChannel
|
|
618
|
+
})
|
|
619
|
+
break
|
|
620
|
+
}
|
|
621
|
+
@@ -599,7 +603,7 @@ export class Client<TIntents extends readonly IntentResolvable[] = readonly Inte
|
|
622
|
+
this.dispatch('MESSAGE_UPDATE', {
|
|
623
|
+
type: 'MESSAGE_UPDATE',
|
|
624
|
+
message,
|
|
625
|
+
- channel: resolveChannel(message.channelId, this.cache) as import('../events/index.ts').PartialChannel,
|
|
626
|
+
+ channel: resolveChannel(message.channelId, this) as import('../events/index.ts').PartialChannel,
|
|
627
|
+
...(oldMessage ? { oldMessage } : {})
|
|
628
|
+
})
|
|
629
|
+
break
|
|
630
|
+
@@ -700,7 +704,7 @@ export class Client<TIntents extends readonly IntentResolvable[] = readonly Inte
|
|
631
|
+
|
|
632
|
+
this.dispatch('INTERACTION_CREATE', {
|
|
633
|
+
type: 'INTERACTION_CREATE',
|
|
634
|
+
- interaction: d as unknown as import('../types/interaction/index.ts').Interaction
|
|
635
|
+
+ interaction: buildInteraction(d as Record<string, unknown>, this.cache)
|
|
636
|
+
})
|
|
637
|
+
break
|
|
638
|
+
}
|
|
639
|
+
@@ -708,7 +712,7 @@ export class Client<TIntents extends readonly IntentResolvable[] = readonly Inte
|
|
640
|
+
case 'VOICE_STATE_UPDATE': {
|
|
641
|
+
this.dispatch('VOICE_STATE_UPDATE', {
|
|
642
|
+
type: 'VOICE_STATE_UPDATE',
|
|
643
|
+
- voiceState: d as unknown as import('../types/voice/index.ts').Voice
|
|
644
|
+
+ voiceState: buildVoiceState(d as Record<string, unknown>, this.cache)
|
|
645
|
+
})
|
|
646
|
+
break
|
|
647
|
+
}
|
|
648
|
+
@@ -761,15 +765,15 @@ export class Client<TIntents extends readonly IntentResolvable[] = readonly Inte
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
case 'ENTITLEMENT_CREATE': {
|
|
652
|
+
- this.dispatch('ENTITLEMENT_CREATE', { type: 'ENTITLEMENT_CREATE', entitlement: d as unknown as import('../types/entitlement/index.ts').Entitlement })
|
|
653
|
+
+ this.dispatch('ENTITLEMENT_CREATE', { type: 'ENTITLEMENT_CREATE', entitlement: buildEntitlement(d as Record<string, unknown>) })
|
|
654
|
+
break
|
|
655
|
+
}
|
|
656
|
+
case 'ENTITLEMENT_UPDATE': {
|
|
657
|
+
- this.dispatch('ENTITLEMENT_UPDATE', { type: 'ENTITLEMENT_UPDATE', entitlement: d as unknown as import('../types/entitlement/index.ts').Entitlement })
|
|
658
|
+
+ this.dispatch('ENTITLEMENT_UPDATE', { type: 'ENTITLEMENT_UPDATE', entitlement: buildEntitlement(d as Record<string, unknown>) })
|
|
659
|
+
break
|
|
660
|
+
}
|
|
661
|
+
case 'ENTITLEMENT_DELETE': {
|
|
662
|
+
- this.dispatch('ENTITLEMENT_DELETE', { type: 'ENTITLEMENT_DELETE', entitlement: d as unknown as import('../types/entitlement/index.ts').Entitlement })
|
|
663
|
+
+ this.dispatch('ENTITLEMENT_DELETE', { type: 'ENTITLEMENT_DELETE', entitlement: buildEntitlement(d as Record<string, unknown>) })
|
|
664
|
+
break
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
@@ -916,7 +920,7 @@ export class Client<TIntents extends readonly IntentResolvable[] = readonly Inte
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
case 'GUILD_EMOJIS_UPDATE': {
|
|
671
|
+
- const emojis = d.emojis as import('../types/expressions/index.ts').Emoji[]
|
|
672
|
+
+ const emojis = (d.emojis as Record<string, unknown>[]).map(raw => buildEmoji(raw))
|
|
673
|
+
for (const emoji of emojis) {
|
|
674
|
+
if (emoji.id) this.cache.emojis.set(emoji.id, emoji)
|
|
675
|
+
}
|
|
676
|
+
@@ -929,7 +933,7 @@ export class Client<TIntents extends readonly IntentResolvable[] = readonly Inte
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
case 'GUILD_STICKERS_UPDATE': {
|
|
680
|
+
- const stickers = d.stickers as import('../types/expressions/index.ts').Sticker[]
|
|
681
|
+
+ const stickers = (d.stickers as Record<string, unknown>[]).map(raw => buildSticker(raw))
|
|
682
|
+
for (const sticker of stickers) {
|
|
683
|
+
this.cache.stickers.set(sticker.id, sticker)
|
|
684
|
+
}
|
|
685
|
+
@@ -942,38 +946,38 @@ export class Client<TIntents extends readonly IntentResolvable[] = readonly Inte
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
case 'STAGE_INSTANCE_CREATE': {
|
|
689
|
+
- const stageInstance = d as unknown as import('../types/stage/index.ts').StageInstance
|
|
690
|
+
+ const stageInstance = buildStageInstance(d as Record<string, unknown>)
|
|
691
|
+
this.cache.stageInstances.set(stageInstance.id, stageInstance)
|
|
692
|
+
this.dispatch('STAGE_INSTANCE_CREATE', { type: 'STAGE_INSTANCE_CREATE', stageInstance })
|
|
693
|
+
break
|
|
694
|
+
}
|
|
695
|
+
case 'STAGE_INSTANCE_UPDATE': {
|
|
696
|
+
- const stageInstance = d as unknown as import('../types/stage/index.ts').StageInstance
|
|
697
|
+
+ const stageInstance = buildStageInstance(d as Record<string, unknown>)
|
|
698
|
+
this.cache.stageInstances.set(stageInstance.id, stageInstance)
|
|
699
|
+
this.dispatch('STAGE_INSTANCE_UPDATE', { type: 'STAGE_INSTANCE_UPDATE', stageInstance })
|
|
700
|
+
break
|
|
701
|
+
}
|
|
702
|
+
case 'STAGE_INSTANCE_DELETE': {
|
|
703
|
+
- const stageInstance = d as unknown as import('../types/stage/index.ts').StageInstance
|
|
704
|
+
+ const stageInstance = buildStageInstance(d as Record<string, unknown>)
|
|
705
|
+
this.cache.stageInstances.delete(stageInstance.id)
|
|
706
|
+
this.dispatch('STAGE_INSTANCE_DELETE', { type: 'STAGE_INSTANCE_DELETE', stageInstance })
|
|
707
|
+
break
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
case 'GUILD_SCHEDULED_EVENT_CREATE': {
|
|
711
|
+
- const scheduledEvent = d as unknown as import('../types/scheduled/index.ts').GuildScheduledEvent
|
|
712
|
+
+ const scheduledEvent = buildScheduledEvent(d as Record<string, unknown>)
|
|
713
|
+
this.cache.scheduledEvents.set(scheduledEvent.id, scheduledEvent)
|
|
714
|
+
this.dispatch('GUILD_SCHEDULED_EVENT_CREATE', { type: 'GUILD_SCHEDULED_EVENT_CREATE', scheduledEvent })
|
|
715
|
+
break
|
|
716
|
+
}
|
|
717
|
+
case 'GUILD_SCHEDULED_EVENT_UPDATE': {
|
|
718
|
+
- const scheduledEvent = d as unknown as import('../types/scheduled/index.ts').GuildScheduledEvent
|
|
719
|
+
+ const scheduledEvent = buildScheduledEvent(d as Record<string, unknown>)
|
|
720
|
+
this.cache.scheduledEvents.set(scheduledEvent.id, scheduledEvent)
|
|
721
|
+
this.dispatch('GUILD_SCHEDULED_EVENT_UPDATE', { type: 'GUILD_SCHEDULED_EVENT_UPDATE', scheduledEvent })
|
|
722
|
+
break
|
|
723
|
+
}
|
|
724
|
+
case 'GUILD_SCHEDULED_EVENT_DELETE': {
|
|
725
|
+
- const scheduledEvent = d as unknown as import('../types/scheduled/index.ts').GuildScheduledEvent
|
|
726
|
+
+ const scheduledEvent = buildScheduledEvent(d as Record<string, unknown>)
|
|
727
|
+
this.cache.scheduledEvents.delete(scheduledEvent.id)
|
|
728
|
+
this.dispatch('GUILD_SCHEDULED_EVENT_DELETE', { type: 'GUILD_SCHEDULED_EVENT_DELETE', scheduledEvent })
|
|
729
|
+
break
|
|
730
|
+
@@ -988,19 +992,19 @@ export class Client<TIntents extends readonly IntentResolvable[] = readonly Inte
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
case 'AUTO_MODERATION_RULE_CREATE': {
|
|
734
|
+
- const rule = d as unknown as import('../types/automod/index.ts').AutoModerationRule
|
|
735
|
+
+ const rule = buildAutoModRule(d as Record<string, unknown>)
|
|
736
|
+
this.cache.autoModRules.set(rule.id, rule)
|
|
737
|
+
this.dispatch('AUTO_MODERATION_RULE_CREATE', { type: 'AUTO_MODERATION_RULE_CREATE', rule })
|
|
738
|
+
break
|
|
739
|
+
}
|
|
740
|
+
case 'AUTO_MODERATION_RULE_UPDATE': {
|
|
741
|
+
- const rule = d as unknown as import('../types/automod/index.ts').AutoModerationRule
|
|
742
|
+
+ const rule = buildAutoModRule(d as Record<string, unknown>)
|
|
743
|
+
this.cache.autoModRules.set(rule.id, rule)
|
|
744
|
+
this.dispatch('AUTO_MODERATION_RULE_UPDATE', { type: 'AUTO_MODERATION_RULE_UPDATE', rule })
|
|
745
|
+
break
|
|
746
|
+
}
|
|
747
|
+
case 'AUTO_MODERATION_RULE_DELETE': {
|
|
748
|
+
- const rule = d as unknown as import('../types/automod/index.ts').AutoModerationRule
|
|
749
|
+
+ const rule = buildAutoModRule(d as Record<string, unknown>)
|
|
750
|
+
this.cache.autoModRules.delete(rule.id)
|
|
751
|
+
this.dispatch('AUTO_MODERATION_RULE_DELETE', { type: 'AUTO_MODERATION_RULE_DELETE', rule })
|
|
752
|
+
break
|
|
753
|
+
@@ -1023,13 +1027,13 @@ export class Client<TIntents extends readonly IntentResolvable[] = readonly Inte
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
case 'INTEGRATION_CREATE': {
|
|
757
|
+
- const integration = d as unknown as import('../types/integration/index.ts').Integration
|
|
758
|
+
+ const integration = buildIntegration(d as Record<string, unknown>)
|
|
759
|
+
this.cache.integrations.set(integration.id, integration)
|
|
760
|
+
this.dispatch('INTEGRATION_CREATE', { type: 'INTEGRATION_CREATE', guildId: d.guild_id as string, integration })
|
|
761
|
+
break
|
|
762
|
+
}
|
|
763
|
+
case 'INTEGRATION_UPDATE': {
|
|
764
|
+
- const integration = d as unknown as import('../types/integration/index.ts').Integration
|
|
765
|
+
+ const integration = buildIntegration(d as Record<string, unknown>)
|
|
766
|
+
this.cache.integrations.set(integration.id, integration)
|
|
767
|
+
this.dispatch('INTEGRATION_UPDATE', { type: 'INTEGRATION_UPDATE', guildId: d.guild_id as string, integration })
|
|
768
|
+
break
|
|
769
|
+
diff --git a/src/commands/command.ts b/src/commands/command.ts
|
|
770
|
+
index 98208f9..011b095 100644
|
|
771
|
+
--- a/src/commands/command.ts
|
|
772
|
+
+++ b/src/commands/command.ts
|
|
773
|
+
@@ -1,21 +1,21 @@
|
|
774
|
+
-import type { OptionDef, ResolveOptions } from './options.js'
|
|
775
|
+
+import type { OptionDef, ResolveOptions, OptionType } from './options.js'
|
|
776
|
+
import type { CommandContext } from './context.js'
|
|
777
|
+
|
|
778
|
+
-export type ExecuteFunction<O extends Record<string, OptionDef<any, boolean>>> = (ctx: CommandContext<ResolveOptions<O>>) => void | Promise<void>
|
|
779
|
+
+export type ExecuteFunction<O extends Record<string, OptionDef<OptionType, boolean>>> = (ctx: CommandContext<ResolveOptions<O>>) => void | Promise<void>
|
|
780
|
+
|
|
781
|
+
-export interface Subcommand<O extends Record<string, OptionDef<any, boolean>> = Record<string, never>> {
|
|
782
|
+
+export interface Subcommand<O extends Record<string, OptionDef<OptionType, boolean>> = Record<string, never>> {
|
|
783
|
+
description: string
|
|
784
|
+
options?: O
|
|
785
|
+
execute: ExecuteFunction<O>
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
-export function defineSubcommand<O extends Record<string, OptionDef<any, boolean>>>(def: Subcommand<O>): Subcommand<O> {
|
|
789
|
+
+export function defineSubcommand<O extends Record<string, OptionDef<OptionType, boolean>>>(def: Subcommand<O>): Subcommand<O> {
|
|
790
|
+
return def
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
export type CommandDef<
|
|
794
|
+
- O extends Record<string, OptionDef<any, boolean>> = Record<string, never>,
|
|
795
|
+
- S extends Record<string, Subcommand<any>> = Record<string, never>
|
|
796
|
+
+ O extends Record<string, OptionDef<OptionType, boolean>> = Record<string, never>,
|
|
797
|
+
+ S extends Record<string, Subcommand<Record<string, OptionDef<OptionType, boolean>>>> = Record<string, never>
|
|
798
|
+
> = {
|
|
799
|
+
name: string
|
|
800
|
+
description: string
|
|
801
|
+
@@ -24,9 +24,11 @@ export type CommandDef<
|
|
802
|
+
execute?: ExecuteFunction<O>
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
+export type AnyCommandDef = CommandDef<Record<string, OptionDef<OptionType, boolean>>, Record<string, Subcommand<Record<string, OptionDef<OptionType, boolean>>>>>
|
|
806
|
+
+
|
|
807
|
+
export function defineCommand<
|
|
808
|
+
- O extends Record<string, OptionDef<any, boolean>>,
|
|
809
|
+
- S extends Record<string, Subcommand<any>>
|
|
810
|
+
+ O extends Record<string, OptionDef<OptionType, boolean>>,
|
|
811
|
+
+ S extends Record<string, Subcommand<Record<string, OptionDef<OptionType, boolean>>>>
|
|
812
|
+
>(def: CommandDef<O, S>): CommandDef<O, S> {
|
|
813
|
+
|
|
814
|
+
if (!def.execute && (!def.subcommands || Object.keys(def.subcommands).length === 0)) {
|
|
815
|
+
diff --git a/src/commands/context.ts b/src/commands/context.ts
|
|
816
|
+
index f757816..98e97ab 100644
|
|
817
|
+
--- a/src/commands/context.ts
|
|
818
|
+
+++ b/src/commands/context.ts
|
|
819
|
+
@@ -2,40 +2,39 @@ import type { Client } from '../client/client.js'
|
|
820
|
+
import type { User } from '../types/user/index.js'
|
|
821
|
+
import type { Guild } from '../types/guild/index.js'
|
|
822
|
+
import type { Channel } from '../types/channel/index.js'
|
|
823
|
+
-import { INTERACTION_CALLBACK_TYPES, COMPONENT_TYPES } from '../utils/constants.js'
|
|
824
|
+
+import type { Embed } from '../types/message/index.js'
|
|
825
|
+
+import type { MessageComponent } from '../types/components/index.js'
|
|
826
|
+
+import { serializeComponent } from '../builders/index.js'
|
|
827
|
+
+import { INTERACTION_CALLBACK_TYPES, COMPONENT_TYPES, MESSAGE_FLAGS } from '../utils/constants.js'
|
|
828
|
+
|
|
829
|
+
export type InteractionReplyOptions = string | {
|
|
830
|
+
content?: string
|
|
831
|
+
- embeds?: any[]
|
|
832
|
+
- components?: any[]
|
|
833
|
+
+ embeds?: (Embed | { toJSON(): Record<string, unknown> } | Record<string, unknown>)[]
|
|
834
|
+
+ components?: (MessageComponent | { build?(): MessageComponent } | { toJSON(): Record<string, unknown> } | Record<string, unknown>)[]
|
|
835
|
+
ephemeral?: boolean
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
-export class CommandContext<Options = Record<string, any>> {
|
|
839
|
+
-
|
|
840
|
+
- public options: Options
|
|
841
|
+
+export class BaseInteractionContext {
|
|
842
|
+
public user: User
|
|
843
|
+
public guild?: Guild | { id: string } | undefined
|
|
844
|
+
public channel?: Channel | { id: string } | undefined
|
|
845
|
+
public interactionId: string
|
|
846
|
+
public interactionToken: string
|
|
847
|
+
|
|
848
|
+
- private _client: Client
|
|
849
|
+
- private _deferred = false
|
|
850
|
+
- private _replied = false
|
|
851
|
+
+ protected _client: Client
|
|
852
|
+
+ protected _deferred = false
|
|
853
|
+
+ protected _replied = false
|
|
854
|
+
|
|
855
|
+
constructor(
|
|
856
|
+
client: Client,
|
|
857
|
+
- rawInteraction: Record<string, any>,
|
|
858
|
+
- parsedOptions: Options,
|
|
859
|
+
+ raw: Record<string, unknown>,
|
|
860
|
+
user: User,
|
|
861
|
+
guild?: Guild | { id: string },
|
|
862
|
+
channel?: Channel | { id: string }
|
|
863
|
+
) {
|
|
864
|
+
this._client = client
|
|
865
|
+
- this.interactionId = rawInteraction.id
|
|
866
|
+
- this.interactionToken = rawInteraction.token
|
|
867
|
+
- this.options = parsedOptions
|
|
868
|
+
+ this.interactionId = raw.id as string
|
|
869
|
+
+ this.interactionToken = raw.token as string
|
|
870
|
+
this.user = user
|
|
871
|
+
this.guild = guild
|
|
872
|
+
this.channel = channel
|
|
873
|
+
@@ -44,12 +43,34 @@ export class CommandContext<Options = Record<string, any>> {
|
|
874
|
+
get replied() { return this._replied }
|
|
875
|
+
get deferred() { return this._deferred }
|
|
876
|
+
|
|
877
|
+
+ protected _resolvePayload(payload: InteractionReplyOptions): Record<string, unknown> {
|
|
878
|
+
+
|
|
879
|
+
+ const data: Record<string, unknown> = typeof payload === 'string' ? { content: payload } : { ...payload }
|
|
880
|
+
+
|
|
881
|
+
+ if (typeof payload === 'object') {
|
|
882
|
+
+
|
|
883
|
+
+ if (payload.ephemeral) data.flags = MESSAGE_FLAGS.EPHEMERAL
|
|
884
|
+
+ if (payload.embeds) {
|
|
885
|
+
+ data.embeds = payload.embeds.map((e: unknown) => {
|
|
886
|
+
+ if (e && typeof (e as Record<string, unknown>).toJSON === 'function') {
|
|
887
|
+
+ return (e as { toJSON(): Record<string, unknown> }).toJSON()
|
|
888
|
+
+ }
|
|
889
|
+
+ return e
|
|
890
|
+
+ })
|
|
891
|
+
+ }
|
|
892
|
+
+
|
|
893
|
+
+ if (payload.components) {
|
|
894
|
+
+ data.components = payload.components.map(c => serializeComponent(c))
|
|
895
|
+
+ }
|
|
896
|
+
+ }
|
|
897
|
+
+ return data
|
|
898
|
+
+ }
|
|
899
|
+
+
|
|
900
|
+
async reply(payload: InteractionReplyOptions): Promise<void> {
|
|
901
|
+
|
|
902
|
+
if (this._replied || this._deferred) throw new Error('Interaction already acknowledged.')
|
|
903
|
+
|
|
904
|
+
- const data: any = typeof payload === 'string' ? { content: payload } : { ...payload }
|
|
905
|
+
- if (typeof payload === 'object' && payload.ephemeral) data.flags = 64
|
|
906
|
+
+ const data = this._resolvePayload(payload)
|
|
907
|
+
|
|
908
|
+
await this._client.rest.post(`/interactions/${this.interactionId}/${this.interactionToken}/callback`, {
|
|
909
|
+
type: INTERACTION_CALLBACK_TYPES.CHANNEL_MESSAGE_WITH_SOURCE,
|
|
910
|
+
@@ -64,36 +85,33 @@ export class CommandContext<Options = Record<string, any>> {
|
|
911
|
+
|
|
912
|
+
await this._client.rest.post(`/interactions/${this.interactionId}/${this.interactionToken}/callback`, {
|
|
913
|
+
type: INTERACTION_CALLBACK_TYPES.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
|
|
914
|
+
- data: {
|
|
915
|
+
- flags: options?.ephemeral ? 64 : 0
|
|
916
|
+
- }
|
|
917
|
+
+ data: { flags: options?.ephemeral ? MESSAGE_FLAGS.EPHEMERAL : 0 }
|
|
918
|
+
})
|
|
919
|
+
this._deferred = true
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
async followUp(payload: InteractionReplyOptions): Promise<void> {
|
|
923
|
+
+
|
|
924
|
+
+ if (!this._deferred && !this._replied) throw new Error('Interaction not acknowledged.')
|
|
925
|
+
|
|
926
|
+
- if (!this._deferred && !this._replied) throw new Error('Interaction not acknowledged. Use reply or defer first.')
|
|
927
|
+
-
|
|
928
|
+
- const data: any = typeof payload === 'string' ? { content: payload } : { ...payload }
|
|
929
|
+
- if (typeof payload === 'object' && payload.ephemeral) data.flags = 64
|
|
930
|
+
+ const data = this._resolvePayload(payload)
|
|
931
|
+
|
|
932
|
+
await this._client.rest.post(`/webhooks/${this._client.user?.id}/${this.interactionToken}`, data)
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
- async showModal(modal: any): Promise<void> {
|
|
936
|
+
+ async showModal(modal: Record<string, unknown>): Promise<void> {
|
|
937
|
+
|
|
938
|
+
if (this._replied || this._deferred) throw new Error('Interaction already acknowledged.')
|
|
939
|
+
|
|
940
|
+
const payload = modal.type === 'modal' ? {
|
|
941
|
+
custom_id: modal.customId,
|
|
942
|
+
title: modal.title,
|
|
943
|
+
- components: modal.fields.map((f: any) => ({
|
|
944
|
+
+ components: Array.isArray(modal.fields) ? modal.fields.map((f: Record<string, unknown>) => ({
|
|
945
|
+
type: COMPONENT_TYPES.ACTION_ROW,
|
|
946
|
+
components: [{
|
|
947
|
+
type: COMPONENT_TYPES.TEXT_INPUT,
|
|
948
|
+
custom_id: f.id,
|
|
949
|
+
- style: f.type, // 1 short, 2 paragraph
|
|
950
|
+
+ style: f.type,
|
|
951
|
+
label: f.label,
|
|
952
|
+
required: f.required,
|
|
953
|
+
min_length: f.minLength,
|
|
954
|
+
@@ -101,7 +119,7 @@ export class CommandContext<Options = Record<string, any>> {
|
|
955
|
+
placeholder: f.placeholder,
|
|
956
|
+
value: f.value
|
|
957
|
+
}]
|
|
958
|
+
- }))
|
|
959
|
+
+ })) : []
|
|
960
|
+
} : modal
|
|
961
|
+
|
|
962
|
+
await this._client.rest.post(`/interactions/${this.interactionId}/${this.interactionToken}/callback`, {
|
|
963
|
+
@@ -110,4 +128,21 @@ export class CommandContext<Options = Record<string, any>> {
|
|
964
|
+
})
|
|
965
|
+
this._replied = true
|
|
966
|
+
}
|
|
967
|
+
+}
|
|
968
|
+
+
|
|
969
|
+
+export class CommandContext<Options = Record<string, unknown>> extends BaseInteractionContext {
|
|
970
|
+
+
|
|
971
|
+
+ public options: Options
|
|
972
|
+
+
|
|
973
|
+
+ constructor(
|
|
974
|
+
+ client: Client,
|
|
975
|
+
+ rawInteraction: Record<string, unknown>,
|
|
976
|
+
+ parsedOptions: Options,
|
|
977
|
+
+ user: User,
|
|
978
|
+
+ guild?: Guild | { id: string },
|
|
979
|
+
+ channel?: Channel | { id: string }
|
|
980
|
+
+ ) {
|
|
981
|
+
+ super(client, rawInteraction, user, guild, channel)
|
|
982
|
+
+ this.options = parsedOptions
|
|
983
|
+
+ }
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
diff --git a/src/commands/interactions.ts b/src/commands/interactions.ts
|
|
987
|
+
index 5391cff..1c108a0 100644
|
|
988
|
+
--- a/src/commands/interactions.ts
|
|
989
|
+
+++ b/src/commands/interactions.ts
|
|
990
|
+
@@ -2,180 +2,42 @@ import type { Client } from '../client/client.js'
|
|
991
|
+
import type { User } from '../types/user/index.js'
|
|
992
|
+
import type { Guild } from '../types/guild/index.js'
|
|
993
|
+
import type { Channel } from '../types/channel/index.js'
|
|
994
|
+
-import type { InteractionReplyOptions } from './context.js'
|
|
995
|
+
-import { INTERACTION_CALLBACK_TYPES } from '../utils/constants.js'
|
|
996
|
+
+import { BaseInteractionContext } from './context.js'
|
|
997
|
+
|
|
998
|
+
-export class ComponentContext {
|
|
999
|
+
-
|
|
1000
|
+
- public customId: string
|
|
1001
|
+
- public user: User
|
|
1002
|
+
- public guild?: Guild | { id: string } | undefined
|
|
1003
|
+
- public channel?: Channel | { id: string } | undefined
|
|
1004
|
+
- public message?: Record<string, unknown>
|
|
1005
|
+
-
|
|
1006
|
+
- public interactionId: string
|
|
1007
|
+
- public interactionToken: string
|
|
1008
|
+
-
|
|
1009
|
+
- private _client: Client
|
|
1010
|
+
- private _deferred = false
|
|
1011
|
+
- private _replied = false
|
|
1012
|
+
-
|
|
1013
|
+
- constructor(
|
|
1014
|
+
- client: Client,
|
|
1015
|
+
- raw: Record<string, any>,
|
|
1016
|
+
- user: User,
|
|
1017
|
+
- guild?: Guild | { id: string },
|
|
1018
|
+
- channel?: Channel | { id: string }
|
|
1019
|
+
- ) {
|
|
1020
|
+
- this._client = client
|
|
1021
|
+
- this.interactionId = raw.id
|
|
1022
|
+
- this.interactionToken = raw.token
|
|
1023
|
+
- this.customId = raw.data?.custom_id ?? ''
|
|
1024
|
+
- this.message = raw.message
|
|
1025
|
+
- this.user = user
|
|
1026
|
+
- this.guild = guild
|
|
1027
|
+
- this.channel = channel
|
|
1028
|
+
- }
|
|
1029
|
+
-
|
|
1030
|
+
- get replied() { return this._replied }
|
|
1031
|
+
- get deferred() { return this._deferred }
|
|
1032
|
+
-
|
|
1033
|
+
- /** Values from a select menu interaction */
|
|
1034
|
+
- get values(): string[] {
|
|
1035
|
+
- return (this as any)._values ?? []
|
|
1036
|
+
- }
|
|
1037
|
+
-
|
|
1038
|
+
- async reply(payload: InteractionReplyOptions): Promise<void> {
|
|
1039
|
+
-
|
|
1040
|
+
- if (this._replied || this._deferred) throw new Error('Interaction already acknowledged.')
|
|
1041
|
+
- const data: any = typeof payload === 'string' ? { content: payload } : { ...payload }
|
|
1042
|
+
- if (typeof payload === 'object' && payload.ephemeral) data.flags = 64
|
|
1043
|
+
-
|
|
1044
|
+
- await this._client.rest.post(`/interactions/${this.interactionId}/${this.interactionToken}/callback`, {
|
|
1045
|
+
- type: INTERACTION_CALLBACK_TYPES.CHANNEL_MESSAGE_WITH_SOURCE,
|
|
1046
|
+
- data
|
|
1047
|
+
- })
|
|
1048
|
+
- this._replied = true
|
|
1049
|
+
- }
|
|
1050
|
+
-
|
|
1051
|
+
- async deferUpdate(): Promise<void> {
|
|
1052
|
+
-
|
|
1053
|
+
- if (this._replied || this._deferred) throw new Error('Interaction already acknowledged.')
|
|
1054
|
+
- await this._client.rest.post(`/interactions/${this.interactionId}/${this.interactionToken}/callback`, {
|
|
1055
|
+
- type: INTERACTION_CALLBACK_TYPES.DEFERRED_UPDATE_MESSAGE
|
|
1056
|
+
- })
|
|
1057
|
+
- this._deferred = true
|
|
1058
|
+
- }
|
|
1059
|
+
-
|
|
1060
|
+
- async update(payload: InteractionReplyOptions): Promise<void> {
|
|
1061
|
+
-
|
|
1062
|
+
- if (this._replied || this._deferred) throw new Error('Interaction already acknowledged.')
|
|
1063
|
+
- const data: any = typeof payload === 'string' ? { content: payload } : { ...payload }
|
|
1064
|
+
- if (typeof payload === 'object' && payload.ephemeral) data.flags = 64
|
|
1065
|
+
-
|
|
1066
|
+
- await this._client.rest.post(`/interactions/${this.interactionId}/${this.interactionToken}/callback`, {
|
|
1067
|
+
- type: INTERACTION_CALLBACK_TYPES.UPDATE_MESSAGE,
|
|
1068
|
+
- data
|
|
1069
|
+
- })
|
|
1070
|
+
- this._replied = true
|
|
1071
|
+
- }
|
|
1072
|
+
-
|
|
1073
|
+
- async followUp(payload: InteractionReplyOptions): Promise<void> {
|
|
1074
|
+
-
|
|
1075
|
+
- if (!this._deferred && !this._replied) throw new Error('Interaction not acknowledged.')
|
|
1076
|
+
- const data: any = typeof payload === 'string' ? { content: payload } : { ...payload }
|
|
1077
|
+
-
|
|
1078
|
+
- if (typeof payload === 'object' && payload.ephemeral) data.flags = 64
|
|
1079
|
+
-
|
|
1080
|
+
- await this._client.rest.post(`/webhooks/${this._client.user?.id}/${this.interactionToken}`, data)
|
|
1081
|
+
- }
|
|
1082
|
+
-}
|
|
1083
|
+
-
|
|
1084
|
+
-export class ModalContext {
|
|
1085
|
+
+export class ModalContext extends BaseInteractionContext {
|
|
1086
|
+
|
|
1087
|
+
public customId: string
|
|
1088
|
+
- public user: User
|
|
1089
|
+
- public guild?: Guild | { id: string } | undefined
|
|
1090
|
+
- public channel?: Channel | { id: string } | undefined
|
|
1091
|
+
- public interactionId: string
|
|
1092
|
+
- public interactionToken: string
|
|
1093
|
+
-
|
|
1094
|
+
private _fields: Map<string, string> = new Map()
|
|
1095
|
+
- private _client: Client
|
|
1096
|
+
- private _replied = false
|
|
1097
|
+
- private _deferred = false
|
|
1098
|
+
|
|
1099
|
+
constructor(
|
|
1100
|
+
client: Client,
|
|
1101
|
+
- raw: Record<string, any>,
|
|
1102
|
+
+ raw: Record<string, unknown>,
|
|
1103
|
+
user: User,
|
|
1104
|
+
guild?: Guild | { id: string },
|
|
1105
|
+
channel?: Channel | { id: string }
|
|
1106
|
+
) {
|
|
1107
|
+
- this._client = client
|
|
1108
|
+
- this.interactionId = raw.id
|
|
1109
|
+
- this.interactionToken = raw.token
|
|
1110
|
+
- this.customId = raw.data?.custom_id ?? ''
|
|
1111
|
+
- this.user = user
|
|
1112
|
+
- this.guild = guild
|
|
1113
|
+
- this.channel = channel
|
|
1114
|
+
+ super(client, raw, user, guild, channel)
|
|
1115
|
+
+ this.customId = (raw.data as Record<string, unknown>)?.custom_id as string ?? ''
|
|
1116
|
+
|
|
1117
|
+
// parse modal fields from action rows
|
|
1118
|
+
- const rows = raw.data?.components ?? []
|
|
1119
|
+
+ const rows = (raw.data as Record<string, unknown>)?.components as Record<string, unknown>[] ?? []
|
|
1120
|
+
for (const row of rows) {
|
|
1121
|
+
- for (const comp of row.components ?? []) {
|
|
1122
|
+
+ for (const comp of (row.components as Record<string, unknown>[]) ?? []) {
|
|
1123
|
+
if (comp.custom_id && comp.value !== undefined) {
|
|
1124
|
+
- this._fields.set(comp.custom_id, comp.value)
|
|
1125
|
+
+ this._fields.set(comp.custom_id as string, comp.value as string)
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
- get replied() { return this._replied }
|
|
1132
|
+
- get deferred() { return this._deferred }
|
|
1133
|
+
-
|
|
1134
|
+
- /** Get a text input value by its customId */
|
|
1135
|
+
- getField(customId: string): string | undefined {
|
|
1136
|
+
- return this._fields.get(customId)
|
|
1137
|
+
+ // Get a text input value by its customId
|
|
1138
|
+
+ // values from a select menu interaction
|
|
1139
|
+
+ get values(): string[] {
|
|
1140
|
+
+ return ((this as unknown) as Record<string, string[]>)._values ?? []
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
- /** Get all field values as a plain object */
|
|
1144
|
+
+ // Get all field values as a plain object
|
|
1145
|
+
get fields(): Record<string, string> {
|
|
1146
|
+
return Object.fromEntries(this._fields)
|
|
1147
|
+
}
|
|
1148
|
+
-
|
|
1149
|
+
- async reply(payload: InteractionReplyOptions): Promise<void> {
|
|
1150
|
+
-
|
|
1151
|
+
- if (this._replied || this._deferred) throw new Error('Interaction already acknowledged.')
|
|
1152
|
+
-
|
|
1153
|
+
- const data: any = typeof payload === 'string' ? { content: payload } : { ...payload }
|
|
1154
|
+
-
|
|
1155
|
+
- if (typeof payload === 'object' && payload.ephemeral) data.flags = 64
|
|
1156
|
+
-
|
|
1157
|
+
- await this._client.rest.post(`/interactions/${this.interactionId}/${this.interactionToken}/callback`, {
|
|
1158
|
+
- type: INTERACTION_CALLBACK_TYPES.CHANNEL_MESSAGE_WITH_SOURCE,
|
|
1159
|
+
- data
|
|
1160
|
+
- })
|
|
1161
|
+
- this._replied = true
|
|
1162
|
+
- }
|
|
1163
|
+
-
|
|
1164
|
+
- async defer(options?: { ephemeral?: boolean }): Promise<void> {
|
|
1165
|
+
-
|
|
1166
|
+
- if (this._replied || this._deferred) throw new Error('Interaction already acknowledged.')
|
|
1167
|
+
-
|
|
1168
|
+
- await this._client.rest.post(`/interactions/${this.interactionId}/${this.interactionToken}/callback`, {
|
|
1169
|
+
- type: INTERACTION_CALLBACK_TYPES.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
|
|
1170
|
+
- data: { flags: options?.ephemeral ? 64 : 0 }
|
|
1171
|
+
- })
|
|
1172
|
+
- this._deferred = true
|
|
1173
|
+
- }
|
|
1174
|
+
-
|
|
1175
|
+
- async followUp(payload: InteractionReplyOptions): Promise<void> {
|
|
1176
|
+
-
|
|
1177
|
+
- if (!this._deferred && !this._replied) throw new Error('Interaction not acknowledged.')
|
|
1178
|
+
- const data: any = typeof payload === 'string' ? { content: payload } : { ...payload }
|
|
1179
|
+
- if (typeof payload === 'object' && payload.ephemeral) data.flags = 64
|
|
1180
|
+
-
|
|
1181
|
+
- await this._client.rest.post(`/webhooks/${this._client.user?.id}/${this.interactionToken}`, data)
|
|
1182
|
+
- }
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
diff --git a/src/commands/manager.ts b/src/commands/manager.ts
|
|
1186
|
+
index 9025ffb..8bc0b89 100644
|
|
1187
|
+
--- a/src/commands/manager.ts
|
|
1188
|
+
+++ b/src/commands/manager.ts
|
|
1189
|
+
@@ -1,26 +1,52 @@
|
|
1190
|
+
import type { Client } from '../client/client.js'
|
|
1191
|
+
-import type { CommandDef } from './command.js'
|
|
1192
|
+
+import type { AnyCommandDef } from './command.js'
|
|
1193
|
+
import { CommandContext } from './context.js'
|
|
1194
|
+
-import { ComponentContext, ModalContext } from './interactions.js'
|
|
1195
|
+
+import { ModalContext } from './interactions.js'
|
|
1196
|
+
+import { ComponentContext } from '../components/context.js'
|
|
1197
|
+
import { resolveUser, resolveGuild, resolveChannel, resolveRole, buildUser } from '../builders/index.js'
|
|
1198
|
+
-import { COMMAND_OPTION_TYPES, INTERACTION_TYPES, COMPONENT_TYPES } from '../utils/constants.js'
|
|
1199
|
+
+import { COMMAND_OPTION_TYPES, INTERACTION_TYPES } from '../utils/constants.js'
|
|
1200
|
+
+import type { OptionType } from './options.js'
|
|
1201
|
+
|
|
1202
|
+
export interface ComponentHandler {
|
|
1203
|
+
- customId: string | RegExp
|
|
1204
|
+
- execute: (ctx: ComponentContext) => any | Promise<any>
|
|
1205
|
+
+ type?: string
|
|
1206
|
+
+ customId?: string | RegExp
|
|
1207
|
+
+ execute?: (ctx: ComponentContext) => unknown | Promise<unknown>
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
export interface ModalHandler {
|
|
1211
|
+
customId: string | RegExp
|
|
1212
|
+
- execute: (ctx: ModalContext) => any | Promise<any>
|
|
1213
|
+
+ execute: (ctx: ModalContext) => unknown | Promise<unknown>
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
import * as fs from 'fs'
|
|
1217
|
+
import * as path from 'path'
|
|
1218
|
+
|
|
1219
|
+
+/** Shape of the `data` field inside a raw Discord interaction payload */
|
|
1220
|
+
+interface InteractionData {
|
|
1221
|
+
+ name?: string
|
|
1222
|
+
+ custom_id?: string
|
|
1223
|
+
+ component_type?: number
|
|
1224
|
+
+ options?: InteractionOption[]
|
|
1225
|
+
+ resolved?: {
|
|
1226
|
+
+ users?: Record<string, Record<string, unknown>>
|
|
1227
|
+
+ channels?: Record<string, Record<string, unknown>>
|
|
1228
|
+
+ roles?: Record<string, Record<string, unknown>>
|
|
1229
|
+
+ members?: Record<string, Record<string, unknown>>
|
|
1230
|
+
+ }
|
|
1231
|
+
+ values?: unknown[]
|
|
1232
|
+
+ components?: unknown[]
|
|
1233
|
+
+}
|
|
1234
|
+
+
|
|
1235
|
+
+interface InteractionOption {
|
|
1236
|
+
+ name: string
|
|
1237
|
+
+ type: number
|
|
1238
|
+
+ value?: unknown
|
|
1239
|
+
+ options?: InteractionOption[]
|
|
1240
|
+
+}
|
|
1241
|
+
+
|
|
1242
|
+
export class CommandManager {
|
|
1243
|
+
|
|
1244
|
+
- private _commands = new Map<string, CommandDef<any, any>>()
|
|
1245
|
+
+ private _commands = new Map<string, AnyCommandDef>()
|
|
1246
|
+
private _components: ComponentHandler[] = []
|
|
1247
|
+
private _modals: ModalHandler[] = []
|
|
1248
|
+
private _client: Client
|
|
1249
|
+
@@ -29,7 +55,7 @@ export class CommandManager {
|
|
1250
|
+
this._client = client
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
- register(...commands: CommandDef<any, any>[]) {
|
|
1254
|
+
+ register(...commands: AnyCommandDef[]) {
|
|
1255
|
+
for (const cmd of commands) {
|
|
1256
|
+
this._commands.set(cmd.name, cmd)
|
|
1257
|
+
}
|
|
1258
|
+
@@ -55,7 +81,7 @@ export class CommandManager {
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
const files = fs.readdirSync(fullPath).filter(f => f.endsWith('.js') || f.endsWith('.ts'))
|
|
1262
|
+
- const commands: CommandDef<any, any>[] = []
|
|
1263
|
+
+ const commands: AnyCommandDef[] = []
|
|
1264
|
+
|
|
1265
|
+
for (const file of files) {
|
|
1266
|
+
|
|
1267
|
+
@@ -79,7 +105,7 @@ export class CommandManager {
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
- private async _deployCommands(commands: CommandDef<any, any>[]) {
|
|
1272
|
+
+ private async _deployCommands(commands: AnyCommandDef[]) {
|
|
1273
|
+
|
|
1274
|
+
const payload = commands.map(c => this._transformCommand(c))
|
|
1275
|
+
|
|
1276
|
+
@@ -93,7 +119,7 @@ export class CommandManager {
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
- private _transformCommand(cmd: CommandDef<any, any>) {
|
|
1281
|
+
+ private _transformCommand(cmd: AnyCommandDef) {
|
|
1282
|
+
|
|
1283
|
+
const mapType = (t: string) => {
|
|
1284
|
+
switch(t) {
|
|
1285
|
+
@@ -108,25 +134,25 @@ export class CommandManager {
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
- const options: any[] = []
|
|
1290
|
+
+ const options: unknown[] = []
|
|
1291
|
+
|
|
1292
|
+
if (cmd.subcommands) {
|
|
1293
|
+
|
|
1294
|
+
- for (const [subName, subDefRaw] of Object.entries(cmd.subcommands)) {
|
|
1295
|
+
+ for (const [subName, subDef] of Object.entries(cmd.subcommands)) {
|
|
1296
|
+
|
|
1297
|
+
- const subDef = subDefRaw as any
|
|
1298
|
+
- const subOpts: any[] = []
|
|
1299
|
+
+ const subOpts: unknown[] = []
|
|
1300
|
+
|
|
1301
|
+
if (subDef.options) {
|
|
1302
|
+
- for (const [optName, optDef] of Object.entries(subDef.options)) {
|
|
1303
|
+
+ for (const [optName, optDefRaw] of Object.entries(subDef.options)) {
|
|
1304
|
+
+ const optDef = optDefRaw as import('./options.js').OptionDef<OptionType, boolean>
|
|
1305
|
+
subOpts.push({
|
|
1306
|
+
- type: mapType((optDef as any).type),
|
|
1307
|
+
+ type: mapType(optDef.type),
|
|
1308
|
+
name: optName,
|
|
1309
|
+
- description: (optDef as any).description,
|
|
1310
|
+
- required: (optDef as any).required,
|
|
1311
|
+
- choices: (optDef as any).choices,
|
|
1312
|
+
- min_value: (optDef as any).min,
|
|
1313
|
+
- max_value: (optDef as any).max
|
|
1314
|
+
+ description: optDef.description,
|
|
1315
|
+
+ required: optDef.required,
|
|
1316
|
+
+ choices: optDef.choices,
|
|
1317
|
+
+ min_value: optDef.min,
|
|
1318
|
+
+ max_value: optDef.max
|
|
1319
|
+
})
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
@@ -138,15 +164,16 @@ export class CommandManager {
|
|
1323
|
+
})
|
|
1324
|
+
}
|
|
1325
|
+
} else if (cmd.options) {
|
|
1326
|
+
- for (const [optName, optDef] of Object.entries(cmd.options)) {
|
|
1327
|
+
+ for (const [optName, optDefRaw] of Object.entries(cmd.options)) {
|
|
1328
|
+
+ const optDef = optDefRaw as import('./options.js').OptionDef<OptionType, boolean>
|
|
1329
|
+
options.push({
|
|
1330
|
+
- type: mapType((optDef as any).type),
|
|
1331
|
+
+ type: mapType(optDef.type),
|
|
1332
|
+
name: optName,
|
|
1333
|
+
- description: (optDef as any).description,
|
|
1334
|
+
- required: (optDef as any).required,
|
|
1335
|
+
- choices: (optDef as any).choices,
|
|
1336
|
+
- min_value: (optDef as any).min,
|
|
1337
|
+
- max_value: (optDef as any).max
|
|
1338
|
+
+ description: optDef.description,
|
|
1339
|
+
+ required: optDef.required,
|
|
1340
|
+
+ choices: optDef.choices,
|
|
1341
|
+
+ min_value: optDef.min,
|
|
1342
|
+
+ max_value: optDef.max
|
|
1343
|
+
})
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
@@ -158,26 +185,31 @@ export class CommandManager {
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
- public async handleInteraction(raw: any) {
|
|
1351
|
+
+ public async handleInteraction(raw: Record<string, unknown>) {
|
|
1352
|
+
|
|
1353
|
+
- if (raw.type === INTERACTION_TYPES.MESSAGE_COMPONENT) return this._handleComponentInteraction(raw)
|
|
1354
|
+
- if (raw.type === INTERACTION_TYPES.MODAL_SUBMIT) return this._handleModalInteraction(raw)
|
|
1355
|
+
+ const data = raw.data as InteractionData | undefined
|
|
1356
|
+
+ if (!data) return
|
|
1357
|
+
+
|
|
1358
|
+
+ if (raw.type === INTERACTION_TYPES.MESSAGE_COMPONENT) return this._handleComponentInteraction(raw, data)
|
|
1359
|
+
+ if (raw.type === INTERACTION_TYPES.MODAL_SUBMIT) return this._handleModalInteraction(raw, data)
|
|
1360
|
+
if (raw.type !== INTERACTION_TYPES.APPLICATION_COMMAND) return
|
|
1361
|
+
|
|
1362
|
+
- const name = raw.data.name
|
|
1363
|
+
+ const name = data.name
|
|
1364
|
+
+ if (!name) return
|
|
1365
|
+
+
|
|
1366
|
+
const command = this._commands.get(name)
|
|
1367
|
+
if (!command) return
|
|
1368
|
+
|
|
1369
|
+
- const parsedOptions: Record<string, any> = {}
|
|
1370
|
+
+ const parsedOptions: Record<string, unknown> = {}
|
|
1371
|
+
let targetExecute = command.execute
|
|
1372
|
+
|
|
1373
|
+
- const rawOptions = raw.data.options || []
|
|
1374
|
+
+ const rawOptions = data.options || []
|
|
1375
|
+
|
|
1376
|
+
- let actualOptions = rawOptions
|
|
1377
|
+
+ let actualOptions: InteractionOption[] = rawOptions
|
|
1378
|
+
|
|
1379
|
+
- if (rawOptions.length > 0 && rawOptions[0].type === COMMAND_OPTION_TYPES.SUB_COMMAND) { // SUB_COMMAND
|
|
1380
|
+
- const subcommandName = rawOptions[0].name
|
|
1381
|
+
- actualOptions = rawOptions[0].options || []
|
|
1382
|
+
+ if (rawOptions.length > 0 && rawOptions[0]!.type === COMMAND_OPTION_TYPES.SUB_COMMAND) {
|
|
1383
|
+
+ const subcommandName = rawOptions[0]!.name
|
|
1384
|
+
+ actualOptions = rawOptions[0]!.options || []
|
|
1385
|
+
|
|
1386
|
+
if (command.subcommands && command.subcommands[subcommandName]) {
|
|
1387
|
+
targetExecute = command.subcommands[subcommandName].execute
|
|
1388
|
+
@@ -185,91 +217,90 @@ export class CommandManager {
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
for (const opt of actualOptions) {
|
|
1392
|
+
- if (opt.type === COMMAND_OPTION_TYPES.USER && raw.data.resolved?.users?.[opt.value]) { // User
|
|
1393
|
+
- parsedOptions[opt.name] = resolveUser(opt.value, this._client.cache)
|
|
1394
|
+
- } else if (opt.type === COMMAND_OPTION_TYPES.CHANNEL && raw.data.resolved?.channels?.[opt.value]) { // Channel
|
|
1395
|
+
- parsedOptions[opt.name] = resolveChannel(opt.value, this._client.cache)
|
|
1396
|
+
- } else if (opt.type === COMMAND_OPTION_TYPES.ROLE && raw.data.resolved?.roles?.[opt.value]) { // Role
|
|
1397
|
+
- parsedOptions[opt.name] = resolveRole(opt.value, this._client.cache)
|
|
1398
|
+
+ if (opt.type === COMMAND_OPTION_TYPES.USER && data.resolved?.users?.[opt.value as string]) {
|
|
1399
|
+
+ parsedOptions[opt.name] = resolveUser(opt.value as string, this._client)
|
|
1400
|
+
+ } else if (opt.type === COMMAND_OPTION_TYPES.CHANNEL && data.resolved?.channels?.[opt.value as string]) {
|
|
1401
|
+
+ parsedOptions[opt.name] = resolveChannel(opt.value as string, this._client)
|
|
1402
|
+
+ } else if (opt.type === COMMAND_OPTION_TYPES.ROLE && data.resolved?.roles?.[opt.value as string]) {
|
|
1403
|
+
+ parsedOptions[opt.name] = resolveRole(opt.value as string, this._client, raw.guild_id as string | undefined)
|
|
1404
|
+
} else {
|
|
1405
|
+
parsedOptions[opt.name] = opt.value
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
- const member = raw.member || {}
|
|
1410
|
+
- const userRaw = member.user || raw.user
|
|
1411
|
+
+ const member = raw.member as Record<string, unknown> | undefined
|
|
1412
|
+
+ const userRaw = member?.user ?? raw.user
|
|
1413
|
+
|
|
1414
|
+
- const user = buildUser(userRaw)
|
|
1415
|
+
+ const user = buildUser(userRaw as Record<string, unknown>)
|
|
1416
|
+
|
|
1417
|
+
const ctx = new CommandContext(
|
|
1418
|
+
this._client,
|
|
1419
|
+
raw,
|
|
1420
|
+
parsedOptions,
|
|
1421
|
+
user,
|
|
1422
|
+
- raw.guild_id ? resolveGuild(raw.guild_id, this._client.cache) : undefined,
|
|
1423
|
+
- raw.channel_id ? resolveChannel(raw.channel_id, this._client.cache) : undefined
|
|
1424
|
+
+ raw.guild_id ? resolveGuild(raw.guild_id as string, this._client) : undefined,
|
|
1425
|
+
+ raw.channel_id ? resolveChannel(raw.channel_id as string, this._client) : undefined
|
|
1426
|
+
)
|
|
1427
|
+
|
|
1428
|
+
if (targetExecute) {
|
|
1429
|
+
try {
|
|
1430
|
+
- await targetExecute(ctx)
|
|
1431
|
+
+ await targetExecute(ctx as unknown)
|
|
1432
|
+
} catch (err) {
|
|
1433
|
+
console.error(`[Chameleon] Error executing command ${name}:`, err)
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
- private async _handleComponentInteraction(raw: any) {
|
|
1439
|
+
+ private async _handleComponentInteraction(raw: Record<string, unknown>, data: InteractionData) {
|
|
1440
|
+
|
|
1441
|
+
- const customId = raw.data.custom_id
|
|
1442
|
+
- const handler = this._components.find(h =>
|
|
1443
|
+
- typeof h.customId === 'string' ? h.customId === customId : h.customId.test(customId)
|
|
1444
|
+
- )
|
|
1445
|
+
+ const customId = data.custom_id
|
|
1446
|
+
+ const handler = this._components.find(h => {
|
|
1447
|
+
+ if (!h.customId) return false
|
|
1448
|
+
+ return typeof h.customId === 'string' ? h.customId === customId : h.customId.test(customId as string)
|
|
1449
|
+
+ })
|
|
1450
|
+
|
|
1451
|
+
if (!handler) return
|
|
1452
|
+
|
|
1453
|
+
- const userRaw = raw.member?.user || raw.user
|
|
1454
|
+
- const user = buildUser(userRaw)
|
|
1455
|
+
+ const member = raw.member as Record<string, unknown> | undefined
|
|
1456
|
+
+ const userRaw = member?.user ?? raw.user
|
|
1457
|
+
+ const user = buildUser(userRaw as Record<string, unknown>)
|
|
1458
|
+
const ctx = new ComponentContext(
|
|
1459
|
+
this._client,
|
|
1460
|
+
raw,
|
|
1461
|
+
user,
|
|
1462
|
+
- raw.guild_id ? resolveGuild(raw.guild_id, this._client.cache) : undefined,
|
|
1463
|
+
- raw.channel_id ? resolveChannel(raw.channel_id, this._client.cache) : undefined
|
|
1464
|
+
+ raw.guild_id ? resolveGuild(raw.guild_id as string, this._client) : undefined,
|
|
1465
|
+
+ raw.channel_id ? resolveChannel(raw.channel_id as string, this._client) : undefined
|
|
1466
|
+
)
|
|
1467
|
+
|
|
1468
|
+
- if (raw.data.component_type === COMPONENT_TYPES.STRING_SELECT || raw.data.component_type === COMPONENT_TYPES.USER_SELECT || raw.data.component_type === COMPONENT_TYPES.ROLE_SELECT || raw.data.component_type === COMPONENT_TYPES.MENTIONABLE_SELECT || raw.data.component_type === COMPONENT_TYPES.CHANNEL_SELECT) {
|
|
1469
|
+
- (ctx as any)._values = raw.data.values || []
|
|
1470
|
+
- }
|
|
1471
|
+
-
|
|
1472
|
+
try {
|
|
1473
|
+
- await handler.execute(ctx)
|
|
1474
|
+
+ await handler.execute?.(ctx)
|
|
1475
|
+
} catch (err) {
|
|
1476
|
+
console.error(`[Chameleon] Error executing component ${customId}:`, err)
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
- private async _handleModalInteraction(raw: any) {
|
|
1481
|
+
+ private async _handleModalInteraction(raw: Record<string, unknown>, data: InteractionData) {
|
|
1482
|
+
|
|
1483
|
+
- const customId = raw.data.custom_id
|
|
1484
|
+
+ const customId = data.custom_id
|
|
1485
|
+
const handler = this._modals.find(h =>
|
|
1486
|
+
- typeof h.customId === 'string' ? h.customId === customId : h.customId.test(customId)
|
|
1487
|
+
+ typeof h.customId === 'string' ? h.customId === customId : h.customId.test(customId as string)
|
|
1488
|
+
)
|
|
1489
|
+
|
|
1490
|
+
if (!handler) return
|
|
1491
|
+
|
|
1492
|
+
- const userRaw = raw.member?.user || raw.user
|
|
1493
|
+
- const user = buildUser(userRaw)
|
|
1494
|
+
+ const member = raw.member as Record<string, unknown> | undefined
|
|
1495
|
+
+ const userRaw = member?.user ?? raw.user
|
|
1496
|
+
+ const user = buildUser(userRaw as Record<string, unknown>)
|
|
1497
|
+
const ctx = new ModalContext(
|
|
1498
|
+
this._client,
|
|
1499
|
+
raw,
|
|
1500
|
+
user,
|
|
1501
|
+
- raw.guild_id ? resolveGuild(raw.guild_id, this._client.cache) : undefined,
|
|
1502
|
+
- raw.channel_id ? resolveChannel(raw.channel_id, this._client.cache) : undefined
|
|
1503
|
+
+ raw.guild_id ? resolveGuild(raw.guild_id as string, this._client) : undefined,
|
|
1504
|
+
+ raw.channel_id ? resolveChannel(raw.channel_id as string, this._client) : undefined
|
|
1505
|
+
)
|
|
1506
|
+
|
|
1507
|
+
try {
|
|
1508
|
+
- await handler.execute(ctx)
|
|
1509
|
+
+ await handler.execute?.(ctx)
|
|
1510
|
+
} catch (err) {
|
|
1511
|
+
console.error(`[Chameleon] Error executing modal ${customId}:`, err)
|
|
1512
|
+
}
|
|
1513
|
+
diff --git a/src/commands/options.ts b/src/commands/options.ts
|
|
1514
|
+
index 20e0c84..1e7442b 100644
|
|
1515
|
+
--- a/src/commands/options.ts
|
|
1516
|
+
+++ b/src/commands/options.ts
|
|
1517
|
+
@@ -22,10 +22,10 @@ export type ResolveOptionType<T extends OptionType> =
|
|
1518
|
+
T extends 'role' ? Role :
|
|
1519
|
+
never
|
|
1520
|
+
|
|
1521
|
+
-export type ResolveOption<O extends OptionDef<any, boolean>> =
|
|
1522
|
+
+export type ResolveOption<O extends OptionDef<OptionType, boolean>> =
|
|
1523
|
+
O['required'] extends true ? ResolveOptionType<O['type']> : ResolveOptionType<O['type']> | undefined
|
|
1524
|
+
|
|
1525
|
+
-export type ResolveOptions<O extends Record<string, OptionDef<any, boolean>>> = {
|
|
1526
|
+
+export type ResolveOptions<O extends Record<string, OptionDef<OptionType, boolean>>> = {
|
|
1527
|
+
[K in keyof O]: ResolveOption<O[K]>
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
diff --git a/src/components/actionRow.ts b/src/components/actionRow.ts
|
|
1531
|
+
index 2c56b1d..fcbfa05 100644
|
|
1532
|
+
--- a/src/components/actionRow.ts
|
|
1533
|
+
+++ b/src/components/actionRow.ts
|
|
1534
|
+
@@ -1,71 +1,90 @@
|
|
1535
|
+
import { resolveButtonStyle } from './define.js'
|
|
1536
|
+
+import type { ButtonDef, StringSelectDef, UserSelectDef, RoleSelectDef, MentionableSelectDef, ChannelSelectDef, ModalFieldDef } from './define.js'
|
|
1537
|
+
import { COMPONENT_TYPES, TEXT_INPUT_STYLES } from '../utils/constants.js'
|
|
1538
|
+
|
|
1539
|
+
+type ComponentInput =
|
|
1540
|
+
+ | (ButtonDef & { type: 'button' })
|
|
1541
|
+
+ | (StringSelectDef & { type: 'string_select' })
|
|
1542
|
+
+ | (UserSelectDef & { type: 'user_select' })
|
|
1543
|
+
+ | (RoleSelectDef & { type: 'role_select' })
|
|
1544
|
+
+ | (MentionableSelectDef & { type: 'mentionable_select' })
|
|
1545
|
+
+ | (ChannelSelectDef & { type: 'channel_select' })
|
|
1546
|
+
+ | (ModalFieldDef & { id: string })
|
|
1547
|
+
+ | Record<string, unknown>
|
|
1548
|
+
+
|
|
1549
|
+
export const ActionRow = {
|
|
1550
|
+
- of: (...components: any[]) => {
|
|
1551
|
+
+
|
|
1552
|
+
+ of: (...components: ComponentInput[]) => {
|
|
1553
|
+
|
|
1554
|
+
return {
|
|
1555
|
+
type: COMPONENT_TYPES.ACTION_ROW,
|
|
1556
|
+
components: components.map(c => {
|
|
1557
|
+
- if (c.type === 'button') {
|
|
1558
|
+
+ if ('type' in c && c.type === 'button') {
|
|
1559
|
+
+ const btn = c as ButtonDef & { type: 'button' }
|
|
1560
|
+
return {
|
|
1561
|
+
type: COMPONENT_TYPES.BUTTON,
|
|
1562
|
+
- custom_id: c.customId,
|
|
1563
|
+
- url: c.url,
|
|
1564
|
+
- label: c.label,
|
|
1565
|
+
- style: resolveButtonStyle(c.style),
|
|
1566
|
+
- disabled: c.disabled,
|
|
1567
|
+
- emoji: c.emoji,
|
|
1568
|
+
- sku_id: c.skuId
|
|
1569
|
+
+ custom_id: btn.customId,
|
|
1570
|
+
+ url: btn.url,
|
|
1571
|
+
+ label: btn.label,
|
|
1572
|
+
+ style: resolveButtonStyle(btn.style),
|
|
1573
|
+
+ disabled: btn.disabled,
|
|
1574
|
+
+ emoji: btn.emoji,
|
|
1575
|
+
+ sku_id: btn.skuId
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
- if (c.type === 'string_select') {
|
|
1580
|
+
+ if ('type' in c && c.type === 'string_select') {
|
|
1581
|
+
+ const sel = c as StringSelectDef & { type: 'string_select' }
|
|
1582
|
+
return {
|
|
1583
|
+
type: COMPONENT_TYPES.STRING_SELECT,
|
|
1584
|
+
- custom_id: c.customId,
|
|
1585
|
+
- options: c.options,
|
|
1586
|
+
- placeholder: c.placeholder,
|
|
1587
|
+
- min_values: c.minValues,
|
|
1588
|
+
- max_values: c.maxValues,
|
|
1589
|
+
- disabled: c.disabled
|
|
1590
|
+
+ custom_id: sel.customId,
|
|
1591
|
+
+ options: sel.options,
|
|
1592
|
+
+ placeholder: sel.placeholder,
|
|
1593
|
+
+ min_values: sel.minValues,
|
|
1594
|
+
+ max_values: sel.maxValues,
|
|
1595
|
+
+ disabled: sel.disabled
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
- if (c.type === 'user_select') {
|
|
1600
|
+
- return { type: COMPONENT_TYPES.USER_SELECT, custom_id: c.customId, placeholder: c.placeholder, min_values: c.minValues, max_values: c.maxValues, disabled: c.disabled }
|
|
1601
|
+
+ if ('type' in c && c.type === 'user_select') {
|
|
1602
|
+
+ const sel = c as UserSelectDef & { type: 'user_select' }
|
|
1603
|
+
+ return { type: COMPONENT_TYPES.USER_SELECT, custom_id: sel.customId, placeholder: sel.placeholder, min_values: sel.minValues, max_values: sel.maxValues, disabled: sel.disabled }
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
- if (c.type === 'role_select') {
|
|
1607
|
+
- return { type: COMPONENT_TYPES.ROLE_SELECT, custom_id: c.customId, placeholder: c.placeholder, min_values: c.minValues, max_values: c.maxValues, disabled: c.disabled }
|
|
1608
|
+
+ if ('type' in c && c.type === 'role_select') {
|
|
1609
|
+
+ const sel = c as RoleSelectDef & { type: 'role_select' }
|
|
1610
|
+
+ return { type: COMPONENT_TYPES.ROLE_SELECT, custom_id: sel.customId, placeholder: sel.placeholder, min_values: sel.minValues, max_values: sel.maxValues, disabled: sel.disabled }
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
- if (c.type === 'mentionable_select') {
|
|
1614
|
+
- return { type: COMPONENT_TYPES.MENTIONABLE_SELECT, custom_id: c.customId, placeholder: c.placeholder, min_values: c.minValues, max_values: c.maxValues, disabled: c.disabled }
|
|
1615
|
+
+ if ('type' in c && c.type === 'mentionable_select') {
|
|
1616
|
+
+ const sel = c as MentionableSelectDef & { type: 'mentionable_select' }
|
|
1617
|
+
+ return { type: COMPONENT_TYPES.MENTIONABLE_SELECT, custom_id: sel.customId, placeholder: sel.placeholder, min_values: sel.minValues, max_values: sel.maxValues, disabled: sel.disabled }
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
- if (c.type === 'channel_select') {
|
|
1621
|
+
- return { type: COMPONENT_TYPES.CHANNEL_SELECT, custom_id: c.customId, channel_types: c.channelTypes, placeholder: c.placeholder, min_values: c.minValues, max_values: c.maxValues, disabled: c.disabled }
|
|
1622
|
+
+ if ('type' in c && c.type === 'channel_select') {
|
|
1623
|
+
+ const sel = c as ChannelSelectDef & { type: 'channel_select' }
|
|
1624
|
+
+ return { type: COMPONENT_TYPES.CHANNEL_SELECT, custom_id: sel.customId, channel_types: sel.channelTypes, placeholder: sel.placeholder, min_values: sel.minValues, max_values: sel.maxValues, disabled: sel.disabled }
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
- if (c.type === TEXT_INPUT_STYLES.SHORT || c.type === TEXT_INPUT_STYLES.PARAGRAPH) {
|
|
1628
|
+
+ if ('type' in c && (c.type === TEXT_INPUT_STYLES.SHORT || c.type === TEXT_INPUT_STYLES.PARAGRAPH)) {
|
|
1629
|
+
+ const field = c as ModalFieldDef & { id: string }
|
|
1630
|
+
return {
|
|
1631
|
+
type: COMPONENT_TYPES.TEXT_INPUT,
|
|
1632
|
+
- custom_id: c.id,
|
|
1633
|
+
- style: c.type,
|
|
1634
|
+
- label: c.label,
|
|
1635
|
+
- required: c.required,
|
|
1636
|
+
- min_length: c.minLength,
|
|
1637
|
+
- max_length: c.maxLength,
|
|
1638
|
+
- placeholder: c.placeholder,
|
|
1639
|
+
- value: c.value
|
|
1640
|
+
+ custom_id: field.id,
|
|
1641
|
+
+ style: field.type,
|
|
1642
|
+
+ label: field.label,
|
|
1643
|
+
+ required: field.required,
|
|
1644
|
+
+ min_length: field.minLength,
|
|
1645
|
+
+ max_length: field.maxLength,
|
|
1646
|
+
+ placeholder: field.placeholder,
|
|
1647
|
+
+ value: field.value
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
return c // fallback for raw discord API objects
|
|
1652
|
+
}).map(obj => {
|
|
1653
|
+
|
|
1654
|
+
- const clean: any = {} // remove undefined keys for cleaner JSON
|
|
1655
|
+
+ const clean: Record<string, unknown> = {} // remove undefined keys for cleaner JSON
|
|
1656
|
+
|
|
1657
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
1658
|
+
if (v !== undefined) clean[k] = v
|
|
1659
|
+
diff --git a/src/components/context.ts b/src/components/context.ts
|
|
1660
|
+
index 79b239c..bff958f 100644
|
|
1661
|
+
--- a/src/components/context.ts
|
|
1662
|
+
+++ b/src/components/context.ts
|
|
1663
|
+
@@ -2,46 +2,48 @@ import type { Client } from '../client/client.js'
|
|
1664
|
+
import type { User } from '../types/user/index.js'
|
|
1665
|
+
import type { Guild } from '../types/guild/index.js'
|
|
1666
|
+
import type { Channel } from '../types/channel/index.js'
|
|
1667
|
+
-import type { InteractionReplyOptions } from '../commands/context.js'
|
|
1668
|
+
-import { INTERACTION_CALLBACK_TYPES, COMPONENT_TYPES } from '../utils/constants.js'
|
|
1669
|
+
-
|
|
1670
|
+
-export class ComponentContext<Values = any, Fields = any> {
|
|
1671
|
+
+import { BaseInteractionContext, type InteractionReplyOptions } from '../commands/context.js'
|
|
1672
|
+
+import { INTERACTION_CALLBACK_TYPES } from '../utils/constants.js'
|
|
1673
|
+
+
|
|
1674
|
+
+interface InteractionData {
|
|
1675
|
+
+ custom_id?: string
|
|
1676
|
+
+ values?: unknown[]
|
|
1677
|
+
+ components?: { components: { custom_id: string, value: unknown }[] }[]
|
|
1678
|
+
+ component_type?: number
|
|
1679
|
+
+ resolved?: Record<string, unknown>
|
|
1680
|
+
+}
|
|
1681
|
+
|
|
1682
|
+
- public user: User
|
|
1683
|
+
- public guild?: Guild | { id: string } | undefined
|
|
1684
|
+
- public channel?: Channel | { id: string } | undefined
|
|
1685
|
+
- public interactionId: string
|
|
1686
|
+
- public interactionToken: string
|
|
1687
|
+
+export class ComponentContext<Values = unknown, Fields = unknown> extends BaseInteractionContext {
|
|
1688
|
+
+
|
|
1689
|
+
public customId: string
|
|
1690
|
+
+ public message?: Record<string, unknown>
|
|
1691
|
+
public values: Values
|
|
1692
|
+
public fields: Fields
|
|
1693
|
+
|
|
1694
|
+
- private _client: Client
|
|
1695
|
+
- private _deferred = false
|
|
1696
|
+
- private _replied = false
|
|
1697
|
+
-
|
|
1698
|
+
- constructor (
|
|
1699
|
+
+ constructor(
|
|
1700
|
+
client: Client,
|
|
1701
|
+
- rawInteraction: Record<string, any>,
|
|
1702
|
+
+ raw: Record<string, unknown>,
|
|
1703
|
+
user: User,
|
|
1704
|
+
guild?: Guild | { id: string },
|
|
1705
|
+
channel?: Channel | { id: string }
|
|
1706
|
+
) {
|
|
1707
|
+
- this._client = client
|
|
1708
|
+
- this.interactionId = rawInteraction.id
|
|
1709
|
+
- this.interactionToken = rawInteraction.token
|
|
1710
|
+
- this.customId = rawInteraction.data?.custom_id as string
|
|
1711
|
+
- this.user = user
|
|
1712
|
+
- this.guild = guild
|
|
1713
|
+
- this.channel = channel
|
|
1714
|
+
-
|
|
1715
|
+
- this.values = (rawInteraction.data?.values as Values) ?? ([] as Values)
|
|
1716
|
+
-
|
|
1717
|
+
- const fields: Record<string, any> = {}
|
|
1718
|
+
+ super(client, raw, user, guild, channel)
|
|
1719
|
+
+
|
|
1720
|
+
+ const data = raw.data as InteractionData | undefined
|
|
1721
|
+
+
|
|
1722
|
+
+ this.customId = data?.custom_id ?? ''
|
|
1723
|
+
|
|
1724
|
+
- if (rawInteraction.data?.components) {
|
|
1725
|
+
- for (const row of rawInteraction.data.components as any[]) {
|
|
1726
|
+
- for (const comp of row.components as any[]) {
|
|
1727
|
+
+ if (raw.message) {
|
|
1728
|
+
+ this.message = raw.message as Record<string, unknown>
|
|
1729
|
+
+ }
|
|
1730
|
+
+
|
|
1731
|
+
+ this.values = (data?.values as Values) ?? ([] as Values)
|
|
1732
|
+
+
|
|
1733
|
+
+ const fields: Record<string, unknown> = {}
|
|
1734
|
+
+
|
|
1735
|
+
+ if (data?.components) {
|
|
1736
|
+
+ for (const row of data.components) {
|
|
1737
|
+
+ for (const comp of row.components) {
|
|
1738
|
+
fields[comp.custom_id] = comp.value
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
@@ -49,41 +51,19 @@ export class ComponentContext<Values = any, Fields = any> {
|
|
1742
|
+
this.fields = fields as Fields
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
- get replied() { return this._replied }
|
|
1746
|
+
- get deferred() { return this._deferred }
|
|
1747
|
+
-
|
|
1748
|
+
- async reply(payload: InteractionReplyOptions): Promise<void> {
|
|
1749
|
+
-
|
|
1750
|
+
- if (this._replied || this._deferred) throw new Error('Interaction already acknowledged.')
|
|
1751
|
+
-
|
|
1752
|
+
- const data: any = typeof payload === 'string' ? { content: payload } : { ...payload }
|
|
1753
|
+
- if (typeof payload === 'object' && payload.ephemeral) data.flags = 64
|
|
1754
|
+
-
|
|
1755
|
+
- await this._client.rest.post(`/interactions/${this.interactionId}/${this.interactionToken}/callback`, {
|
|
1756
|
+
- type: INTERACTION_CALLBACK_TYPES.CHANNEL_MESSAGE_WITH_SOURCE,
|
|
1757
|
+
- data
|
|
1758
|
+
- })
|
|
1759
|
+
- this._replied = true
|
|
1760
|
+
- }
|
|
1761
|
+
-
|
|
1762
|
+
- async defer(options?: { ephemeral?: boolean }): Promise<void> {
|
|
1763
|
+
-
|
|
1764
|
+
+ async deferUpdate(): Promise<void> {
|
|
1765
|
+
if (this._replied || this._deferred) throw new Error('Interaction already acknowledged.')
|
|
1766
|
+
|
|
1767
|
+
await this._client.rest.post(`/interactions/${this.interactionId}/${this.interactionToken}/callback`, {
|
|
1768
|
+
- type: INTERACTION_CALLBACK_TYPES.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
|
|
1769
|
+
- data: {
|
|
1770
|
+
- flags: options?.ephemeral ? 64 : 0
|
|
1771
|
+
- }
|
|
1772
|
+
+ type: INTERACTION_CALLBACK_TYPES.DEFERRED_UPDATE_MESSAGE
|
|
1773
|
+
})
|
|
1774
|
+
this._deferred = true
|
|
1775
|
+
}
|
|
1776
|
+
-
|
|
1777
|
+
- async update(payload: InteractionReplyOptions): Promise<void> {
|
|
1778
|
+
|
|
1779
|
+
+ async update(payload: InteractionReplyOptions): Promise<void> {
|
|
1780
|
+
if (this._replied || this._deferred) throw new Error('Interaction already acknowledged.')
|
|
1781
|
+
|
|
1782
|
+
- const data: any = typeof payload === 'string' ? { content: payload } : { ...payload }
|
|
1783
|
+
+ const data = this._resolvePayload(payload)
|
|
1784
|
+
|
|
1785
|
+
await this._client.rest.post(`/interactions/${this.interactionId}/${this.interactionToken}/callback`, {
|
|
1786
|
+
type: INTERACTION_CALLBACK_TYPES.UPDATE_MESSAGE,
|
|
1787
|
+
@@ -91,57 +71,4 @@ export class ComponentContext<Values = any, Fields = any> {
|
|
1788
|
+
})
|
|
1789
|
+
this._replied = true
|
|
1790
|
+
}
|
|
1791
|
+
-
|
|
1792
|
+
- async deferUpdate(): Promise<void> {
|
|
1793
|
+
-
|
|
1794
|
+
- if (this._replied || this._deferred) throw new Error('Interaction already acknowledged.')
|
|
1795
|
+
-
|
|
1796
|
+
- await this._client.rest.post(`/interactions/${this.interactionId}/${this.interactionToken}/callback`, {
|
|
1797
|
+
- type: INTERACTION_CALLBACK_TYPES.DEFERRED_UPDATE_MESSAGE,
|
|
1798
|
+
- })
|
|
1799
|
+
- this._deferred = true
|
|
1800
|
+
- }
|
|
1801
|
+
-
|
|
1802
|
+
- async followUp(payload: InteractionReplyOptions): Promise<void> {
|
|
1803
|
+
-
|
|
1804
|
+
- if (!this._deferred && !this._replied) throw new Error('Interaction not acknowledged. Use reply or defer first.')
|
|
1805
|
+
-
|
|
1806
|
+
- const data: any = typeof payload === 'string' ? { content: payload } : { ...payload }
|
|
1807
|
+
-
|
|
1808
|
+
- if (typeof payload === 'object' && payload.ephemeral) data.flags = 64
|
|
1809
|
+
-
|
|
1810
|
+
- await this._client.rest.post(`/webhooks/${this._client.user?.id}/${this.interactionToken}`, data)
|
|
1811
|
+
- }
|
|
1812
|
+
-
|
|
1813
|
+
- async showModal(modal: any): Promise<void> {
|
|
1814
|
+
-
|
|
1815
|
+
- if (this._replied || this._deferred) throw new Error('Interaction already acknowledged.')
|
|
1816
|
+
-
|
|
1817
|
+
- await this._client.rest.post(`/interactions/${this.interactionId}/${this.interactionToken}/callback`, {
|
|
1818
|
+
- type: INTERACTION_CALLBACK_TYPES.MODAL,
|
|
1819
|
+
- data: modal.type === 'modal' ? buildModalPayload(modal) : modal
|
|
1820
|
+
- })
|
|
1821
|
+
- this._replied = true
|
|
1822
|
+
- }
|
|
1823
|
+
-}
|
|
1824
|
+
-
|
|
1825
|
+
-export function buildModalPayload(def: any) {
|
|
1826
|
+
- return {
|
|
1827
|
+
- custom_id: def.customId,
|
|
1828
|
+
- title: def.title,
|
|
1829
|
+
- components: def.fields.map((f: any) => ({
|
|
1830
|
+
- type: COMPONENT_TYPES.ACTION_ROW,
|
|
1831
|
+
- components: [{
|
|
1832
|
+
- type: COMPONENT_TYPES.TEXT_INPUT,
|
|
1833
|
+
- custom_id: f.id,
|
|
1834
|
+
- style: f.type, // 1 short, 2 paragraph
|
|
1835
|
+
- label: f.label,
|
|
1836
|
+
- required: f.required,
|
|
1837
|
+
- min_length: f.minLength,
|
|
1838
|
+
- max_length: f.maxLength,
|
|
1839
|
+
- placeholder: f.placeholder,
|
|
1840
|
+
- value: f.value
|
|
1841
|
+
- }]
|
|
1842
|
+
- }))
|
|
1843
|
+
- }
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
diff --git a/src/components/define.ts b/src/components/define.ts
|
|
1847
|
+
index d39a388..b92a948 100644
|
|
1848
|
+
--- a/src/components/define.ts
|
|
1849
|
+
+++ b/src/components/define.ts
|
|
1850
|
+
@@ -127,17 +127,17 @@ export const field = {
|
|
1851
|
+
})
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
-export type ResolveModalFields<F extends ReadonlyArray<ModalFieldDef<any>>> = {
|
|
1855
|
+
+export type ResolveModalFields<F extends ReadonlyArray<ModalFieldDef<unknown>>> = {
|
|
1856
|
+
[K in F[number] as K['id']]: K['required'] extends false ? string | undefined : string
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
-export interface ModalDef<F extends ReadonlyArray<ModalFieldDef<any>>> {
|
|
1860
|
+
+export interface ModalDef<F extends ReadonlyArray<ModalFieldDef<unknown>>> {
|
|
1861
|
+
customId: string
|
|
1862
|
+
title: string
|
|
1863
|
+
fields: F
|
|
1864
|
+
execute: (ctx: ComponentContext<never, ResolveModalFields<F>>) => void | Promise<void>
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
-export function defineModal<F extends ReadonlyArray<ModalFieldDef<any>>>(def: ModalDef<F>): ModalDef<F> & { type: 'modal' } {
|
|
1868
|
+
+export function defineModal<F extends ReadonlyArray<ModalFieldDef<unknown>>>(def: ModalDef<F>): ModalDef<F> & { type: 'modal' } {
|
|
1869
|
+
return { ...def, type: 'modal' }
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
diff --git a/src/components/manager.ts b/src/components/manager.ts
|
|
1873
|
+
index bfcdcfb..db44524 100644
|
|
1874
|
+
--- a/src/components/manager.ts
|
|
1875
|
+
+++ b/src/components/manager.ts
|
|
1876
|
+
@@ -1,53 +1,58 @@
|
|
1877
|
+
import type { Client } from '../client/client.js'
|
|
1878
|
+
import { ComponentContext } from './context.js'
|
|
1879
|
+
import { buildUser, buildChannel, resolveChannel } from '../builders/index.js'
|
|
1880
|
+
+import type { ComponentHandler } from '../commands/manager.js'
|
|
1881
|
+
|
|
1882
|
+
export class ComponentManager {
|
|
1883
|
+
|
|
1884
|
+
- private handlers = new Map<string, any>()
|
|
1885
|
+
+ private handlers = new Map<string, ComponentHandler>()
|
|
1886
|
+
|
|
1887
|
+
constructor(private client: Client) {}
|
|
1888
|
+
|
|
1889
|
+
- public register(...components: any[]): void {
|
|
1890
|
+
+ public register(...components: ComponentHandler[]): void {
|
|
1891
|
+
|
|
1892
|
+
for (const comp of components) {
|
|
1893
|
+
-
|
|
1894
|
+
- if (comp.customId) {
|
|
1895
|
+
+
|
|
1896
|
+
+ if (comp.customId && typeof comp.customId === 'string') {
|
|
1897
|
+
this.handlers.set(comp.customId, comp)
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
- public async handleInteraction(raw: Record<string, any>): Promise<void> {
|
|
1903
|
+
+ public async handleInteraction(raw: Record<string, unknown>): Promise<void> {
|
|
1904
|
+
+
|
|
1905
|
+
+ const data = raw.data as Record<string, unknown> | undefined
|
|
1906
|
+
+ if (!data) return
|
|
1907
|
+
|
|
1908
|
+
- const data = raw.data as Record<string, any>
|
|
1909
|
+
const customId = data.custom_id as string
|
|
1910
|
+
-
|
|
1911
|
+
if (!customId) return
|
|
1912
|
+
|
|
1913
|
+
const handler = this.handlers.get(customId)
|
|
1914
|
+
if (!handler || !handler.execute) return
|
|
1915
|
+
|
|
1916
|
+
- const user = buildUser(raw.user ?? raw.member?.user)
|
|
1917
|
+
-
|
|
1918
|
+
+ const userRaw = (raw.member as Record<string, unknown> | undefined)?.user ?? raw.user
|
|
1919
|
+
+ const user = buildUser(userRaw as Record<string, unknown>)
|
|
1920
|
+
+
|
|
1921
|
+
let guild
|
|
1922
|
+
if (raw.guild_id) {
|
|
1923
|
+
- guild = this.client.cache.guilds.get(raw.guild_id) ?? { id: raw.guild_id }
|
|
1924
|
+
+ guild = this.client.cache.guilds.get(raw.guild_id as string) ?? { id: raw.guild_id as string }
|
|
1925
|
+
}
|
|
1926
|
+
-
|
|
1927
|
+
+
|
|
1928
|
+
let channel
|
|
1929
|
+
if (raw.channel_id) {
|
|
1930
|
+
- channel = resolveChannel(raw.channel_id, this.client.cache) ?? { id: raw.channel_id }
|
|
1931
|
+
+ channel = resolveChannel(raw.channel_id as string, this.client) ?? { id: raw.channel_id as string }
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
// Hydrate members from resolved data for select menus
|
|
1935
|
+
- if (data.resolved?.members && data.resolved?.users && raw.guild_id) {
|
|
1936
|
+
- for (const [id, memberData] of Object.entries(data.resolved.members as Record<string, any>)) {
|
|
1937
|
+
+ const resolved = data.resolved as Record<string, Record<string, Record<string, unknown>>> | undefined
|
|
1938
|
+
+
|
|
1939
|
+
+ if (resolved?.members && resolved?.users && raw.guild_id) {
|
|
1940
|
+
+
|
|
1941
|
+
+ for (const [id] of Object.entries(resolved.members)) {
|
|
1942
|
+
+
|
|
1943
|
+
+ const userData = resolved.users[id]
|
|
1944
|
+
|
|
1945
|
+
- const userData = (data.resolved.users as Record<string, any>)[id]
|
|
1946
|
+
-
|
|
1947
|
+
if (userData) {
|
|
1948
|
+
- const mergedMember = { ...memberData, user: userData }
|
|
1949
|
+
const u = buildUser(userData)
|
|
1950
|
+
this.client.cache.users.set(u.id, u)
|
|
1951
|
+
}
|
|
1952
|
+
@@ -56,16 +61,16 @@ export class ComponentManager {
|
|
1953
|
+
|
|
1954
|
+
const ctx = new ComponentContext(this.client, raw, user, guild, channel)
|
|
1955
|
+
|
|
1956
|
+
- if (handler.type === 'user_select' && data.resolved?.users) {
|
|
1957
|
+
- ctx.values = ctx.values.map((id: string) => {
|
|
1958
|
+
- const uData = (data.resolved!.users as Record<string, any>)[id]
|
|
1959
|
+
+ if (handler.type === 'user_select' && resolved?.users) {
|
|
1960
|
+
+ ctx.values = (ctx.values as string[]).map((id: string) => {
|
|
1961
|
+
+ const uData = resolved.users![id]
|
|
1962
|
+
return uData ? buildUser(uData) : { id }
|
|
1963
|
+
- })
|
|
1964
|
+
- } else if (handler.type === 'channel_select' && data.resolved?.channels) {
|
|
1965
|
+
- ctx.values = ctx.values.map((id: string) => {
|
|
1966
|
+
- const cData = (data.resolved!.channels as Record<string, any>)[id]
|
|
1967
|
+
- return cData ? buildChannel(cData, raw.guild_id) : { id }
|
|
1968
|
+
- })
|
|
1969
|
+
+ }) as typeof ctx.values
|
|
1970
|
+
+ } else if (handler.type === 'channel_select' && resolved?.channels) {
|
|
1971
|
+
+ ctx.values = (ctx.values as string[]).map((id: string) => {
|
|
1972
|
+
+ const cData = resolved.channels![id]
|
|
1973
|
+
+ return cData ? buildChannel(cData, raw.guild_id as string | undefined) : { id }
|
|
1974
|
+
+ }) as typeof ctx.values
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
try {
|
|
1978
|
+
diff --git a/src/gateway/index.ts b/src/gateway/index.ts
|
|
1979
|
+
index f988567..d55fb1e 100644
|
|
1980
|
+
--- a/src/gateway/index.ts
|
|
1981
|
+
+++ b/src/gateway/index.ts
|
|
1982
|
+
@@ -131,7 +131,7 @@ export class ChameleonGateway {
|
|
1983
|
+
this.ws.send(JSON.stringify({ op, d }))
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
- public pendingPresence: Record<string, any> | null = null
|
|
1987
|
+
+ public pendingPresence: Record<string, unknown> | null = null
|
|
1988
|
+
|
|
1989
|
+
/**
|
|
1990
|
+
* Update the presence/status for this shard
|
|
1991
|
+
@@ -189,7 +189,7 @@ export class ChameleonGateway {
|
|
1992
|
+
throw new Error('[GATEWAY] Cannot request guild members while disconnected')
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
- const payload: Record<string, any> = {
|
|
1996
|
+
+ const payload: Record<string, unknown> = {
|
|
1997
|
+
guild_id: options.guildId,
|
|
1998
|
+
limit: options.limit,
|
|
1999
|
+
}
|
|
2000
|
+
diff --git a/src/managers/base.ts b/src/managers/base.ts
|
|
2001
|
+
index b2e52cb..843274e 100644
|
|
2002
|
+
--- a/src/managers/base.ts
|
|
2003
|
+
+++ b/src/managers/base.ts
|
|
2004
|
+
@@ -1,5 +1,6 @@
|
|
2005
|
+
import type { ChameleonREST } from '../rest/index.js'
|
|
2006
|
+
import type { TongueStore } from '../client/store.js'
|
|
2007
|
+
+import type { Tongue } from '../utils/tongue.js'
|
|
2008
|
+
import type { ChameleonAPIResult } from '../rest/types.js'
|
|
2009
|
+
|
|
2010
|
+
export abstract class BaseManager<T extends { id: string }> {
|
|
2011
|
+
@@ -17,10 +18,10 @@ export abstract class BaseManager<T extends { id: string }> {
|
|
2012
|
+
|
|
2013
|
+
if (!force) {
|
|
2014
|
+
|
|
2015
|
+
- const tongue = this.store[this.storeKey] as any
|
|
2016
|
+
+ const tongue = this.store[this.storeKey] as unknown as Tongue<string, T>
|
|
2017
|
+
const cached = tongue.get(id)
|
|
2018
|
+
|
|
2019
|
+
- if (cached) return { ok: true, data: cached as T }
|
|
2020
|
+
+ if (cached) return { ok: true, data: cached }
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
const result = await this.rest.get<unknown>(this.endpoint(id))
|
|
2024
|
+
@@ -29,7 +30,7 @@ export abstract class BaseManager<T extends { id: string }> {
|
|
2025
|
+
|
|
2026
|
+
const entity = this.build(result.data)
|
|
2027
|
+
|
|
2028
|
+
- const tongue = this.store[this.storeKey] as any
|
|
2029
|
+
+ const tongue = this.store[this.storeKey] as unknown as Tongue<string, T>
|
|
2030
|
+
tongue.set(id, entity)
|
|
2031
|
+
|
|
2032
|
+
return { ok: true, data: entity }
|
|
2033
|
+
diff --git a/src/managers/channel.ts b/src/managers/channel.ts
|
|
2034
|
+
index d175f25..6fef84f 100644
|
|
2035
|
+
--- a/src/managers/channel.ts
|
|
2036
|
+
+++ b/src/managers/channel.ts
|
|
2037
|
+
@@ -1,10 +1,77 @@
|
|
2038
|
+
import { BaseManager } from './base.js'
|
|
2039
|
+
import { buildChannel } from '../builders/index.js'
|
|
2040
|
+
-import type { Channel } from '../types/channel/index.js'
|
|
2041
|
+
+import type { Channel, Overwrite } from '../types/channel/index.js'
|
|
2042
|
+
+import type { ChameleonAPIResult } from '../rest/types.js'
|
|
2043
|
+
+import { toSnakeCase } from '../utils/object.js'
|
|
2044
|
+
|
|
2045
|
+
export class ChannelManager extends BaseManager<Channel> {
|
|
2046
|
+
|
|
2047
|
+
protected storeKey = 'channels' as const
|
|
2048
|
+
protected endpoint(id: string) { return `/channels/${id}` }
|
|
2049
|
+
protected build = buildChannel
|
|
2050
|
+
+
|
|
2051
|
+
+ async create(guildId: string, payload: Partial<Channel>, reason?: string): Promise<ChameleonAPIResult<Channel>> {
|
|
2052
|
+
+
|
|
2053
|
+
+ const headers: Record<string, string> = {}
|
|
2054
|
+
+
|
|
2055
|
+
+ if (reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(reason)
|
|
2056
|
+
+
|
|
2057
|
+
+ const result = await this.rest.post<unknown>(`/guilds/${guildId}/channels`, toSnakeCase(payload), headers)
|
|
2058
|
+
+
|
|
2059
|
+
+ if (!result.ok) return result as ChameleonAPIResult<never>
|
|
2060
|
+
+
|
|
2061
|
+
+ const entity = this.build(result.data as Record<string, unknown>, guildId)
|
|
2062
|
+
+
|
|
2063
|
+
+ this.store.channels.set(entity.id, entity)
|
|
2064
|
+
+
|
|
2065
|
+
+ return { ok: true, data: entity }
|
|
2066
|
+
+ }
|
|
2067
|
+
+
|
|
2068
|
+
+ async edit(channelId: string, payload: Partial<Channel>, reason?: string): Promise<ChameleonAPIResult<Channel>> {
|
|
2069
|
+
+
|
|
2070
|
+
+ const headers: Record<string, string> = {}
|
|
2071
|
+
+
|
|
2072
|
+
+ if (reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(reason)
|
|
2073
|
+
+
|
|
2074
|
+
+ const result = await this.rest.patch<unknown>(this.endpoint(channelId), toSnakeCase(payload), headers)
|
|
2075
|
+
+
|
|
2076
|
+
+ if (!result.ok) return result as ChameleonAPIResult<never>
|
|
2077
|
+
+
|
|
2078
|
+
+ const entity = this.build(result.data as Record<string, unknown>)
|
|
2079
|
+
+
|
|
2080
|
+
+ this.store.channels.set(entity.id, entity)
|
|
2081
|
+
+
|
|
2082
|
+
+ return { ok: true, data: entity }
|
|
2083
|
+
+ }
|
|
2084
|
+
+
|
|
2085
|
+
+ async delete(channelId: string, reason?: string): Promise<ChameleonAPIResult<void>> {
|
|
2086
|
+
+
|
|
2087
|
+
+ const headers: Record<string, string> = {}
|
|
2088
|
+
+
|
|
2089
|
+
+ if (reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(reason)
|
|
2090
|
+
+
|
|
2091
|
+
+ const result = await this.rest.delete(this.endpoint(channelId), headers)
|
|
2092
|
+
+
|
|
2093
|
+
+ if (result.ok) this.store.channels.delete(channelId)
|
|
2094
|
+
+
|
|
2095
|
+
+ return result as ChameleonAPIResult<void>
|
|
2096
|
+
+ }
|
|
2097
|
+
+
|
|
2098
|
+
+ async updatePermissions(channelId: string, overwriteId: string, payload: Partial<Overwrite>, reason?: string): Promise<ChameleonAPIResult<void>> {
|
|
2099
|
+
+
|
|
2100
|
+
+ const headers: Record<string, string> = {}
|
|
2101
|
+
+
|
|
2102
|
+
+ if (reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(reason)
|
|
2103
|
+
+
|
|
2104
|
+
+ return await this.rest.put(`/channels/${channelId}/permissions/${overwriteId}`, toSnakeCase(payload), headers) as ChameleonAPIResult<void>
|
|
2105
|
+
+ }
|
|
2106
|
+
+
|
|
2107
|
+
+ async deletePermission(channelId: string, overwriteId: string, reason?: string): Promise<ChameleonAPIResult<void>> {
|
|
2108
|
+
+
|
|
2109
|
+
+ const headers: Record<string, string> = {}
|
|
2110
|
+
+
|
|
2111
|
+
+ if (reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(reason)
|
|
2112
|
+
+
|
|
2113
|
+
+ return await this.rest.delete(`/channels/${channelId}/permissions/${overwriteId}`, headers) as ChameleonAPIResult<void>
|
|
2114
|
+
+ }
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
diff --git a/src/managers/collector.ts b/src/managers/collector.ts
|
|
2118
|
+
index 082de06..52d7d2e 100644
|
|
2119
|
+
--- a/src/managers/collector.ts
|
|
2120
|
+
+++ b/src/managers/collector.ts
|
|
2121
|
+
@@ -1,8 +1,8 @@
|
|
2122
|
+
import type { Client } from '../client/client.js'
|
|
2123
|
+
import type { Message } from '../types/message/index.js'
|
|
2124
|
+
-import { ComponentContext } from '../commands/interactions.js'
|
|
2125
|
+
+import { ComponentContext } from '../components/context.js'
|
|
2126
|
+
import { resolveGuild, resolveChannel, buildUser } from '../builders/index.js'
|
|
2127
|
+
-import { COMPONENT_TYPES, INTERACTION_TYPES } from '../utils/constants.js'
|
|
2128
|
+
+import { INTERACTION_TYPES } from '../utils/constants.js'
|
|
2129
|
+
|
|
2130
|
+
export interface AwaitMessagesOptions {
|
|
2131
|
+
filter?: (message: Message) => boolean
|
|
2132
|
+
@@ -51,10 +51,10 @@ export class CollectorManager {
|
|
2133
|
+
const cleanup = () => {
|
|
2134
|
+
if (timeoutId) clearTimeout(timeoutId)
|
|
2135
|
+
|
|
2136
|
+
- this.client.off('MESSAGE_CREATE', handler as any)
|
|
2137
|
+
+ this.client.off('MESSAGE_CREATE', handler)
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
- this.client.on('MESSAGE_CREATE', handler as any)
|
|
2141
|
+
+ this.client.on('MESSAGE_CREATE', handler)
|
|
2142
|
+
|
|
2143
|
+
if (time > 0) {
|
|
2144
|
+
timeoutId = setTimeout(() => {
|
|
2145
|
+
@@ -77,9 +77,10 @@ export class CollectorManager {
|
|
2146
|
+
|
|
2147
|
+
let timeoutId: NodeJS.Timeout | null = null
|
|
2148
|
+
|
|
2149
|
+
- const handler = (data: { type: 'INTERACTION_CREATE', interaction: any }) => {
|
|
2150
|
+
+ const handler = (data: import('../events/index.js').ChameleonEvent) => {
|
|
2151
|
+
|
|
2152
|
+
- const raw = data.interaction
|
|
2153
|
+
+ if (data.type !== 'INTERACTION_CREATE') return
|
|
2154
|
+
+ const raw = data.interaction as Record<string, unknown>
|
|
2155
|
+
|
|
2156
|
+
if (raw.type !== INTERACTION_TYPES.MESSAGE_COMPONENT) return
|
|
2157
|
+
if (raw.message?.id !== messageId) return
|
|
2158
|
+
@@ -91,15 +92,10 @@ export class CollectorManager {
|
|
2159
|
+
this.client,
|
|
2160
|
+
raw,
|
|
2161
|
+
user,
|
|
2162
|
+
- raw.guild_id ? resolveGuild(raw.guild_id, this.client.cache) : undefined,
|
|
2163
|
+
- raw.channel_id ? resolveChannel(raw.channel_id, this.client.cache) : undefined
|
|
2164
|
+
+ raw.guild_id ? resolveGuild(raw.guild_id, this.client) : undefined,
|
|
2165
|
+
+ raw.channel_id ? resolveChannel(raw.channel_id, this.client) : undefined
|
|
2166
|
+
)
|
|
2167
|
+
|
|
2168
|
+
- // hydrate values if select menu
|
|
2169
|
+
- if (raw.data.component_type === COMPONENT_TYPES.STRING_SELECT || raw.data.component_type === COMPONENT_TYPES.USER_SELECT || raw.data.component_type === COMPONENT_TYPES.ROLE_SELECT || raw.data.component_type === COMPONENT_TYPES.MENTIONABLE_SELECT || raw.data.component_type === COMPONENT_TYPES.CHANNEL_SELECT) {
|
|
2170
|
+
- (ctx as any)._values = raw.data.values || []
|
|
2171
|
+
- }
|
|
2172
|
+
-
|
|
2173
|
+
if (filter && !filter(ctx)) return
|
|
2174
|
+
|
|
2175
|
+
cleanup()
|
|
2176
|
+
@@ -110,10 +106,10 @@ export class CollectorManager {
|
|
2177
|
+
|
|
2178
|
+
if (timeoutId) clearTimeout(timeoutId)
|
|
2179
|
+
|
|
2180
|
+
- this.client.off('INTERACTION_CREATE', handler as any)
|
|
2181
|
+
+ this.client.off('INTERACTION_CREATE', handler)
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
- this.client.on('INTERACTION_CREATE', handler as any)
|
|
2185
|
+
+ this.client.on('INTERACTION_CREATE', handler)
|
|
2186
|
+
|
|
2187
|
+
if (time > 0) {
|
|
2188
|
+
timeoutId = setTimeout(() => {
|
|
2189
|
+
diff --git a/src/managers/guild.ts b/src/managers/guild.ts
|
|
2190
|
+
index 2cbf01d..8edf688 100644
|
|
2191
|
+
--- a/src/managers/guild.ts
|
|
2192
|
+
+++ b/src/managers/guild.ts
|
|
2193
|
+
@@ -1,9 +1,12 @@
|
|
2194
|
+
import { BaseManager } from './base.js'
|
|
2195
|
+
import { buildGuild, buildChannel } from '../builders/index.js'
|
|
2196
|
+
+import { RoleManager } from './role.js'
|
|
2197
|
+
+import { MemberManager } from './member.js'
|
|
2198
|
+
import { TongueStore } from '../client/store.js'
|
|
2199
|
+
import type { Guild } from '../types/guild/index.js'
|
|
2200
|
+
import type { Channel } from '../types/channel/index.js'
|
|
2201
|
+
import type { ChameleonAPIResult } from '../rest/types.js'
|
|
2202
|
+
+import { toSnakeCase } from '../utils/object.js'
|
|
2203
|
+
|
|
2204
|
+
export class GuildManager extends BaseManager<Guild> {
|
|
2205
|
+
|
|
2206
|
+
@@ -11,14 +14,22 @@ export class GuildManager extends BaseManager<Guild> {
|
|
2207
|
+
protected endpoint(id: string) { return `/guilds/${id}` }
|
|
2208
|
+
protected build = buildGuild
|
|
2209
|
+
|
|
2210
|
+
+ roles(guildId: string): RoleManager {
|
|
2211
|
+
+ return new RoleManager(this.rest, this.store, guildId)
|
|
2212
|
+
+ }
|
|
2213
|
+
+
|
|
2214
|
+
+ members(guildId: string): MemberManager {
|
|
2215
|
+
+ return new MemberManager(this.rest, this.store, guildId)
|
|
2216
|
+
+ }
|
|
2217
|
+
+
|
|
2218
|
+
async fetchChannels(guildId: string): Promise<ChameleonAPIResult<Channel[]>> {
|
|
2219
|
+
|
|
2220
|
+
const result = await this.rest.get<unknown[]>(`/guilds/${guildId}/channels`)
|
|
2221
|
+
-
|
|
2222
|
+
- if (!result.ok) return result
|
|
2223
|
+
+
|
|
2224
|
+
+ if (!result.ok) return result as ChameleonAPIResult<never>
|
|
2225
|
+
|
|
2226
|
+
const channels = result.data.map(raw => buildChannel(raw as Record<string, unknown>, guildId))
|
|
2227
|
+
-
|
|
2228
|
+
+
|
|
2229
|
+
for (const c of channels) {
|
|
2230
|
+
this.store.channels.set(c.id, c)
|
|
2231
|
+
}
|
|
2232
|
+
@@ -27,11 +38,11 @@ export class GuildManager extends BaseManager<Guild> {
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
async ban(guildId: string, userId: string, options?: { deleteMessageSeconds?: number; reason?: string }): Promise<ChameleonAPIResult<void>> {
|
|
2236
|
+
-
|
|
2237
|
+
- const payload: Record<string, any> = {}
|
|
2238
|
+
-
|
|
2239
|
+
+
|
|
2240
|
+
+ const payload: Record<string, unknown> = {}
|
|
2241
|
+
+
|
|
2242
|
+
if (options?.deleteMessageSeconds !== undefined) payload.delete_message_seconds = options.deleteMessageSeconds
|
|
2243
|
+
-
|
|
2244
|
+
+
|
|
2245
|
+
const headers: Record<string, string> = {}
|
|
2246
|
+
if (options?.reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(options.reason)
|
|
2247
|
+
|
|
2248
|
+
@@ -42,7 +53,7 @@ export class GuildManager extends BaseManager<Guild> {
|
|
2249
|
+
async unban(guildId: string, userId: string, reason?: string): Promise<ChameleonAPIResult<void>> {
|
|
2250
|
+
|
|
2251
|
+
const headers: Record<string, string> = {}
|
|
2252
|
+
-
|
|
2253
|
+
+
|
|
2254
|
+
if (reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(reason)
|
|
2255
|
+
|
|
2256
|
+
const result = await this.rest.delete(`/guilds/${guildId}/bans/${userId}`, headers)
|
|
2257
|
+
@@ -52,15 +63,41 @@ export class GuildManager extends BaseManager<Guild> {
|
|
2258
|
+
async kick(guildId: string, userId: string, reason?: string): Promise<ChameleonAPIResult<void>> {
|
|
2259
|
+
|
|
2260
|
+
const headers: Record<string, string> = {}
|
|
2261
|
+
-
|
|
2262
|
+
+
|
|
2263
|
+
if (reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(reason)
|
|
2264
|
+
|
|
2265
|
+
const result = await this.rest.delete(`/guilds/${guildId}/members/${userId}`, headers)
|
|
2266
|
+
-
|
|
2267
|
+
+
|
|
2268
|
+
if (result.ok) {
|
|
2269
|
+
this.store.members.delete(TongueStore.memberKey(guildId, userId))
|
|
2270
|
+
}
|
|
2271
|
+
-
|
|
2272
|
+
+
|
|
2273
|
+
+ return result as ChameleonAPIResult<void>
|
|
2274
|
+
+ }
|
|
2275
|
+
+
|
|
2276
|
+
+ async edit(guildId: string, payload: Partial<Guild>, reason?: string): Promise<ChameleonAPIResult<Guild>> {
|
|
2277
|
+
+
|
|
2278
|
+
+ const headers: Record<string, string> = {}
|
|
2279
|
+
+
|
|
2280
|
+
+ if (reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(reason)
|
|
2281
|
+
+
|
|
2282
|
+
+ const result = await this.rest.patch<unknown>(this.endpoint(guildId), toSnakeCase(payload), headers)
|
|
2283
|
+
+
|
|
2284
|
+
+ if (!result.ok) return result as ChameleonAPIResult<never>
|
|
2285
|
+
+
|
|
2286
|
+
+ const entity = this.build(result.data as Record<string, unknown>)
|
|
2287
|
+
+
|
|
2288
|
+
+ this.store.guilds.set(entity.id, entity)
|
|
2289
|
+
+
|
|
2290
|
+
+ return { ok: true, data: entity }
|
|
2291
|
+
+ }
|
|
2292
|
+
+
|
|
2293
|
+
+ async delete(guildId: string): Promise<ChameleonAPIResult<void>> {
|
|
2294
|
+
+
|
|
2295
|
+
+ const result = await this.rest.delete(this.endpoint(guildId))
|
|
2296
|
+
+
|
|
2297
|
+
+ if (result.ok) this.store.guilds.delete(guildId)
|
|
2298
|
+
+
|
|
2299
|
+
return result as ChameleonAPIResult<void>
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
diff --git a/src/managers/index.ts b/src/managers/index.ts
|
|
2304
|
+
index f89c5d2..1e57eef 100644
|
|
2305
|
+
--- a/src/managers/index.ts
|
|
2306
|
+
+++ b/src/managers/index.ts
|
|
2307
|
+
@@ -4,4 +4,5 @@ export { GuildManager } from './guild.js'
|
|
2308
|
+
export { ChannelManager } from './channel.js'
|
|
2309
|
+
export { MemberManager } from './member.js'
|
|
2310
|
+
export { MessageManager } from './message.js'
|
|
2311
|
+
-export { CollectorManager } from './collector.js'
|
|
2312
|
+
|
|
2313
|
+
+export { CollectorManager } from './collector.js'
|
|
2314
|
+
+export { RoleManager } from './role.js'
|
|
2315
|
+
|
|
2316
|
+
diff --git a/src/managers/member.ts b/src/managers/member.ts
|
|
2317
|
+
index 39d2604..cf2cd86 100644
|
|
2318
|
+
--- a/src/managers/member.ts
|
|
2319
|
+
+++ b/src/managers/member.ts
|
|
2320
|
+
@@ -3,6 +3,7 @@ import { TongueStore } from '../client/store.js'
|
|
2321
|
+
import type { ChameleonAPIResult } from '../rest/types.js'
|
|
2322
|
+
import type { Member } from '../types/guild/index.js'
|
|
2323
|
+
import { buildMember } from '../builders/index.js'
|
|
2324
|
+
+import { toSnakeCase } from '../utils/object.js'
|
|
2325
|
+
|
|
2326
|
+
export class MemberManager {
|
|
2327
|
+
|
|
2328
|
+
@@ -21,15 +22,32 @@ export class MemberManager {
|
|
2329
|
+
const cacheKey = TongueStore.memberKey(this.guildId, userId)
|
|
2330
|
+
|
|
2331
|
+
if (!force) {
|
|
2332
|
+
-
|
|
2333
|
+
+
|
|
2334
|
+
const cached = this.store.members.get(cacheKey)
|
|
2335
|
+
|
|
2336
|
+
if (cached) return { ok: true, data: cached }
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
const result = await this.rest.get<unknown>(`/guilds/${this.guildId}/members/${userId}`)
|
|
2340
|
+
- if (!result.ok) return result
|
|
2341
|
+
+ if (!result.ok) return result as ChameleonAPIResult<never>
|
|
2342
|
+
+
|
|
2343
|
+
+ const member = buildMember(result.data as Record<string, unknown>, this.guildId, this.store)
|
|
2344
|
+
+ this.store.members.set(cacheKey, member)
|
|
2345
|
+
+
|
|
2346
|
+
+ return { ok: true, data: member }
|
|
2347
|
+
+ }
|
|
2348
|
+
|
|
2349
|
+
+ async edit(userId: string, payload: Partial<Member>, reason?: string): Promise<ChameleonAPIResult<Member>> {
|
|
2350
|
+
+
|
|
2351
|
+
+ const headers: Record<string, string> = {}
|
|
2352
|
+
+
|
|
2353
|
+
+ if (reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(reason)
|
|
2354
|
+
+
|
|
2355
|
+
+ const result = await this.rest.patch<unknown>(`/guilds/${this.guildId}/members/${userId}`, toSnakeCase(payload), headers)
|
|
2356
|
+
+
|
|
2357
|
+
+ if (!result.ok) return result as ChameleonAPIResult<never>
|
|
2358
|
+
+
|
|
2359
|
+
+ const cacheKey = TongueStore.memberKey(this.guildId, userId)
|
|
2360
|
+
const member = buildMember(result.data as Record<string, unknown>, this.guildId, this.store)
|
|
2361
|
+
this.store.members.set(cacheKey, member)
|
|
2362
|
+
|
|
2363
|
+
diff --git a/src/managers/message.ts b/src/managers/message.ts
|
|
2364
|
+
index 35510ea..3e2916f 100644
|
|
2365
|
+
--- a/src/managers/message.ts
|
|
2366
|
+
+++ b/src/managers/message.ts
|
|
2367
|
+
@@ -1,9 +1,17 @@
|
|
2368
|
+
import type { ChameleonREST } from '../rest/index.js'
|
|
2369
|
+
import type { TongueStore } from '../client/store.js'
|
|
2370
|
+
-import { buildMessage } from '../builders/index.js'
|
|
2371
|
+
-import type { Message } from '../types/message/index.js'
|
|
2372
|
+
+import { buildMessage, serializeComponent } from '../builders/index.js'
|
|
2373
|
+
+import type { Message, Embed } from '../types/message/index.js'
|
|
2374
|
+
+import type { MessageComponent } from '../types/components/index.js'
|
|
2375
|
+
import type { ChameleonAPIResult } from '../rest/types.js'
|
|
2376
|
+
|
|
2377
|
+
+export type MessageCreateOptions = string | {
|
|
2378
|
+
+ content?: string
|
|
2379
|
+
+ embeds?: (Embed | { toJSON(): Record<string, unknown> } | Record<string, unknown>)[]
|
|
2380
|
+
+ components?: (MessageComponent | { build?(): MessageComponent } | { toJSON(): Record<string, unknown> } | Record<string, unknown>)[]
|
|
2381
|
+
+ reply?: { messageId: string, failIfNotExists?: boolean }
|
|
2382
|
+
+}
|
|
2383
|
+
+
|
|
2384
|
+
export class MessageManager {
|
|
2385
|
+
|
|
2386
|
+
constructor (
|
|
2387
|
+
@@ -29,12 +37,26 @@ export class MessageManager {
|
|
2388
|
+
return { ok: true, data: message }
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
- async send(channelId: string, payload: string | Record<string, any>): Promise<ChameleonAPIResult<Message>> {
|
|
2392
|
+
+ async send(channelId: string, payload: MessageCreateOptions): Promise<ChameleonAPIResult<Message>> {
|
|
2393
|
+
+
|
|
2394
|
+
+ const data: Record<string, unknown> = typeof payload === 'string' ? { content: payload } : { ...payload }
|
|
2395
|
+
+
|
|
2396
|
+
+ if (typeof payload === 'object') {
|
|
2397
|
+
+ if (payload.embeds) {
|
|
2398
|
+
+ data.embeds = payload.embeds.map(e => (e && typeof (e as { toJSON?(): Record<string, unknown> }).toJSON === 'function' ? (e as { toJSON(): Record<string, unknown> }).toJSON() : e))
|
|
2399
|
+
+ }
|
|
2400
|
+
+ if (payload.components) {
|
|
2401
|
+
+ data.components = payload.components.map(c => serializeComponent(c))
|
|
2402
|
+
+ }
|
|
2403
|
+
+ if (payload.reply) {
|
|
2404
|
+
+ data.message_reference = { message_id: payload.reply.messageId, fail_if_not_exists: payload.reply.failIfNotExists ?? true }
|
|
2405
|
+
+ delete data.reply
|
|
2406
|
+
+ }
|
|
2407
|
+
+ }
|
|
2408
|
+
|
|
2409
|
+
- const data = typeof payload === 'string' ? { content: payload } : payload
|
|
2410
|
+
const result = await this.rest.post<unknown>(`/channels/${channelId}/messages`, data)
|
|
2411
|
+
|
|
2412
|
+
- if (!result.ok) return result
|
|
2413
|
+
+ if (!result.ok) return result as ChameleonAPIResult<never>
|
|
2414
|
+
|
|
2415
|
+
const message = buildMessage(result.data as Record<string, unknown>, this.store)
|
|
2416
|
+
|
|
2417
|
+
@@ -43,12 +65,22 @@ export class MessageManager {
|
|
2418
|
+
return { ok: true, data: message }
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
- async edit(channelId: string, messageId: string, payload: string | Record<string, any>): Promise<ChameleonAPIResult<Message>> {
|
|
2422
|
+
+ async edit(channelId: string, messageId: string, payload: MessageCreateOptions): Promise<ChameleonAPIResult<Message>> {
|
|
2423
|
+
|
|
2424
|
+
- const data = typeof payload === 'string' ? { content: payload } : payload
|
|
2425
|
+
+ const data: Record<string, unknown> = typeof payload === 'string' ? { content: payload } : { ...payload }
|
|
2426
|
+
+
|
|
2427
|
+
+ if (typeof payload === 'object') {
|
|
2428
|
+
+ if (payload.embeds) {
|
|
2429
|
+
+ data.embeds = payload.embeds.map(e => (e && typeof (e as { toJSON?(): Record<string, unknown> }).toJSON === 'function' ? (e as { toJSON(): Record<string, unknown> }).toJSON() : e))
|
|
2430
|
+
+ }
|
|
2431
|
+
+ if (payload.components) {
|
|
2432
|
+
+ data.components = payload.components.map(c => serializeComponent(c))
|
|
2433
|
+
+ }
|
|
2434
|
+
+ }
|
|
2435
|
+
+
|
|
2436
|
+
const result = await this.rest.patch<unknown>(`/channels/${channelId}/messages/${messageId}`, data)
|
|
2437
|
+
|
|
2438
|
+
- if (!result.ok) return result
|
|
2439
|
+
+ if (!result.ok) return result as ChameleonAPIResult<never>
|
|
2440
|
+
|
|
2441
|
+
const oldMsg = this.store.messages.get(messageId)
|
|
2442
|
+
const message = buildMessage(result.data as Record<string, unknown>, this.store, oldMsg)
|
|
2443
|
+
@@ -67,4 +99,35 @@ export class MessageManager {
|
|
2444
|
+
|
|
2445
|
+
return result as ChameleonAPIResult<void>
|
|
2446
|
+
}
|
|
2447
|
+
+
|
|
2448
|
+
+ async list(channelId: string, options?: { limit?: number, before?: string, after?: string, around?: string }): Promise<ChameleonAPIResult<Message[]>> {
|
|
2449
|
+
+
|
|
2450
|
+
+ let url = `/channels/${channelId}/messages`
|
|
2451
|
+
+
|
|
2452
|
+
+ if (options) {
|
|
2453
|
+
+
|
|
2454
|
+
+ const params = new URLSearchParams()
|
|
2455
|
+
+
|
|
2456
|
+
+ if (options.limit) params.append('limit', options.limit.toString())
|
|
2457
|
+
+ if (options.before) params.append('before', options.before)
|
|
2458
|
+
+ if (options.after) params.append('after', options.after)
|
|
2459
|
+
+ if (options.around) params.append('around', options.around)
|
|
2460
|
+
+
|
|
2461
|
+
+ const qs = params.toString()
|
|
2462
|
+
+
|
|
2463
|
+
+ if (qs) url += `?${qs}`
|
|
2464
|
+
+ }
|
|
2465
|
+
+
|
|
2466
|
+
+ const result = await this.rest.get<unknown[]>(url)
|
|
2467
|
+
+
|
|
2468
|
+
+ if (!result.ok) return result as ChameleonAPIResult<never>
|
|
2469
|
+
+
|
|
2470
|
+
+ const messages = (result.data as Record<string, unknown>[]).map(msgData => {
|
|
2471
|
+
+ const msg = buildMessage(msgData, this.store)
|
|
2472
|
+
+ this.store.messages.set(msg.id, msg)
|
|
2473
|
+
+ return msg
|
|
2474
|
+
+ })
|
|
2475
|
+
+
|
|
2476
|
+
+ return { ok: true, data: messages }
|
|
2477
|
+
+ }
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
diff --git a/src/managers/role.ts b/src/managers/role.ts
|
|
2481
|
+
new file mode 100644
|
|
2482
|
+
index 0000000..5f9f83d
|
|
2483
|
+
--- /dev/null
|
|
2484
|
+
+++ b/src/managers/role.ts
|
|
2485
|
+
@@ -0,0 +1,99 @@
|
|
2486
|
+
+import type { ChameleonREST } from '../rest/index.js'
|
|
2487
|
+
+import { TongueStore } from '../client/store.js'
|
|
2488
|
+
+import type { ChameleonAPIResult } from '../rest/types.js'
|
|
2489
|
+
+import type { Role } from '../types/guild/index.js'
|
|
2490
|
+
+import { buildRole } from '../builders/index.js'
|
|
2491
|
+
+import { toSnakeCase } from '../utils/object.js'
|
|
2492
|
+
+
|
|
2493
|
+
+export class RoleManager {
|
|
2494
|
+
+
|
|
2495
|
+
+ constructor (
|
|
2496
|
+
+ protected rest: ChameleonREST,
|
|
2497
|
+
+ protected store: TongueStore,
|
|
2498
|
+
+ protected guildId: string
|
|
2499
|
+
+ ) {}
|
|
2500
|
+
+
|
|
2501
|
+
+ async fetch(roleId: string, force = false): Promise<ChameleonAPIResult<Role>> {
|
|
2502
|
+
+
|
|
2503
|
+
+ if (!force) {
|
|
2504
|
+
+
|
|
2505
|
+
+ const cached = this.store.roles.get(roleId)
|
|
2506
|
+
+
|
|
2507
|
+
+ if (cached) return { ok: true, data: cached }
|
|
2508
|
+
+ }
|
|
2509
|
+
+
|
|
2510
|
+
+ const result = await this.rest.get<unknown[]>(`/guilds/${this.guildId}/roles`)
|
|
2511
|
+
+
|
|
2512
|
+
+ if (!result.ok) return result as ChameleonAPIResult<never>
|
|
2513
|
+
+
|
|
2514
|
+
+ const roles = result.data.map(raw => buildRole(raw as Record<string, unknown>))
|
|
2515
|
+
+
|
|
2516
|
+
+ for (const r of roles) {
|
|
2517
|
+
+ this.store.roles.set(r.id, r)
|
|
2518
|
+
+ }
|
|
2519
|
+
+
|
|
2520
|
+
+ const target = roles.find(r => r.id === roleId)
|
|
2521
|
+
+
|
|
2522
|
+
+ if (target) return { ok: true, data: target }
|
|
2523
|
+
+
|
|
2524
|
+
+ return { ok: false, status: 404, message: 'Role not found' } as ChameleonAPIResult<never>
|
|
2525
|
+
+ }
|
|
2526
|
+
+
|
|
2527
|
+
+ async list(): Promise<ChameleonAPIResult<Role[]>> {
|
|
2528
|
+
+
|
|
2529
|
+
+ const result = await this.rest.get<unknown[]>(`/guilds/${this.guildId}/roles`)
|
|
2530
|
+
+
|
|
2531
|
+
+ if (!result.ok) return result as ChameleonAPIResult<never>
|
|
2532
|
+
+
|
|
2533
|
+
+ const roles = result.data.map(raw => buildRole(raw as Record<string, unknown>))
|
|
2534
|
+
+
|
|
2535
|
+
+ for (const r of roles) {
|
|
2536
|
+
+ this.store.roles.set(r.id, r)
|
|
2537
|
+
+ }
|
|
2538
|
+
+
|
|
2539
|
+
+ return { ok: true, data: roles }
|
|
2540
|
+
+ }
|
|
2541
|
+
+
|
|
2542
|
+
+ async create(payload: Partial<Role>, reason?: string): Promise<ChameleonAPIResult<Role>> {
|
|
2543
|
+
+
|
|
2544
|
+
+ const headers: Record<string, string> = {}
|
|
2545
|
+
+ if (reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(reason)
|
|
2546
|
+
+
|
|
2547
|
+
+ const result = await this.rest.post<unknown>(`/guilds/${this.guildId}/roles`, toSnakeCase(payload), headers)
|
|
2548
|
+
+ if (!result.ok) return result as ChameleonAPIResult<never>
|
|
2549
|
+
+
|
|
2550
|
+
+ const role = buildRole(result.data as Record<string, unknown>)
|
|
2551
|
+
+ this.store.roles.set(role.id, role)
|
|
2552
|
+
+
|
|
2553
|
+
+ return { ok: true, data: role }
|
|
2554
|
+
+ }
|
|
2555
|
+
+
|
|
2556
|
+
+ async edit(roleId: string, payload: Partial<Role>, reason?: string): Promise<ChameleonAPIResult<Role>> {
|
|
2557
|
+
+
|
|
2558
|
+
+ const headers: Record<string, string> = {}
|
|
2559
|
+
+
|
|
2560
|
+
+ if (reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(reason)
|
|
2561
|
+
+
|
|
2562
|
+
+ const result = await this.rest.patch<unknown>(`/guilds/${this.guildId}/roles/${roleId}`, toSnakeCase(payload), headers)
|
|
2563
|
+
+
|
|
2564
|
+
+ if (!result.ok) return result as ChameleonAPIResult<never>
|
|
2565
|
+
+
|
|
2566
|
+
+ const role = buildRole(result.data as Record<string, unknown>)
|
|
2567
|
+
+ this.store.roles.set(role.id, role)
|
|
2568
|
+
+
|
|
2569
|
+
+ return { ok: true, data: role }
|
|
2570
|
+
+ }
|
|
2571
|
+
+
|
|
2572
|
+
+ async delete(roleId: string, reason?: string): Promise<ChameleonAPIResult<void>> {
|
|
2573
|
+
+
|
|
2574
|
+
+ const headers: Record<string, string> = {}
|
|
2575
|
+
+
|
|
2576
|
+
+ if (reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(reason)
|
|
2577
|
+
+
|
|
2578
|
+
+ const result = await this.rest.delete(`/guilds/${this.guildId}/roles/${roleId}`, headers)
|
|
2579
|
+
+
|
|
2580
|
+
+ if (result.ok) this.store.roles.delete(roleId)
|
|
2581
|
+
+
|
|
2582
|
+
+ return result as ChameleonAPIResult<void>
|
|
2583
|
+
+ }
|
|
2584
|
+
+}
|
|
2585
|
+
|
|
2586
|
+
diff --git a/src/managers/user.ts b/src/managers/user.ts
|
|
2587
|
+
index 599fcdc..cb7c7bb 100644
|
|
2588
|
+
--- a/src/managers/user.ts
|
|
2589
|
+
+++ b/src/managers/user.ts
|
|
2590
|
+
@@ -7,4 +7,13 @@ export class UserManager extends BaseManager<User> {
|
|
2591
|
+
protected storeKey = 'users' as const
|
|
2592
|
+
protected endpoint(id: string) { return `/users/${id}` }
|
|
2593
|
+
protected build = buildUser
|
|
2594
|
+
+
|
|
2595
|
+
+ async createDM(userId: string): Promise<import('../rest/types.js').ChameleonAPIResult<import('../types/channel/index.js').Channel>> {
|
|
2596
|
+
+
|
|
2597
|
+
+ const result = await this.rest.post<unknown>('/users/@me/channels', { recipient_id: userId })
|
|
2598
|
+
+
|
|
2599
|
+
+ if (!result.ok) return result as import('../rest/types.js').ChameleonAPIResult<never>
|
|
2600
|
+
+
|
|
2601
|
+
+ return { ok: true, data: (await import('../builders/index.js')).buildChannel(result.data as Record<string, unknown>) }
|
|
2602
|
+
+ }
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
diff --git a/src/types/automod/index.ts b/src/types/automod/index.ts
|
|
2606
|
+
index 31cf066..ab3c6d0 100644
|
|
2607
|
+
--- a/src/types/automod/index.ts
|
|
2608
|
+
+++ b/src/types/automod/index.ts
|
|
2609
|
+
@@ -1,5 +1,3 @@
|
|
2610
|
+
-import type { User } from '../user/index.js'
|
|
2611
|
+
-
|
|
2612
|
+
export const AutoModerationEventType = {
|
|
2613
|
+
MESSAGE_SEND: 1,
|
|
2614
|
+
MEMBER_UPDATE: 2
|
|
2615
|
+
diff --git a/src/utils/object.ts b/src/utils/object.ts
|
|
2616
|
+
new file mode 100644
|
|
2617
|
+
index 0000000..ef4a652
|
|
2618
|
+
--- /dev/null
|
|
2619
|
+
+++ b/src/utils/object.ts
|
|
2620
|
+
@@ -0,0 +1,23 @@
|
|
2621
|
+
+export function toSnakeCase(obj: unknown): unknown {
|
|
2622
|
+
+
|
|
2623
|
+
+ if (Array.isArray(obj)) {
|
|
2624
|
+
+
|
|
2625
|
+
+ return obj.map(v => toSnakeCase(v))
|
|
2626
|
+
+
|
|
2627
|
+
+ } else if (obj !== null && typeof obj === 'object') {
|
|
2628
|
+
+
|
|
2629
|
+
+ if (typeof (obj as Record<string, unknown>).toJSON === 'function') {
|
|
2630
|
+
+ return (obj as { toJSON: () => unknown }).toJSON()
|
|
2631
|
+
+ }
|
|
2632
|
+
+
|
|
2633
|
+
+ return Object.keys(obj).reduce((result, key) => {
|
|
2634
|
+
+
|
|
2635
|
+
+ const snakeKey = key.replace(/([A-Z])/g, "_$1").toLowerCase()
|
|
2636
|
+
+
|
|
2637
|
+
+ result[snakeKey] = toSnakeCase((obj as Record<string, unknown>)[key])
|
|
2638
|
+
+
|
|
2639
|
+
+ return result
|
|
2640
|
+
+ }, {} as Record<string, unknown>)
|
|
2641
|
+
+ }
|
|
2642
|
+
+ return obj
|
|
2643
|
+
+}
|
|
2644
|
+
|
|
2645
|
+
diff --git a/tests/builders.test.ts b/tests/builders.test.ts
|
|
2646
|
+
index 83a12b3..e42b113 100644
|
|
2647
|
+
--- a/tests/builders.test.ts
|
|
2648
|
+
+++ b/tests/builders.test.ts
|
|
2649
|
+
@@ -56,10 +56,10 @@ describe('SelectMenuBuilder', () => {
|
|
2650
|
+
const json = sel.toJSON()
|
|
2651
|
+
|
|
2652
|
+
expect(json.type).toBe(ComponentType.STRING_SELECT)
|
|
2653
|
+
- expect(json.customId).toBe('sel')
|
|
2654
|
+
+ expect(json.custom_id).toBe('sel')
|
|
2655
|
+
expect(json.placeholder).toBe('Pick')
|
|
2656
|
+
- expect(json.minValues).toBe(1)
|
|
2657
|
+
- expect(json.maxValues).toBe(3)
|
|
2658
|
+
+ expect(json.min_values).toBe(1)
|
|
2659
|
+
+ expect(json.max_values).toBe(3)
|
|
2660
|
+
expect(json.disabled).toBe(true)
|
|
2661
|
+
expect(json.options).toHaveLength(3)
|
|
2662
|
+
})
|
|
2663
|
+
@@ -110,8 +110,8 @@ describe('ActionRowBuilder', () => {
|
|
2664
|
+
|
|
2665
|
+
expect(built.type).toBe(ComponentType.ACTION_ROW)
|
|
2666
|
+
expect(built.components).toHaveLength(2)
|
|
2667
|
+
- expect(built.components?.[0]?.customId).toBe('b1')
|
|
2668
|
+
- expect(built.components?.[1]?.customId).toBe('b2')
|
|
2669
|
+
+ expect((built.components?.[0] as Record<string, unknown>)?.customId).toBe('b1')
|
|
2670
|
+
+ expect((built.components?.[1] as Record<string, unknown>)?.customId).toBe('b2')
|
|
2671
|
+
|
|
2672
|
+
const json = row.toJSON()
|
|
2673
|
+
|
|
2674
|
+
diff --git a/tests/components.test.ts b/tests/components.test.ts
|
|
2675
|
+
index 157fb37..a4083e6 100644
|
|
2676
|
+
--- a/tests/components.test.ts
|
|
2677
|
+
+++ b/tests/components.test.ts
|
|
2678
|
+
@@ -192,9 +192,9 @@ describe('ActionRow', () => {
|
|
2679
|
+
|
|
2680
|
+
expect(row.type).toBe(COMPONENT_TYPES.ACTION_ROW)
|
|
2681
|
+
expect(row.components).toHaveLength(1)
|
|
2682
|
+
- expect(row.components[0].type).toBe(COMPONENT_TYPES.BUTTON)
|
|
2683
|
+
- expect(row.components[0].label).toBe('OK')
|
|
2684
|
+
- expect(row.components[0].style).toBe(3) // success
|
|
2685
|
+
+ expect((row.components[0] as Record<string, unknown>).type).toBe(COMPONENT_TYPES.BUTTON)
|
|
2686
|
+
+ expect((row.components[0] as Record<string, unknown>).label).toBe('OK')
|
|
2687
|
+
+ expect((row.components[0] as Record<string, unknown>).style).toBe(3)
|
|
2688
|
+
})
|
|
2689
|
+
|
|
2690
|
+
it('should wrap a string select into an action row', () => {
|
|
2691
|
+
@@ -206,8 +206,8 @@ describe('ActionRow', () => {
|
|
2692
|
+
})
|
|
2693
|
+
|
|
2694
|
+
const row = ActionRow.of(sel)
|
|
2695
|
+
- expect(row.components[0].type).toBe(COMPONENT_TYPES.STRING_SELECT)
|
|
2696
|
+
- expect(row.components[0].options).toHaveLength(1)
|
|
2697
|
+
+ expect((row.components[0] as Record<string, unknown>).type).toBe(COMPONENT_TYPES.STRING_SELECT)
|
|
2698
|
+
+ expect((row.components[0] as Record<string, unknown>).options).toHaveLength(1)
|
|
2699
|
+
})
|
|
2700
|
+
|
|
2701
|
+
it('should wrap user/role/channel/mentionable selects', () => {
|
|
2702
|
+
@@ -217,10 +217,10 @@ describe('ActionRow', () => {
|
|
2703
|
+
const channel = defineChannelSelect({ customId: 'ch', execute: async () => {} })
|
|
2704
|
+
const mentionable = defineMentionableSelect({ customId: 'm', execute: async () => {} })
|
|
2705
|
+
|
|
2706
|
+
- expect(ActionRow.of(user).components[0].type).toBe(COMPONENT_TYPES.USER_SELECT)
|
|
2707
|
+
- expect(ActionRow.of(role).components[0].type).toBe(COMPONENT_TYPES.ROLE_SELECT)
|
|
2708
|
+
- expect(ActionRow.of(channel).components[0].type).toBe(COMPONENT_TYPES.CHANNEL_SELECT)
|
|
2709
|
+
- expect(ActionRow.of(mentionable).components[0].type).toBe(COMPONENT_TYPES.MENTIONABLE_SELECT)
|
|
2710
|
+
+ expect((ActionRow.of(user).components[0] as Record<string, unknown>).type).toBe(COMPONENT_TYPES.USER_SELECT)
|
|
2711
|
+
+ expect((ActionRow.of(role).components[0] as Record<string, unknown>).type).toBe(COMPONENT_TYPES.ROLE_SELECT)
|
|
2712
|
+
+ expect((ActionRow.of(channel).components[0] as Record<string, unknown>).type).toBe(COMPONENT_TYPES.CHANNEL_SELECT)
|
|
2713
|
+
+ expect((ActionRow.of(mentionable).components[0] as Record<string, unknown>).type).toBe(COMPONENT_TYPES.MENTIONABLE_SELECT)
|
|
2714
|
+
})
|
|
2715
|
+
|
|
2716
|
+
it('should wrap text input fields', () => {
|
|
2717
|
+
@@ -228,9 +228,9 @@ describe('ActionRow', () => {
|
|
2718
|
+
const f = field.short('name', 'Your Name')
|
|
2719
|
+
const row = ActionRow.of(f)
|
|
2720
|
+
|
|
2721
|
+
- expect(row.components[0].type).toBe(COMPONENT_TYPES.TEXT_INPUT)
|
|
2722
|
+
- expect(row.components[0].style).toBe(TEXT_INPUT_STYLES.SHORT)
|
|
2723
|
+
- expect(row.components[0].label).toBe('Your Name')
|
|
2724
|
+
+ expect((row.components[0] as Record<string, unknown>).type).toBe(COMPONENT_TYPES.TEXT_INPUT)
|
|
2725
|
+
+ expect((row.components[0] as Record<string, unknown>).style).toBe(TEXT_INPUT_STYLES.SHORT)
|
|
2726
|
+
+ expect((row.components[0] as Record<string, unknown>).label).toBe('Your Name')
|
|
2727
|
+
})
|
|
2728
|
+
|
|
2729
|
+
it('should strip undefined keys from output', () => {
|
|
2730
|
+
@@ -243,7 +243,7 @@ describe('ActionRow', () => {
|
|
2731
|
+
})
|
|
2732
|
+
|
|
2733
|
+
const row = ActionRow.of(btn)
|
|
2734
|
+
- const comp = row.components[0]
|
|
2735
|
+
+ const comp = row.components[0] as Record<string, unknown>
|
|
2736
|
+
|
|
2737
|
+
expect(Object.keys(comp)).not.toContain('url')
|
|
2738
|
+
expect(Object.keys(comp)).not.toContain('emoji')
|
|
2739
|
+
@@ -255,7 +255,7 @@ describe('ActionRow', () => {
|
|
2740
|
+
const raw = { type: 999, custom_id: 'raw' }
|
|
2741
|
+
const row = ActionRow.of(raw)
|
|
2742
|
+
|
|
2743
|
+
- expect(row.components[0].type).toBe(999)
|
|
2744
|
+
+ expect((row.components[0] as Record<string, unknown>).type).toBe(999)
|
|
2745
|
+
})
|
|
2746
|
+
|
|
2747
|
+
it('should handle multiple buttons in one row', () => {
|
|
2748
|
+
@@ -267,8 +267,8 @@ describe('ActionRow', () => {
|
|
2749
|
+
const row = ActionRow.of(a, b, c)
|
|
2750
|
+
|
|
2751
|
+
expect(row.components).toHaveLength(3)
|
|
2752
|
+
- expect(row.components[0].style).toBe(1) // primary
|
|
2753
|
+
- expect(row.components[1].style).toBe(4) // danger
|
|
2754
|
+
- expect(row.components[2].style).toBe(2) // secondary
|
|
2755
|
+
+ expect((row.components[0] as Record<string, unknown>).style).toBe(1) // primary
|
|
2756
|
+
+ expect((row.components[1] as Record<string, unknown>).style).toBe(4) // danger
|
|
2757
|
+
+ expect((row.components[2] as Record<string, unknown>).style).toBe(2) // secondary
|
|
2758
|
+
})
|
|
2759
|
+
})
|
|
2760
|
+
|
|
2761
|
+
diff --git a/tests/discord.test.ts b/tests/discord.test.ts
|
|
2762
|
+
new file mode 100644
|
|
2763
|
+
index 0000000..e9d4aa9
|
|
2764
|
+
--- /dev/null
|
|
2765
|
+
+++ b/tests/discord.test.ts
|
|
2766
|
+
@@ -0,0 +1,151 @@
|
|
2767
|
+
+import { describe, it, expect, vi } from 'vitest'
|
|
2768
|
+
+import { toSnakeCase } from '../src/utils/object.ts'
|
|
2769
|
+
+import { ChameleonREST } from '../src/rest/index.ts'
|
|
2770
|
+
+
|
|
2771
|
+
+describe('Discord API Compliance', () => {
|
|
2772
|
+
+
|
|
2773
|
+
+ describe('Payload Serialization (toSnakeCase)', () => {
|
|
2774
|
+
+
|
|
2775
|
+
+ it('should recursively convert camelCase keys to snake_case', () => {
|
|
2776
|
+
+
|
|
2777
|
+
+ const payload = {
|
|
2778
|
+
+ customId: 'my_button',
|
|
2779
|
+
+ maxValues: 5,
|
|
2780
|
+
+ channelId: '123',
|
|
2781
|
+
+ embeds: [
|
|
2782
|
+
+ {
|
|
2783
|
+
+ titleText: 'Hello',
|
|
2784
|
+
+ authorName: 'impulsedoes'
|
|
2785
|
+
+ }
|
|
2786
|
+
+ ]
|
|
2787
|
+
+ }
|
|
2788
|
+
+
|
|
2789
|
+
+ const serialized = toSnakeCase(payload) as unknown
|
|
2790
|
+
+
|
|
2791
|
+
+ expect(serialized.custom_id).toBe('my_button')
|
|
2792
|
+
+ expect(serialized.max_values).toBe(5)
|
|
2793
|
+
+ expect(serialized.channel_id).toBe('123')
|
|
2794
|
+
+
|
|
2795
|
+
+ expect(serialized.embeds[0].title_text).toBe('Hello')
|
|
2796
|
+
+ expect(serialized.embeds[0].author_name).toBe('impulsedoes')
|
|
2797
|
+
+
|
|
2798
|
+
+ expect(serialized.customId).toBeUndefined()
|
|
2799
|
+
+ expect(serialized.maxValues).toBeUndefined()
|
|
2800
|
+
+ })
|
|
2801
|
+
+
|
|
2802
|
+
+ it('should correctly handle nulls and primitives', () => {
|
|
2803
|
+
+ expect(toSnakeCase(null)).toBe(null)
|
|
2804
|
+
+ expect(toSnakeCase('string')).toBe('string')
|
|
2805
|
+
+ expect(toSnakeCase(123)).toBe(123)
|
|
2806
|
+
+ expect(toSnakeCase(true)).toBe(true)
|
|
2807
|
+
+ })
|
|
2808
|
+
+
|
|
2809
|
+
+ it('should respect custom toJSON methods and not deep transform them', () => {
|
|
2810
|
+
+
|
|
2811
|
+
+ const component = {
|
|
2812
|
+
+ customId: 'btn',
|
|
2813
|
+
+ toJSON() {
|
|
2814
|
+
+ return { type: 2, custom_id: 'btn_overridden' }
|
|
2815
|
+
+ }
|
|
2816
|
+
+ }
|
|
2817
|
+
+
|
|
2818
|
+
+ const payload = {
|
|
2819
|
+
+ components: [component]
|
|
2820
|
+
+ }
|
|
2821
|
+
+
|
|
2822
|
+
+ const serialized = toSnakeCase(payload) as unknown
|
|
2823
|
+
+
|
|
2824
|
+
+ expect(serialized.components[0].type).toBe(2)
|
|
2825
|
+
+ expect(serialized.components[0].custom_id).toBe('btn_overridden')
|
|
2826
|
+
+ // Shouldn't see custom_id: 'btn' from the raw component properties
|
|
2827
|
+
+ })
|
|
2828
|
+
+ })
|
|
2829
|
+
+
|
|
2830
|
+
+ describe('REST Client Headers & URLs', () => {
|
|
2831
|
+
+
|
|
2832
|
+
+ it('should inject required Discord User-Agent and Authorization headers', async () => {
|
|
2833
|
+
+
|
|
2834
|
+
+ const fetchSpy = vi.spyOn(global, 'fetch').mockResolvedValue({
|
|
2835
|
+
+ status: 200,
|
|
2836
|
+
+ ok: true,
|
|
2837
|
+
+ text: async () => JSON.stringify({ id: '123' })
|
|
2838
|
+
+ } as Response)
|
|
2839
|
+
+
|
|
2840
|
+
+ const rest = new ChameleonREST({ token: 'test_token', version: 10 })
|
|
2841
|
+
+
|
|
2842
|
+
+ await rest.get('/users/@me')
|
|
2843
|
+
+
|
|
2844
|
+
+ expect(fetchSpy).toHaveBeenCalledTimes(1)
|
|
2845
|
+
+
|
|
2846
|
+
+ const [url, init] = fetchSpy.mock.calls[0]
|
|
2847
|
+
+
|
|
2848
|
+
+ expect(url).toBe('https://discord.com/api/v10/users/@me')
|
|
2849
|
+
+
|
|
2850
|
+
+ const headers = init?.headers as Record<string, string>
|
|
2851
|
+
+ expect(headers['Authorization']).toBe('Bot test_token')
|
|
2852
|
+
+ expect(headers['User-Agent']).toMatch(/^Chameleon \(https:\/\/github.com\/impulsedoes\/chameleon\)$/)
|
|
2853
|
+
+
|
|
2854
|
+
+ fetchSpy.mockRestore()
|
|
2855
|
+
+ })
|
|
2856
|
+
+
|
|
2857
|
+
+ it('should prepend a slash to the endpoint if missing', async () => {
|
|
2858
|
+
+
|
|
2859
|
+
+ const fetchSpy = vi.spyOn(global, 'fetch').mockResolvedValue({
|
|
2860
|
+
+ status: 200,
|
|
2861
|
+
+ ok: true,
|
|
2862
|
+
+ text: async () => JSON.stringify({})
|
|
2863
|
+
+ } as Response)
|
|
2864
|
+
+
|
|
2865
|
+
+ const rest = new ChameleonREST({ token: 'test_token', version: 9 })
|
|
2866
|
+
+
|
|
2867
|
+
+ await rest.post('channels/123/messages', { content: 'hello' })
|
|
2868
|
+
+
|
|
2869
|
+
+ const [url] = fetchSpy.mock.calls[0]
|
|
2870
|
+
+ expect(url).toBe('https://discord.com/api/v9/channels/123/messages')
|
|
2871
|
+
+
|
|
2872
|
+
+ fetchSpy.mockRestore()
|
|
2873
|
+
+ })
|
|
2874
|
+
+
|
|
2875
|
+
+ it('should serialize JSON body and set Content-Type appropriately', async () => {
|
|
2876
|
+
+
|
|
2877
|
+
+ const fetchSpy = vi.spyOn(global, 'fetch').mockResolvedValue({
|
|
2878
|
+
+ status: 200,
|
|
2879
|
+
+ ok: true,
|
|
2880
|
+
+ text: async () => JSON.stringify({})
|
|
2881
|
+
+ } as Response)
|
|
2882
|
+
+
|
|
2883
|
+
+ const rest = new ChameleonREST({ token: 'test' })
|
|
2884
|
+
+
|
|
2885
|
+
+ await rest.post('/test', { foo: 'bar' })
|
|
2886
|
+
+
|
|
2887
|
+
+ const [, init] = fetchSpy.mock.calls[0]
|
|
2888
|
+
+
|
|
2889
|
+
+ const headers = init?.headers as Record<string, string>
|
|
2890
|
+
+ expect(headers['Content-Type']).toBe('application/json')
|
|
2891
|
+
+
|
|
2892
|
+
+ expect(init?.body).toBe(JSON.stringify({ foo: 'bar' }))
|
|
2893
|
+
+
|
|
2894
|
+
+ fetchSpy.mockRestore()
|
|
2895
|
+
+ })
|
|
2896
|
+
+
|
|
2897
|
+
+ it('should include audit log reasons in headers when provided', async () => {
|
|
2898
|
+
+
|
|
2899
|
+
+ const fetchSpy = vi.spyOn(global, 'fetch').mockResolvedValue({
|
|
2900
|
+
+ status: 200,
|
|
2901
|
+
+ ok: true,
|
|
2902
|
+
+ text: async () => JSON.stringify({})
|
|
2903
|
+
+ } as Response)
|
|
2904
|
+
+
|
|
2905
|
+
+ const rest = new ChameleonREST({ token: 'test' })
|
|
2906
|
+
+
|
|
2907
|
+
+ await rest.delete('/channels/123', { 'X-Audit-Log-Reason': encodeURIComponent('Because I can') })
|
|
2908
|
+
+
|
|
2909
|
+
+ const [, init] = fetchSpy.mock.calls[0]
|
|
2910
|
+
+
|
|
2911
|
+
+ const headers = init?.headers as Record<string, string>
|
|
2912
|
+
+ expect(headers['X-Audit-Log-Reason']).toBe('Because%20I%20can')
|
|
2913
|
+
+
|
|
2914
|
+
+ fetchSpy.mockRestore()
|
|
2915
|
+
+ })
|
|
2916
|
+
+ })
|
|
2917
|
+
+})
|
|
2918
|
+
|
|
2919
|
+
diff --git a/tests/embed.test.ts b/tests/embed.test.ts
|
|
2920
|
+
index 7701a24..44b67ac 100644
|
|
2921
|
+
--- a/tests/embed.test.ts
|
|
2922
|
+
+++ b/tests/embed.test.ts
|
|
2923
|
+
@@ -138,15 +138,15 @@ describe('EmbedBuilder', () => {
|
|
2924
|
+
title: 'From API',
|
|
2925
|
+
description: 'Desc',
|
|
2926
|
+
color: 0x00FF00,
|
|
2927
|
+
- author: { name: 'Auth', icon_url: 'https://icon.png' },
|
|
2928
|
+
- footer: { text: 'Foot', icon_url: 'https://foot.png' },
|
|
2929
|
+
- image: { url: 'https://img.png', proxy_url: 'https://proxy.png', width: 100, height: 200 },
|
|
2930
|
+
+ author: { name: 'Auth', iconUrl: 'https://icon.png' },
|
|
2931
|
+
+ footer: { text: 'Foot', iconUrl: 'https://foot.png' },
|
|
2932
|
+
+ image: { url: 'https://img.png', proxyUrl: 'https://proxy.png', width: 100, height: 200 },
|
|
2933
|
+
thumbnail: { url: 'https://th.png' },
|
|
2934
|
+
fields: [{ name: 'F', value: 'V', inline: true }],
|
|
2935
|
+
timestamp: '2025-06-01T00:00:00.000Z'
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
- const embed = new EmbedBuilder(raw as any)
|
|
2939
|
+
+ const embed = new EmbedBuilder(raw as unknown)
|
|
2940
|
+
const json = embed.toJSON()
|
|
2941
|
+
|
|
2942
|
+
expect(json.title).toBe('From API')
|
|
2943
|
+
diff --git a/tests/entities.test.ts b/tests/entities.test.ts
|
|
2944
|
+
index 7903fba..1791747 100644
|
|
2945
|
+
--- a/tests/entities.test.ts
|
|
2946
|
+
+++ b/tests/entities.test.ts
|
|
2947
|
+
@@ -2,6 +2,7 @@ import { describe, it, expect } from 'vitest'
|
|
2948
|
+
import { TEST_ENTITIES } from './mock/dataTest.js'
|
|
2949
|
+
import { resolveChannel, resolveGuild, resolveUser, resolveRole, buildMessage, buildGuild } from '../src/builders/index.ts'
|
|
2950
|
+
import { TongueStore } from '../src/client/store.ts'
|
|
2951
|
+
+import type { Client } from '../src/client/client.ts'
|
|
2952
|
+
import type { Channel } from '../src/types/channel/index.ts'
|
|
2953
|
+
import type { Guild, Role } from '../src/types/guild/index.ts'
|
|
2954
|
+
import type { User } from '../src/types/user/index.ts'
|
|
2955
|
+
@@ -66,23 +67,24 @@ describe('Chameleon Entity Resolvers', () => {
|
|
2956
|
+
it('should resolve from cache or fallback to ID', () => {
|
|
2957
|
+
|
|
2958
|
+
const cache = new TongueStore()
|
|
2959
|
+
+ const client = { cache } as unknown as Client
|
|
2960
|
+
|
|
2961
|
+
cache.channels.set('ch1', { id: 'ch1', type: 0 } as unknown as Channel)
|
|
2962
|
+
cache.guilds.set('g1', { id: 'g1', name: 'g' } as unknown as Guild)
|
|
2963
|
+
cache.users.set('u1', { id: 'u1', username: 'u' } as unknown as User)
|
|
2964
|
+
cache.roles.set('r1', { id: 'r1', name: 'r' } as unknown as Role)
|
|
2965
|
+
|
|
2966
|
+
- expect(resolveChannel('ch1', cache)).toHaveProperty('type', 0)
|
|
2967
|
+
- expect(resolveChannel('ch2', cache)).toEqual({ id: 'ch2' })
|
|
2968
|
+
+ expect(resolveChannel('ch1', client)).toHaveProperty('type', 0)
|
|
2969
|
+
+ expect(resolveChannel('ch2', client)).toEqual(expect.objectContaining({ id: 'ch2' }))
|
|
2970
|
+
|
|
2971
|
+
- expect(resolveGuild('g1', cache)).toHaveProperty('name', 'g')
|
|
2972
|
+
- expect(resolveGuild('g2', cache)).toEqual({ id: 'g2' })
|
|
2973
|
+
+ expect(resolveGuild('g1', client)).toHaveProperty('name', 'g')
|
|
2974
|
+
+ expect(resolveGuild('g2', client)).toEqual(expect.objectContaining({ id: 'g2' }))
|
|
2975
|
+
|
|
2976
|
+
- expect(resolveUser('u1', cache)).toHaveProperty('username', 'u')
|
|
2977
|
+
- expect(resolveUser('u2', cache)).toEqual({ id: 'u2' })
|
|
2978
|
+
+ expect(resolveUser('u1', client)).toHaveProperty('username', 'u')
|
|
2979
|
+
+ expect(resolveUser('u2', client)).toEqual(expect.objectContaining({ id: 'u2' }))
|
|
2980
|
+
|
|
2981
|
+
- expect(resolveRole('r1', cache)).toHaveProperty('name', 'r')
|
|
2982
|
+
- expect(resolveRole('r2', cache)).toEqual({ id: 'r2' })
|
|
2983
|
+
+ expect(resolveRole('r1', client)).toHaveProperty('name', 'r')
|
|
2984
|
+
+ expect(resolveRole('r2', client)).toEqual(expect.objectContaining({ id: 'r2' }))
|
|
2985
|
+
})
|
|
2986
|
+
})
|
|
2987
|
+
|
|
2988
|
+
diff --git a/tests/managers.component.test.ts b/tests/managers.component.test.ts
|
|
2989
|
+
index 5ba51f2..87960ec 100644
|
|
2990
|
+
--- a/tests/managers.component.test.ts
|
|
2991
|
+
+++ b/tests/managers.component.test.ts
|
|
2992
|
+
@@ -67,7 +67,7 @@ describe('ComponentManager', () => {
|
|
2993
|
+
const client = new Client({ token: 'test', intents: [] })
|
|
2994
|
+
const manager = new ComponentManager(client)
|
|
2995
|
+
|
|
2996
|
+
- let executedCtx: ComponentContext | undefined
|
|
2997
|
+
+ let executedCtx: ComponentContext<import('../src/types/user/index.ts').User[]> | undefined
|
|
2998
|
+
|
|
2999
|
+
const sel = defineUserSelect({
|
|
3000
|
+
customId: 'u_sel',
|
|
3001
|
+
@@ -75,7 +75,7 @@ describe('ComponentManager', () => {
|
|
3002
|
+
executedCtx = ctx
|
|
3003
|
+
}
|
|
3004
|
+
})
|
|
3005
|
+
- manager.register(sel)
|
|
3006
|
+
+ manager.register(sel as unknown)
|
|
3007
|
+
|
|
3008
|
+
const rawInteraction = {
|
|
3009
|
+
data: {
|
|
3010
|
+
@@ -99,8 +99,8 @@ describe('ComponentManager', () => {
|
|
3011
|
+
expect(executedCtx).toBeDefined()
|
|
3012
|
+
// Test values resolution mapping
|
|
3013
|
+
expect(executedCtx?.values).toHaveLength(1)
|
|
3014
|
+
- expect(executedCtx?.values[0].id).toBe('target1')
|
|
3015
|
+
- expect(executedCtx?.values[0].username).toBe('jane')
|
|
3016
|
+
+ expect(executedCtx?.values[0]?.id).toBe('target1')
|
|
3017
|
+
+ expect(executedCtx?.values[0]?.username).toBe('jane')
|
|
3018
|
+
|
|
3019
|
+
// Test that the user was hydrated into cache
|
|
3020
|
+
expect(client.cache.users.has('target1')).toBe(true)
|
|
3021
|
+
@@ -111,7 +111,7 @@ describe('ComponentManager', () => {
|
|
3022
|
+
const client = new Client({ token: 'test', intents: [] })
|
|
3023
|
+
const manager = new ComponentManager(client)
|
|
3024
|
+
|
|
3025
|
+
- let executedCtx: ComponentContext | undefined
|
|
3026
|
+
+ let executedCtx: ComponentContext<Partial<import('../src/types/channel/index.ts').Channel>[]> | undefined
|
|
3027
|
+
|
|
3028
|
+
const sel = defineChannelSelect({
|
|
3029
|
+
customId: 'c_sel',
|
|
3030
|
+
@@ -119,7 +119,7 @@ describe('ComponentManager', () => {
|
|
3031
|
+
executedCtx = ctx
|
|
3032
|
+
}
|
|
3033
|
+
})
|
|
3034
|
+
- manager.register(sel)
|
|
3035
|
+
+ manager.register(sel as unknown)
|
|
3036
|
+
|
|
3037
|
+
const rawInteraction = {
|
|
3038
|
+
data: {
|
|
3039
|
+
@@ -138,9 +138,9 @@ describe('ComponentManager', () => {
|
|
3040
|
+
|
|
3041
|
+
expect(executedCtx).toBeDefined()
|
|
3042
|
+
expect(executedCtx?.values).toHaveLength(2)
|
|
3043
|
+
- expect(executedCtx?.values[0].id).toBe('c1')
|
|
3044
|
+
- expect(executedCtx?.values[0].name).toBe('general')
|
|
3045
|
+
- expect(executedCtx?.values[1].id).toBe('unknown_channel') // Fallback to id-only object
|
|
3046
|
+
+ expect(executedCtx?.values[0]?.id).toBe('c1')
|
|
3047
|
+
+ expect(executedCtx?.values[0]?.name).toBe('general')
|
|
3048
|
+
+ expect(executedCtx?.values[1]?.id).toBe('unknown_channel') // Fallback to id-only object
|
|
3049
|
+
})
|
|
3050
|
+
|
|
3051
|
+
it('should catch handler errors', async () => {
|
|
3052
|
+
diff --git a/tests/managers.guild.test.ts b/tests/managers.guild.test.ts
|
|
3053
|
+
index 8c33c9d..2b9ab6b 100644
|
|
3054
|
+
--- a/tests/managers.guild.test.ts
|
|
3055
|
+
+++ b/tests/managers.guild.test.ts
|
|
3056
|
+
@@ -77,7 +77,7 @@ describe('GuildManager', () => {
|
|
3057
|
+
manager['rest'].delete = vi.fn().mockResolvedValue({ ok: true })
|
|
3058
|
+
|
|
3059
|
+
// Add fake member to cache to test deletion
|
|
3060
|
+
- client.cache.members.set('g1_u1', {} as any)
|
|
3061
|
+
+ client.cache.members.set('g1_u1', {} as unknown)
|
|
3062
|
+
|
|
3063
|
+
const res = await manager.kick('g1', 'u1', 'nga')
|
|
3064
|
+
|
|
3065
|
+
diff --git a/tests/managers.test.ts b/tests/managers.test.ts
|
|
3066
|
+
index a93309b..ef63a4c 100644
|
|
3067
|
+
--- a/tests/managers.test.ts
|
|
3068
|
+
+++ b/tests/managers.test.ts
|
|
3069
|
+
@@ -13,7 +13,7 @@ function mockRest(response: unknown) {
|
|
3070
|
+
put: vi.fn(),
|
|
3071
|
+
patch: vi.fn(),
|
|
3072
|
+
delete: vi.fn(),
|
|
3073
|
+
- } as any
|
|
3074
|
+
+ } as unknown as import('../src/rest/index.js').ChameleonREST
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
describe('Managers', () => {
|
|
3078
|
+
@@ -103,7 +103,7 @@ describe('Managers', () => {
|
|
3079
|
+
const store = new TongueStore()
|
|
3080
|
+
const rest = {
|
|
3081
|
+
get: vi.fn().mockResolvedValue({ ok: false, status: 404, message: 'Unknown User' }),
|
|
3082
|
+
- } as any
|
|
3083
|
+
+ } as unknown as import('../src/rest/index.js').ChameleonREST
|
|
3084
|
+
|
|
3085
|
+
const users = new UserManager(rest, store)
|
|
3086
|
+
const result = await users.fetch('999')
|