@furlow/discord 1.0.2 → 1.0.4

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.
@@ -22,6 +22,8 @@ declare class FurlowClient {
22
22
  * Auto-detect required intents from spec
23
23
  */
24
24
  private autoDetectIntents;
25
+ /** Default timeout for ready event (30 seconds) */
26
+ private static readonly READY_TIMEOUT_MS;
25
27
  /**
26
28
  * Start the client
27
29
  */
@@ -25,7 +25,7 @@ var INTENT_MAP = {
25
25
  auto_moderation_configuration: GatewayIntentBits.AutoModerationConfiguration,
26
26
  auto_moderation_execution: GatewayIntentBits.AutoModerationExecution
27
27
  };
28
- var FurlowClient = class {
28
+ var FurlowClient = class _FurlowClient {
29
29
  client;
30
30
  token;
31
31
  spec;
@@ -111,14 +111,22 @@ var FurlowClient = class {
111
111
  }
112
112
  return [...intents];
113
113
  }
114
+ /** Default timeout for ready event (30 seconds) */
115
+ static READY_TIMEOUT_MS = 3e4;
114
116
  /**
115
117
  * Start the client
116
118
  */
117
119
  async start() {
118
120
  await this.client.login(this.token);
119
121
  if (!this.client.isReady()) {
120
- await new Promise((resolve) => {
121
- this.client.once("ready", () => resolve());
122
+ await new Promise((resolve, reject) => {
123
+ const timeoutId = setTimeout(() => {
124
+ reject(new Error("Client ready timeout - Discord client did not become ready within 30 seconds"));
125
+ }, _FurlowClient.READY_TIMEOUT_MS);
126
+ this.client.once("ready", () => {
127
+ clearTimeout(timeoutId);
128
+ resolve();
129
+ });
122
130
  });
123
131
  }
124
132
  await this.applyIdentity();
@@ -139,13 +147,15 @@ var FurlowClient = class {
139
147
  if (identity.name && this.client.user?.username !== identity.name) {
140
148
  try {
141
149
  await this.client.user?.setUsername(identity.name);
142
- } catch {
150
+ } catch (err) {
151
+ console.warn("Failed to set bot username (may be rate limited):", err instanceof Error ? err.message : String(err));
143
152
  }
144
153
  }
145
154
  if (identity.avatar) {
146
155
  try {
147
156
  await this.client.user?.setAvatar(identity.avatar);
148
- } catch {
157
+ } catch (err) {
158
+ console.warn("Failed to set bot avatar (may be rate limited):", err instanceof Error ? err.message : String(err));
149
159
  }
150
160
  }
151
161
  }
@@ -155,17 +165,21 @@ var FurlowClient = class {
155
165
  async applyPresence() {
156
166
  if (!this.spec.presence) return;
157
167
  const presence = this.spec.presence;
158
- this.client.user?.setPresence({
159
- status: presence.status ?? "online",
160
- activities: presence.activity ? [
161
- {
162
- type: this.getActivityType(presence.activity.type),
163
- name: presence.activity.text,
164
- url: presence.activity.url,
165
- state: presence.activity.state
166
- }
167
- ] : void 0
168
- });
168
+ try {
169
+ this.client.user?.setPresence({
170
+ status: presence.status ?? "online",
171
+ activities: presence.activity ? [
172
+ {
173
+ type: this.getActivityType(presence.activity.type),
174
+ name: presence.activity.text,
175
+ url: presence.activity.url,
176
+ state: presence.activity.state
177
+ }
178
+ ] : void 0
179
+ });
180
+ } catch (err) {
181
+ console.error("Failed to set presence:", err);
182
+ }
169
183
  }
170
184
  /**
171
185
  * Get Discord.js activity type
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/client/index.ts"],"sourcesContent":["/**\n * Discord client wrapper\n */\n\nimport {\n Client,\n GatewayIntentBits,\n Partials,\n type ClientOptions,\n type ClientEvents,\n} from 'discord.js';\nimport type { FurlowSpec, IntentsConfig, GatewayConfig, Identity, Presence } from '@furlow/schema';\n\nexport interface FurlowClientOptions {\n token: string;\n spec: FurlowSpec;\n}\n\nconst INTENT_MAP: Record<string, GatewayIntentBits> = {\n guilds: GatewayIntentBits.Guilds,\n guild_members: GatewayIntentBits.GuildMembers,\n guild_moderation: GatewayIntentBits.GuildModeration,\n guild_emojis_and_stickers: GatewayIntentBits.GuildEmojisAndStickers,\n guild_integrations: GatewayIntentBits.GuildIntegrations,\n guild_webhooks: GatewayIntentBits.GuildWebhooks,\n guild_invites: GatewayIntentBits.GuildInvites,\n guild_voice_states: GatewayIntentBits.GuildVoiceStates,\n guild_presences: GatewayIntentBits.GuildPresences,\n guild_messages: GatewayIntentBits.GuildMessages,\n guild_message_reactions: GatewayIntentBits.GuildMessageReactions,\n guild_message_typing: GatewayIntentBits.GuildMessageTyping,\n direct_messages: GatewayIntentBits.DirectMessages,\n direct_message_reactions: GatewayIntentBits.DirectMessageReactions,\n direct_message_typing: GatewayIntentBits.DirectMessageTyping,\n message_content: GatewayIntentBits.MessageContent,\n guild_scheduled_events: GatewayIntentBits.GuildScheduledEvents,\n auto_moderation_configuration: GatewayIntentBits.AutoModerationConfiguration,\n auto_moderation_execution: GatewayIntentBits.AutoModerationExecution,\n};\n\nexport class FurlowClient {\n private client: Client;\n private token: string;\n private spec: FurlowSpec;\n\n constructor(options: FurlowClientOptions) {\n this.token = options.token;\n this.spec = options.spec;\n\n const intents = this.resolveIntents(options.spec.intents);\n\n const clientOptions: ClientOptions = {\n intents,\n partials: [\n Partials.Message,\n Partials.Channel,\n Partials.Reaction,\n Partials.User,\n Partials.GuildMember,\n ],\n };\n\n this.client = new Client(clientOptions);\n }\n\n /**\n * Resolve intents from spec\n */\n private resolveIntents(config?: IntentsConfig): GatewayIntentBits[] {\n if (!config) {\n // Default intents\n return [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.GuildMembers,\n GatewayIntentBits.MessageContent,\n ];\n }\n\n if (config.auto) {\n // Auto-detect required intents from spec\n return this.autoDetectIntents();\n }\n\n if (config.explicit) {\n return config.explicit\n .map((intent) => INTENT_MAP[intent])\n .filter((i): i is GatewayIntentBits => i !== undefined);\n }\n\n return [GatewayIntentBits.Guilds];\n }\n\n /**\n * Auto-detect required intents from spec\n */\n private autoDetectIntents(): GatewayIntentBits[] {\n const intents = new Set<GatewayIntentBits>();\n\n // Always need Guilds\n intents.add(GatewayIntentBits.Guilds);\n\n // Check events\n if (this.spec.events) {\n for (const handler of this.spec.events) {\n switch (handler.event) {\n case 'message_create':\n case 'message':\n case 'message_update':\n case 'message_delete':\n intents.add(GatewayIntentBits.GuildMessages);\n intents.add(GatewayIntentBits.MessageContent);\n break;\n case 'guild_member_add':\n case 'guild_member_remove':\n case 'guild_member_update':\n case 'member_join':\n case 'member_leave':\n intents.add(GatewayIntentBits.GuildMembers);\n break;\n case 'voice_state_update':\n case 'voice_join':\n case 'voice_leave':\n intents.add(GatewayIntentBits.GuildVoiceStates);\n break;\n case 'message_reaction_add':\n case 'message_reaction_remove':\n intents.add(GatewayIntentBits.GuildMessageReactions);\n break;\n case 'presence_update':\n intents.add(GatewayIntentBits.GuildPresences);\n break;\n }\n }\n }\n\n // Check if commands exist\n if (this.spec.commands?.length) {\n intents.add(GatewayIntentBits.GuildMessages);\n }\n\n // Check if voice features are used\n if (this.spec.voice) {\n intents.add(GatewayIntentBits.GuildVoiceStates);\n }\n\n return [...intents];\n }\n\n /**\n * Start the client\n */\n async start(): Promise<void> {\n await this.client.login(this.token);\n\n // Wait for ready\n if (!this.client.isReady()) {\n await new Promise<void>((resolve) => {\n this.client.once('ready', () => resolve());\n });\n }\n\n // Apply identity\n await this.applyIdentity();\n\n // Apply presence\n await this.applyPresence();\n }\n\n /**\n * Stop the client\n */\n async stop(): Promise<void> {\n await this.client.destroy();\n }\n\n /**\n * Apply bot identity\n */\n private async applyIdentity(): Promise<void> {\n if (!this.spec.identity) return;\n\n const identity = this.spec.identity;\n\n // Set username if different\n if (identity.name && this.client.user?.username !== identity.name) {\n try {\n await this.client.user?.setUsername(identity.name);\n } catch {\n // Username changes are rate limited\n }\n }\n\n // Set avatar\n if (identity.avatar) {\n try {\n await this.client.user?.setAvatar(identity.avatar);\n } catch {\n // Avatar might be rate limited\n }\n }\n }\n\n /**\n * Apply presence\n */\n private async applyPresence(): Promise<void> {\n if (!this.spec.presence) return;\n\n const presence = this.spec.presence;\n\n this.client.user?.setPresence({\n status: presence.status ?? 'online',\n activities: presence.activity\n ? [\n {\n type: this.getActivityType(presence.activity.type),\n name: presence.activity.text,\n url: presence.activity.url,\n state: presence.activity.state,\n },\n ]\n : undefined,\n });\n }\n\n /**\n * Get Discord.js activity type\n */\n private getActivityType(type: string): number {\n const types: Record<string, number> = {\n playing: 0,\n streaming: 1,\n listening: 2,\n watching: 3,\n custom: 4,\n competing: 5,\n };\n return types[type] ?? 0;\n }\n\n /**\n * Register event listener\n */\n on<K extends keyof ClientEvents>(\n event: K,\n listener: (...args: ClientEvents[K]) => void\n ): this {\n this.client.on(event, listener);\n return this;\n }\n\n /**\n * Register one-time event listener\n */\n once<K extends keyof ClientEvents>(\n event: K,\n listener: (...args: ClientEvents[K]) => void\n ): this {\n this.client.once(event, listener);\n return this;\n }\n\n /**\n * Get the underlying Discord.js client\n */\n getClient(): Client {\n return this.client;\n }\n\n /**\n * Get the spec\n */\n getSpec(): FurlowSpec {\n return this.spec;\n }\n\n /**\n * Check if client is ready\n */\n isReady(): boolean {\n return this.client.isReady();\n }\n\n /**\n * Get guild count\n */\n get guildCount(): number {\n return this.client.guilds.cache.size;\n }\n\n /**\n * Get user count (approximate)\n */\n get userCount(): number {\n return this.client.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0);\n }\n}\n\n/**\n * Create a FURLOW client\n */\nexport function createClient(options: FurlowClientOptions): FurlowClient {\n return new FurlowClient(options);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/client/index.ts"],"sourcesContent":["/**\n * Discord client wrapper\n */\n\nimport {\n Client,\n GatewayIntentBits,\n Partials,\n type ClientOptions,\n type ClientEvents,\n} from 'discord.js';\nimport type { FurlowSpec, IntentsConfig, GatewayConfig, Identity, Presence } from '@furlow/schema';\n\nexport interface FurlowClientOptions {\n token: string;\n spec: FurlowSpec;\n}\n\nconst INTENT_MAP: Record<string, GatewayIntentBits> = {\n guilds: GatewayIntentBits.Guilds,\n guild_members: GatewayIntentBits.GuildMembers,\n guild_moderation: GatewayIntentBits.GuildModeration,\n guild_emojis_and_stickers: GatewayIntentBits.GuildEmojisAndStickers,\n guild_integrations: GatewayIntentBits.GuildIntegrations,\n guild_webhooks: GatewayIntentBits.GuildWebhooks,\n guild_invites: GatewayIntentBits.GuildInvites,\n guild_voice_states: GatewayIntentBits.GuildVoiceStates,\n guild_presences: GatewayIntentBits.GuildPresences,\n guild_messages: GatewayIntentBits.GuildMessages,\n guild_message_reactions: GatewayIntentBits.GuildMessageReactions,\n guild_message_typing: GatewayIntentBits.GuildMessageTyping,\n direct_messages: GatewayIntentBits.DirectMessages,\n direct_message_reactions: GatewayIntentBits.DirectMessageReactions,\n direct_message_typing: GatewayIntentBits.DirectMessageTyping,\n message_content: GatewayIntentBits.MessageContent,\n guild_scheduled_events: GatewayIntentBits.GuildScheduledEvents,\n auto_moderation_configuration: GatewayIntentBits.AutoModerationConfiguration,\n auto_moderation_execution: GatewayIntentBits.AutoModerationExecution,\n};\n\nexport class FurlowClient {\n private client: Client;\n private token: string;\n private spec: FurlowSpec;\n\n constructor(options: FurlowClientOptions) {\n this.token = options.token;\n this.spec = options.spec;\n\n const intents = this.resolveIntents(options.spec.intents);\n\n const clientOptions: ClientOptions = {\n intents,\n partials: [\n Partials.Message,\n Partials.Channel,\n Partials.Reaction,\n Partials.User,\n Partials.GuildMember,\n ],\n };\n\n this.client = new Client(clientOptions);\n }\n\n /**\n * Resolve intents from spec\n */\n private resolveIntents(config?: IntentsConfig): GatewayIntentBits[] {\n if (!config) {\n // Default intents\n return [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.GuildMembers,\n GatewayIntentBits.MessageContent,\n ];\n }\n\n if (config.auto) {\n // Auto-detect required intents from spec\n return this.autoDetectIntents();\n }\n\n if (config.explicit) {\n return config.explicit\n .map((intent) => INTENT_MAP[intent])\n .filter((i): i is GatewayIntentBits => i !== undefined);\n }\n\n return [GatewayIntentBits.Guilds];\n }\n\n /**\n * Auto-detect required intents from spec\n */\n private autoDetectIntents(): GatewayIntentBits[] {\n const intents = new Set<GatewayIntentBits>();\n\n // Always need Guilds\n intents.add(GatewayIntentBits.Guilds);\n\n // Check events\n if (this.spec.events) {\n for (const handler of this.spec.events) {\n switch (handler.event) {\n case 'message_create':\n case 'message':\n case 'message_update':\n case 'message_delete':\n intents.add(GatewayIntentBits.GuildMessages);\n intents.add(GatewayIntentBits.MessageContent);\n break;\n case 'guild_member_add':\n case 'guild_member_remove':\n case 'guild_member_update':\n case 'member_join':\n case 'member_leave':\n intents.add(GatewayIntentBits.GuildMembers);\n break;\n case 'voice_state_update':\n case 'voice_join':\n case 'voice_leave':\n intents.add(GatewayIntentBits.GuildVoiceStates);\n break;\n case 'message_reaction_add':\n case 'message_reaction_remove':\n intents.add(GatewayIntentBits.GuildMessageReactions);\n break;\n case 'presence_update':\n intents.add(GatewayIntentBits.GuildPresences);\n break;\n }\n }\n }\n\n // Check if commands exist\n if (this.spec.commands?.length) {\n intents.add(GatewayIntentBits.GuildMessages);\n }\n\n // Check if voice features are used\n if (this.spec.voice) {\n intents.add(GatewayIntentBits.GuildVoiceStates);\n }\n\n return [...intents];\n }\n\n /** Default timeout for ready event (30 seconds) */\n private static readonly READY_TIMEOUT_MS = 30000;\n\n /**\n * Start the client\n */\n async start(): Promise<void> {\n await this.client.login(this.token);\n\n // Wait for ready with timeout\n if (!this.client.isReady()) {\n await new Promise<void>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n reject(new Error('Client ready timeout - Discord client did not become ready within 30 seconds'));\n }, FurlowClient.READY_TIMEOUT_MS);\n\n this.client.once('ready', () => {\n clearTimeout(timeoutId);\n resolve();\n });\n });\n }\n\n // Apply identity\n await this.applyIdentity();\n\n // Apply presence\n await this.applyPresence();\n }\n\n /**\n * Stop the client\n */\n async stop(): Promise<void> {\n await this.client.destroy();\n }\n\n /**\n * Apply bot identity\n */\n private async applyIdentity(): Promise<void> {\n if (!this.spec.identity) return;\n\n const identity = this.spec.identity;\n\n // Set username if different\n if (identity.name && this.client.user?.username !== identity.name) {\n try {\n await this.client.user?.setUsername(identity.name);\n } catch (err) {\n // Username changes are rate limited - log but don't fail startup\n console.warn('Failed to set bot username (may be rate limited):', err instanceof Error ? err.message : String(err));\n }\n }\n\n // Set avatar\n if (identity.avatar) {\n try {\n await this.client.user?.setAvatar(identity.avatar);\n } catch (err) {\n // Avatar might be rate limited - log but don't fail startup\n console.warn('Failed to set bot avatar (may be rate limited):', err instanceof Error ? err.message : String(err));\n }\n }\n }\n\n /**\n * Apply presence\n */\n private async applyPresence(): Promise<void> {\n if (!this.spec.presence) return;\n\n const presence = this.spec.presence;\n\n try {\n this.client.user?.setPresence({\n status: presence.status ?? 'online',\n activities: presence.activity\n ? [\n {\n type: this.getActivityType(presence.activity.type),\n name: presence.activity.text,\n url: presence.activity.url,\n state: presence.activity.state,\n },\n ]\n : undefined,\n });\n } catch (err) {\n console.error('Failed to set presence:', err);\n }\n }\n\n /**\n * Get Discord.js activity type\n */\n private getActivityType(type: string): number {\n const types: Record<string, number> = {\n playing: 0,\n streaming: 1,\n listening: 2,\n watching: 3,\n custom: 4,\n competing: 5,\n };\n return types[type] ?? 0;\n }\n\n /**\n * Register event listener\n */\n on<K extends keyof ClientEvents>(\n event: K,\n listener: (...args: ClientEvents[K]) => void\n ): this {\n this.client.on(event, listener);\n return this;\n }\n\n /**\n * Register one-time event listener\n */\n once<K extends keyof ClientEvents>(\n event: K,\n listener: (...args: ClientEvents[K]) => void\n ): this {\n this.client.once(event, listener);\n return this;\n }\n\n /**\n * Get the underlying Discord.js client\n */\n getClient(): Client {\n return this.client;\n }\n\n /**\n * Get the spec\n */\n getSpec(): FurlowSpec {\n return this.spec;\n }\n\n /**\n * Check if client is ready\n */\n isReady(): boolean {\n return this.client.isReady();\n }\n\n /**\n * Get guild count\n */\n get guildCount(): number {\n return this.client.guilds.cache.size;\n }\n\n /**\n * Get user count (approximate)\n */\n get userCount(): number {\n return this.client.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0);\n }\n}\n\n/**\n * Create a FURLOW client\n */\nexport function createClient(options: FurlowClientOptions): FurlowClient {\n return new FurlowClient(options);\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@ var INTENT_MAP = {
25
25
  auto_moderation_configuration: GatewayIntentBits.AutoModerationConfiguration,
26
26
  auto_moderation_execution: GatewayIntentBits.AutoModerationExecution
27
27
  };
28
- var FurlowClient = class {
28
+ var FurlowClient = class _FurlowClient {
29
29
  client;
30
30
  token;
31
31
  spec;
@@ -111,14 +111,22 @@ var FurlowClient = class {
111
111
  }
112
112
  return [...intents];
113
113
  }
114
+ /** Default timeout for ready event (30 seconds) */
115
+ static READY_TIMEOUT_MS = 3e4;
114
116
  /**
115
117
  * Start the client
116
118
  */
117
119
  async start() {
118
120
  await this.client.login(this.token);
119
121
  if (!this.client.isReady()) {
120
- await new Promise((resolve) => {
121
- this.client.once("ready", () => resolve());
122
+ await new Promise((resolve, reject) => {
123
+ const timeoutId = setTimeout(() => {
124
+ reject(new Error("Client ready timeout - Discord client did not become ready within 30 seconds"));
125
+ }, _FurlowClient.READY_TIMEOUT_MS);
126
+ this.client.once("ready", () => {
127
+ clearTimeout(timeoutId);
128
+ resolve();
129
+ });
122
130
  });
123
131
  }
124
132
  await this.applyIdentity();
@@ -139,13 +147,15 @@ var FurlowClient = class {
139
147
  if (identity.name && this.client.user?.username !== identity.name) {
140
148
  try {
141
149
  await this.client.user?.setUsername(identity.name);
142
- } catch {
150
+ } catch (err) {
151
+ console.warn("Failed to set bot username (may be rate limited):", err instanceof Error ? err.message : String(err));
143
152
  }
144
153
  }
145
154
  if (identity.avatar) {
146
155
  try {
147
156
  await this.client.user?.setAvatar(identity.avatar);
148
- } catch {
157
+ } catch (err) {
158
+ console.warn("Failed to set bot avatar (may be rate limited):", err instanceof Error ? err.message : String(err));
149
159
  }
150
160
  }
151
161
  }
@@ -155,17 +165,21 @@ var FurlowClient = class {
155
165
  async applyPresence() {
156
166
  if (!this.spec.presence) return;
157
167
  const presence = this.spec.presence;
158
- this.client.user?.setPresence({
159
- status: presence.status ?? "online",
160
- activities: presence.activity ? [
161
- {
162
- type: this.getActivityType(presence.activity.type),
163
- name: presence.activity.text,
164
- url: presence.activity.url,
165
- state: presence.activity.state
166
- }
167
- ] : void 0
168
- });
168
+ try {
169
+ this.client.user?.setPresence({
170
+ status: presence.status ?? "online",
171
+ activities: presence.activity ? [
172
+ {
173
+ type: this.getActivityType(presence.activity.type),
174
+ name: presence.activity.text,
175
+ url: presence.activity.url,
176
+ state: presence.activity.state
177
+ }
178
+ ] : void 0
179
+ });
180
+ } catch (err) {
181
+ console.error("Failed to set presence:", err);
182
+ }
169
183
  }
170
184
  /**
171
185
  * Get Discord.js activity type
@@ -374,6 +388,7 @@ var InteractionHandler = class {
374
388
  modalHandlers = /* @__PURE__ */ new Map();
375
389
  userContextHandlers = /* @__PURE__ */ new Map();
376
390
  messageContextHandlers = /* @__PURE__ */ new Map();
391
+ autocompleteHandlers = /* @__PURE__ */ new Map();
377
392
  constructor(options) {
378
393
  this.client = options.client;
379
394
  this.clientId = options.clientId;
@@ -394,7 +409,8 @@ var InteractionHandler = class {
394
409
  await interaction.reply({
395
410
  content: "An error occurred while processing this interaction.",
396
411
  flags: MessageFlags.Ephemeral
397
- }).catch(() => {
412
+ }).catch((replyError) => {
413
+ console.warn("Failed to send error reply to interaction:", replyError instanceof Error ? replyError.message : String(replyError));
398
414
  });
399
415
  }
400
416
  }
@@ -434,6 +450,13 @@ var InteractionHandler = class {
434
450
  if (handler) {
435
451
  await handler(interaction);
436
452
  }
453
+ } else if (interaction.isAutocomplete()) {
454
+ const focusedOption = interaction.options.getFocused(true);
455
+ const specificKey = `${interaction.commandName}:${focusedOption.name}`;
456
+ const handler = this.autocompleteHandlers.get(specificKey) ?? this.autocompleteHandlers.get(interaction.commandName) ?? this.findPrefixHandler(this.autocompleteHandlers, specificKey);
457
+ if (handler) {
458
+ await handler(interaction);
459
+ }
437
460
  }
438
461
  }
439
462
  /**
@@ -483,6 +506,14 @@ var InteractionHandler = class {
483
506
  onMessageContext(name, handler) {
484
507
  this.messageContextHandlers.set(name, handler);
485
508
  }
509
+ /**
510
+ * Register an autocomplete handler
511
+ * @param nameOrKey Command name, or "command:option" for option-specific handler
512
+ * @param handler The autocomplete handler
513
+ */
514
+ onAutocomplete(nameOrKey, handler) {
515
+ this.autocompleteHandlers.set(nameOrKey, handler);
516
+ }
486
517
  /**
487
518
  * Register slash commands with Discord
488
519
  */
@@ -557,8 +588,10 @@ var InteractionHandler = class {
557
588
  addOption(builder, opt) {
558
589
  const addMethod = `add${this.getOptionMethodName(opt.type)}Option`;
559
590
  if (typeof builder[addMethod] !== "function") {
560
- console.warn(`Unknown option type: ${opt.type}`);
561
- return;
591
+ const validTypes = ["string", "integer", "number", "boolean", "user", "channel", "role", "mentionable", "attachment"];
592
+ throw new Error(
593
+ `Unknown command option type: "${opt.type}" for option "${opt.name}". Valid types are: ${validTypes.join(", ")}`
594
+ );
562
595
  }
563
596
  builder[addMethod]((optBuilder) => {
564
597
  optBuilder.setName(opt.name).setDescription(opt.description);
@@ -625,8 +658,10 @@ import {
625
658
  AudioPlayerStatus,
626
659
  VoiceConnectionStatus,
627
660
  entersState,
628
- getVoiceConnection
661
+ getVoiceConnection,
662
+ StreamType
629
663
  } from "@discordjs/voice";
664
+ import { FFmpeg } from "prism-media";
630
665
  var FILTER_ARGS = {
631
666
  bassboost: ["-af", "bass=g=10,dynaudnorm=f=200"],
632
667
  nightcore: ["-af", "asetrate=44100*1.25,aresample=44100,atempo=1.06"],
@@ -664,14 +699,23 @@ var VoiceManager = class {
664
699
  selfDeaf: options.selfDeaf ?? this.config.connection?.self_deaf ?? true,
665
700
  selfMute: options.selfMute ?? this.config.connection?.self_mute ?? false
666
701
  });
667
- await entersState(connection, VoiceConnectionStatus.Ready, 3e4);
702
+ try {
703
+ await entersState(connection, VoiceConnectionStatus.Ready, 3e4);
704
+ } catch (err) {
705
+ connection.destroy();
706
+ throw new Error(`Failed to connect to voice channel: ${err instanceof Error ? err.message : String(err)}`);
707
+ }
668
708
  const player = createAudioPlayer();
669
709
  player.on(AudioPlayerStatus.Idle, () => {
670
- this.handleTrackEnd(guildId);
710
+ this.handleTrackEnd(guildId).catch((err) => {
711
+ console.error(`Error handling track end in ${guildId}:`, err);
712
+ });
671
713
  });
672
714
  player.on("error", (error) => {
673
715
  console.error(`Audio player error in ${guildId}:`, error);
674
- this.handleTrackEnd(guildId);
716
+ this.handleTrackEnd(guildId).catch((err) => {
717
+ console.error(`Error handling track end after player error in ${guildId}:`, err);
718
+ });
675
719
  });
676
720
  connection.subscribe(player);
677
721
  this.guildStates.set(guildId, {
@@ -708,18 +752,45 @@ var VoiceManager = class {
708
752
  if (!state) {
709
753
  throw new Error("Not connected to voice in this guild");
710
754
  }
711
- const ffmpegArgs = ["-analyzeduration", "0"];
712
- if (options.seek && options.seek > 0) {
713
- ffmpegArgs.push("-ss", String(options.seek / 1e3));
714
- }
715
- if (state.filters.size > 0) {
716
- const filterArgs = this.buildFilterArgs(state.filters);
717
- ffmpegArgs.push(...filterArgs);
755
+ const hasFilters = state.filters.size > 0;
756
+ const hasSeek = options.seek && options.seek > 0;
757
+ let resource;
758
+ if (hasFilters || hasSeek) {
759
+ const ffmpegArgs = [
760
+ "-analyzeduration",
761
+ "0",
762
+ "-loglevel",
763
+ "0",
764
+ "-i",
765
+ source
766
+ ];
767
+ if (hasSeek) {
768
+ const inputIndex = ffmpegArgs.indexOf("-i");
769
+ ffmpegArgs.splice(inputIndex, 0, "-ss", String(options.seek / 1e3));
770
+ }
771
+ if (hasFilters) {
772
+ const filterArgs = this.buildFilterArgs(state.filters);
773
+ ffmpegArgs.push(...filterArgs);
774
+ }
775
+ ffmpegArgs.push(
776
+ "-f",
777
+ "s16le",
778
+ "-ar",
779
+ "48000",
780
+ "-ac",
781
+ "2",
782
+ "pipe:1"
783
+ );
784
+ const ffmpegStream = new FFmpeg({ args: ffmpegArgs });
785
+ resource = createAudioResource(ffmpegStream, {
786
+ inputType: StreamType.Raw,
787
+ inlineVolume: true
788
+ });
789
+ } else {
790
+ resource = createAudioResource(source, {
791
+ inlineVolume: true
792
+ });
718
793
  }
719
- const resource = createAudioResource(source, {
720
- inlineVolume: true,
721
- inputType: ffmpegArgs.length > 2 ? void 0 : void 0
722
- });
723
794
  const volume = options.volume ?? state.volume;
724
795
  resource.volume?.setVolume(volume / 100);
725
796
  state.currentResource = resource;
@@ -1104,7 +1175,9 @@ var VideoManager = class {
1104
1175
  */
1105
1176
  setupListener() {
1106
1177
  this.client.on("voiceStateUpdate", (oldState, newState) => {
1107
- this.handleVoiceStateUpdate(oldState, newState);
1178
+ this.handleVoiceStateUpdate(oldState, newState).catch((err) => {
1179
+ console.error("Error handling voice state update for stream detection:", err);
1180
+ });
1108
1181
  });
1109
1182
  }
1110
1183
  /**