@impulsedev/chameleon 2.0.0 → 3.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/diff.txt DELETED
@@ -1,3086 +0,0 @@
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')