@furlow/discord 0.2.0 → 1.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/dist/index.d.ts +1 -1
- package/dist/index.js +209 -2
- package/dist/index.js.map +1 -1
- package/dist/voice/index.d.ts +54 -2
- package/dist/voice/index.js +209 -2
- package/dist/voice/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { FurlowClient, FurlowClientOptions, createClient } from './client/index.js';
|
|
2
2
|
export { GatewayManager, GatewayManagerOptions, createGatewayManager } from './gateway/index.js';
|
|
3
3
|
export { InteractionCallback, InteractionHandler, InteractionHandlerOptions, createInteractionHandler } from './interactions/index.js';
|
|
4
|
-
export { AudioFilter, GuildVoiceState, QueueItem, QueueLoopMode, VoiceConfig, VoiceManager, createVoiceManager } from './voice/index.js';
|
|
4
|
+
export { AudioFilter, GuildVoiceState, QueueItem, QueueLoopMode, SearchResult, VoiceConfig, VoiceManager, createVoiceManager } from './voice/index.js';
|
|
5
5
|
export { StreamEvent, StreamEventCallback, StreamInfo, VideoManager, createVideoManager } from './video/index.js';
|
|
6
6
|
import 'discord.js';
|
|
7
7
|
import '@furlow/schema';
|
package/dist/index.js
CHANGED
|
@@ -626,6 +626,18 @@ import {
|
|
|
626
626
|
entersState,
|
|
627
627
|
getVoiceConnection
|
|
628
628
|
} from "@discordjs/voice";
|
|
629
|
+
var FILTER_ARGS = {
|
|
630
|
+
bassboost: ["-af", "bass=g=10,dynaudnorm=f=200"],
|
|
631
|
+
nightcore: ["-af", "asetrate=44100*1.25,aresample=44100,atempo=1.06"],
|
|
632
|
+
vaporwave: ["-af", "asetrate=44100*0.8,aresample=44100,atempo=0.9"],
|
|
633
|
+
"8d": ["-af", "apulsator=hz=0.08"],
|
|
634
|
+
treble: ["-af", "treble=g=5,dynaudnorm=f=200"],
|
|
635
|
+
normalizer: ["-af", "dynaudnorm=f=200:g=3"],
|
|
636
|
+
karaoke: ["-af", "stereotools=mlev=0.03"],
|
|
637
|
+
tremolo: ["-af", "tremolo=f=6:d=0.5"],
|
|
638
|
+
vibrato: ["-af", "vibrato=f=6:d=0.5"],
|
|
639
|
+
reverse: ["-af", "areverse"]
|
|
640
|
+
};
|
|
629
641
|
var VoiceManager = class {
|
|
630
642
|
guildStates = /* @__PURE__ */ new Map();
|
|
631
643
|
config = {};
|
|
@@ -666,10 +678,13 @@ var VoiceManager = class {
|
|
|
666
678
|
player,
|
|
667
679
|
queue: [],
|
|
668
680
|
currentTrack: null,
|
|
681
|
+
currentResource: null,
|
|
669
682
|
volume: this.config.default_volume ?? 100,
|
|
670
683
|
loopMode: this.config.default_loop ?? "off",
|
|
671
684
|
filters: /* @__PURE__ */ new Set(),
|
|
672
|
-
paused: false
|
|
685
|
+
paused: false,
|
|
686
|
+
startTime: 0,
|
|
687
|
+
pausedAt: 0
|
|
673
688
|
});
|
|
674
689
|
return connection;
|
|
675
690
|
}
|
|
@@ -692,14 +707,44 @@ var VoiceManager = class {
|
|
|
692
707
|
if (!state) {
|
|
693
708
|
throw new Error("Not connected to voice in this guild");
|
|
694
709
|
}
|
|
710
|
+
const ffmpegArgs = ["-analyzeduration", "0"];
|
|
711
|
+
if (options.seek && options.seek > 0) {
|
|
712
|
+
ffmpegArgs.push("-ss", String(options.seek / 1e3));
|
|
713
|
+
}
|
|
714
|
+
if (state.filters.size > 0) {
|
|
715
|
+
const filterArgs = this.buildFilterArgs(state.filters);
|
|
716
|
+
ffmpegArgs.push(...filterArgs);
|
|
717
|
+
}
|
|
695
718
|
const resource = createAudioResource(source, {
|
|
696
|
-
inlineVolume: true
|
|
719
|
+
inlineVolume: true,
|
|
720
|
+
inputType: ffmpegArgs.length > 2 ? void 0 : void 0
|
|
697
721
|
});
|
|
698
722
|
const volume = options.volume ?? state.volume;
|
|
699
723
|
resource.volume?.setVolume(volume / 100);
|
|
724
|
+
state.currentResource = resource;
|
|
725
|
+
state.startTime = Date.now() - (options.seek ?? 0);
|
|
726
|
+
state.pausedAt = 0;
|
|
700
727
|
state.player.play(resource);
|
|
701
728
|
state.paused = false;
|
|
702
729
|
}
|
|
730
|
+
/**
|
|
731
|
+
* Build FFmpeg filter arguments from active filters
|
|
732
|
+
*/
|
|
733
|
+
buildFilterArgs(filters) {
|
|
734
|
+
if (filters.size === 0) return [];
|
|
735
|
+
const filterStrings = [];
|
|
736
|
+
for (const filter of filters) {
|
|
737
|
+
const args = FILTER_ARGS[filter];
|
|
738
|
+
if (args) {
|
|
739
|
+
const afIndex = args.indexOf("-af");
|
|
740
|
+
if (afIndex !== -1 && args[afIndex + 1]) {
|
|
741
|
+
filterStrings.push(args[afIndex + 1]);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
if (filterStrings.length === 0) return [];
|
|
746
|
+
return ["-af", filterStrings.join(",")];
|
|
747
|
+
}
|
|
703
748
|
/**
|
|
704
749
|
* Add a track to the queue
|
|
705
750
|
*/
|
|
@@ -772,6 +817,7 @@ var VoiceManager = class {
|
|
|
772
817
|
if (!state) return false;
|
|
773
818
|
state.player.pause();
|
|
774
819
|
state.paused = true;
|
|
820
|
+
state.pausedAt = Date.now();
|
|
775
821
|
return true;
|
|
776
822
|
}
|
|
777
823
|
/**
|
|
@@ -780,8 +826,13 @@ var VoiceManager = class {
|
|
|
780
826
|
resume(guildId) {
|
|
781
827
|
const state = this.guildStates.get(guildId);
|
|
782
828
|
if (!state) return false;
|
|
829
|
+
if (state.pausedAt > 0) {
|
|
830
|
+
const pauseDuration = Date.now() - state.pausedAt;
|
|
831
|
+
state.startTime += pauseDuration;
|
|
832
|
+
}
|
|
783
833
|
state.player.unpause();
|
|
784
834
|
state.paused = false;
|
|
835
|
+
state.pausedAt = 0;
|
|
785
836
|
return true;
|
|
786
837
|
}
|
|
787
838
|
/**
|
|
@@ -812,6 +863,162 @@ var VoiceManager = class {
|
|
|
812
863
|
if (!state) return;
|
|
813
864
|
state.loopMode = mode;
|
|
814
865
|
}
|
|
866
|
+
/**
|
|
867
|
+
* Seek to a position in the current track
|
|
868
|
+
*/
|
|
869
|
+
async seek(guildId, position) {
|
|
870
|
+
const state = this.guildStates.get(guildId);
|
|
871
|
+
if (!state || !state.currentTrack) return false;
|
|
872
|
+
await this.play(guildId, state.currentTrack.url, {
|
|
873
|
+
seek: position,
|
|
874
|
+
volume: state.volume
|
|
875
|
+
});
|
|
876
|
+
return true;
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Get current playback position in milliseconds
|
|
880
|
+
*/
|
|
881
|
+
getPlaybackPosition(guildId) {
|
|
882
|
+
const state = this.guildStates.get(guildId);
|
|
883
|
+
if (!state) return 0;
|
|
884
|
+
if (state.paused && state.pausedAt > 0) {
|
|
885
|
+
return state.pausedAt - state.startTime;
|
|
886
|
+
}
|
|
887
|
+
if (state.startTime > 0) {
|
|
888
|
+
return Date.now() - state.startTime;
|
|
889
|
+
}
|
|
890
|
+
return 0;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Set an audio filter
|
|
894
|
+
*/
|
|
895
|
+
async setFilter(guildId, filter, enabled) {
|
|
896
|
+
const state = this.guildStates.get(guildId);
|
|
897
|
+
if (!state) return false;
|
|
898
|
+
const hadFilter = state.filters.has(filter);
|
|
899
|
+
if (enabled) {
|
|
900
|
+
state.filters.add(filter);
|
|
901
|
+
} else {
|
|
902
|
+
state.filters.delete(filter);
|
|
903
|
+
}
|
|
904
|
+
if (hadFilter !== enabled && state.currentTrack) {
|
|
905
|
+
const currentPosition = this.getPlaybackPosition(guildId);
|
|
906
|
+
await this.play(guildId, state.currentTrack.url, {
|
|
907
|
+
seek: currentPosition,
|
|
908
|
+
volume: state.volume
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
return true;
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Get active filters
|
|
915
|
+
*/
|
|
916
|
+
getFilters(guildId) {
|
|
917
|
+
const state = this.guildStates.get(guildId);
|
|
918
|
+
if (!state) return [];
|
|
919
|
+
return [...state.filters];
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Clear all filters
|
|
923
|
+
*/
|
|
924
|
+
async clearFilters(guildId) {
|
|
925
|
+
const state = this.guildStates.get(guildId);
|
|
926
|
+
if (!state) return false;
|
|
927
|
+
if (state.filters.size === 0) return true;
|
|
928
|
+
state.filters.clear();
|
|
929
|
+
if (state.currentTrack) {
|
|
930
|
+
const currentPosition = this.getPlaybackPosition(guildId);
|
|
931
|
+
await this.play(guildId, state.currentTrack.url, {
|
|
932
|
+
seek: currentPosition,
|
|
933
|
+
volume: state.volume
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
return true;
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Search for music tracks
|
|
940
|
+
* This provides a basic implementation that handles URLs directly
|
|
941
|
+
* For full search functionality, integrate with youtube-sr, play-dl, or similar
|
|
942
|
+
*/
|
|
943
|
+
async search(query, options = {}) {
|
|
944
|
+
const limit = options.limit ?? 5;
|
|
945
|
+
if (this.isUrl(query)) {
|
|
946
|
+
return [{
|
|
947
|
+
url: query,
|
|
948
|
+
title: this.extractTitleFromUrl(query),
|
|
949
|
+
duration: 0,
|
|
950
|
+
thumbnail: null
|
|
951
|
+
}];
|
|
952
|
+
}
|
|
953
|
+
try {
|
|
954
|
+
const playDl = await import(
|
|
955
|
+
/* webpackIgnore: true */
|
|
956
|
+
"play-dl"
|
|
957
|
+
).catch(() => null);
|
|
958
|
+
if (playDl) {
|
|
959
|
+
const results = await playDl.search(query, { limit, source: { youtube: "video" } });
|
|
960
|
+
return results.map((result) => ({
|
|
961
|
+
url: result.url,
|
|
962
|
+
title: result.title ?? query,
|
|
963
|
+
duration: result.durationInSec ? result.durationInSec * 1e3 : 0,
|
|
964
|
+
thumbnail: result.thumbnails?.[0]?.url ?? null,
|
|
965
|
+
author: result.channel?.name
|
|
966
|
+
}));
|
|
967
|
+
}
|
|
968
|
+
} catch {
|
|
969
|
+
}
|
|
970
|
+
try {
|
|
971
|
+
const ytsr = await import(
|
|
972
|
+
/* webpackIgnore: true */
|
|
973
|
+
"youtube-sr"
|
|
974
|
+
).catch(() => null);
|
|
975
|
+
if (ytsr && ytsr.default) {
|
|
976
|
+
const results = await ytsr.default.search(query, { limit, type: "video" });
|
|
977
|
+
return results.map((result) => ({
|
|
978
|
+
url: result.url,
|
|
979
|
+
title: result.title ?? query,
|
|
980
|
+
duration: result.duration ?? 0,
|
|
981
|
+
thumbnail: result.thumbnail?.url ?? null,
|
|
982
|
+
author: result.channel?.name
|
|
983
|
+
}));
|
|
984
|
+
}
|
|
985
|
+
} catch {
|
|
986
|
+
}
|
|
987
|
+
return [{
|
|
988
|
+
url: `ytsearch:${query}`,
|
|
989
|
+
title: query,
|
|
990
|
+
duration: 0,
|
|
991
|
+
thumbnail: null
|
|
992
|
+
}];
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Check if a string is a URL
|
|
996
|
+
*/
|
|
997
|
+
isUrl(str) {
|
|
998
|
+
try {
|
|
999
|
+
const url = new URL(str);
|
|
1000
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
1001
|
+
} catch {
|
|
1002
|
+
return false;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Extract a title from a URL
|
|
1007
|
+
*/
|
|
1008
|
+
extractTitleFromUrl(url) {
|
|
1009
|
+
try {
|
|
1010
|
+
const parsed = new URL(url);
|
|
1011
|
+
if (parsed.hostname.includes("youtube.com") || parsed.hostname.includes("youtu.be")) {
|
|
1012
|
+
const videoId = parsed.searchParams.get("v") || parsed.pathname.slice(1);
|
|
1013
|
+
return `YouTube Video (${videoId})`;
|
|
1014
|
+
}
|
|
1015
|
+
const parts = parsed.pathname.split("/");
|
|
1016
|
+
const filename = parts[parts.length - 1];
|
|
1017
|
+
return filename || parsed.hostname;
|
|
1018
|
+
} catch {
|
|
1019
|
+
return url;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
815
1022
|
/**
|
|
816
1023
|
* Get the current state for a guild
|
|
817
1024
|
*/
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client/index.ts","../src/gateway/index.ts","../src/interactions/index.ts","../src/voice/index.ts","../src/video/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","/**\n * Gateway connection management\n */\n\nimport type { Client, ClientEvents } from 'discord.js';\nimport type { GatewayConfig } from '@furlow/schema';\n\nexport interface GatewayManagerOptions {\n config?: GatewayConfig;\n onReconnect?: () => void;\n onDisconnect?: () => void;\n onError?: (error: Error) => void;\n}\n\nexport class GatewayManager {\n private client: Client;\n private config: GatewayConfig;\n private reconnectAttempts = 0;\n private maxReconnectAttempts: number;\n private baseDelay: number;\n private maxDelay: number;\n private backoffStrategy: 'exponential' | 'linear' | 'fixed';\n\n constructor(client: Client, options: GatewayManagerOptions = {}) {\n this.client = client;\n this.config = options.config ?? {};\n\n const reconnect = this.config.reconnect ?? {};\n this.maxReconnectAttempts = reconnect.max_retries ?? 10;\n const baseDelay = reconnect.base_delay ?? '1s';\n const maxDelay = reconnect.max_delay ?? '60s';\n this.baseDelay = typeof baseDelay === 'number' ? baseDelay : this.parseDuration(baseDelay);\n this.maxDelay = typeof maxDelay === 'number' ? maxDelay : this.parseDuration(maxDelay);\n this.backoffStrategy = reconnect.backoff ?? 'exponential';\n\n this.setupListeners(options);\n }\n\n private setupListeners(options: GatewayManagerOptions): void {\n this.client.on('shardReady', (shardId) => {\n console.log(`Shard ${shardId} ready`);\n this.reconnectAttempts = 0;\n });\n\n this.client.on('shardDisconnect', (event, shardId) => {\n console.log(`Shard ${shardId} disconnected: ${event.code}`);\n options.onDisconnect?.();\n });\n\n this.client.on('shardReconnecting', (shardId) => {\n console.log(`Shard ${shardId} reconnecting...`);\n this.reconnectAttempts++;\n options.onReconnect?.();\n });\n\n this.client.on('shardError', (error, shardId) => {\n console.error(`Shard ${shardId} error:`, error);\n options.onError?.(error);\n });\n\n this.client.on('shardResume', (shardId, replayedEvents) => {\n console.log(`Shard ${shardId} resumed, replayed ${replayedEvents} events`);\n this.reconnectAttempts = 0;\n });\n }\n\n /**\n * Get the delay for the next reconnection attempt\n */\n getReconnectDelay(): number {\n switch (this.backoffStrategy) {\n case 'exponential':\n return Math.min(\n this.baseDelay * Math.pow(2, this.reconnectAttempts),\n this.maxDelay\n );\n case 'linear':\n return Math.min(\n this.baseDelay * (this.reconnectAttempts + 1),\n this.maxDelay\n );\n case 'fixed':\n default:\n return this.baseDelay;\n }\n }\n\n /**\n * Check if we should attempt reconnection\n */\n shouldReconnect(): boolean {\n return this.reconnectAttempts < this.maxReconnectAttempts;\n }\n\n /**\n * Get current reconnect attempt count\n */\n getReconnectAttempts(): number {\n return this.reconnectAttempts;\n }\n\n /**\n * Reset reconnect counter\n */\n resetReconnectAttempts(): void {\n this.reconnectAttempts = 0;\n }\n\n /**\n * Get gateway latency (ping)\n */\n getPing(): number {\n return this.client.ws.ping;\n }\n\n /**\n * Get shard info\n */\n getShardInfo(): Array<{ id: number; status: string; ping: number }> {\n return [...this.client.ws.shards.values()].map((shard) => ({\n id: shard.id,\n status: shard.status.toString(),\n ping: shard.ping,\n }));\n }\n\n /**\n * Parse duration string to milliseconds\n */\n private parseDuration(duration: string): number {\n const match = duration.match(/^(\\d+)(ms|s|m|h)?$/);\n if (!match) return 1000;\n\n const value = parseInt(match[1]!, 10);\n const unit = match[2] ?? 's';\n\n switch (unit) {\n case 'ms':\n return value;\n case 's':\n return value * 1000;\n case 'm':\n return value * 60 * 1000;\n case 'h':\n return value * 60 * 60 * 1000;\n default:\n return value * 1000;\n }\n }\n}\n\n/**\n * Create a gateway manager\n */\nexport function createGatewayManager(\n client: Client,\n options?: GatewayManagerOptions\n): GatewayManager {\n return new GatewayManager(client, options);\n}\n","/**\n * Interaction handling (commands, buttons, modals)\n */\n\nimport {\n Client,\n REST,\n Routes,\n type Interaction,\n type ChatInputCommandInteraction,\n type ButtonInteraction,\n type StringSelectMenuInteraction,\n type ModalSubmitInteraction,\n type UserContextMenuCommandInteraction,\n type MessageContextMenuCommandInteraction,\n SlashCommandBuilder,\n ContextMenuCommandBuilder,\n ApplicationCommandType,\n type RESTPostAPIChatInputApplicationCommandsJSONBody,\n type RESTPostAPIContextMenuApplicationCommandsJSONBody,\n} from 'discord.js';\nimport type { CommandDefinition, ContextMenuCommand, CommandOption } from '@furlow/schema';\n\nexport interface InteractionHandlerOptions {\n client: Client;\n token: string;\n clientId: string;\n guildId?: string;\n}\n\nexport type InteractionCallback<T extends Interaction = Interaction> = (\n interaction: T\n) => Promise<void>;\n\nexport class InteractionHandler {\n private client: Client;\n private rest: REST;\n private clientId: string;\n private guildId?: string;\n\n private commandHandlers: Map<string, InteractionCallback<ChatInputCommandInteraction>> = new Map();\n private buttonHandlers: Map<string, InteractionCallback<ButtonInteraction>> = new Map();\n private selectHandlers: Map<string, InteractionCallback<StringSelectMenuInteraction>> = new Map();\n private modalHandlers: Map<string, InteractionCallback<ModalSubmitInteraction>> = new Map();\n private userContextHandlers: Map<string, InteractionCallback<UserContextMenuCommandInteraction>> = new Map();\n private messageContextHandlers: Map<string, InteractionCallback<MessageContextMenuCommandInteraction>> = new Map();\n\n constructor(options: InteractionHandlerOptions) {\n this.client = options.client;\n this.clientId = options.clientId;\n this.guildId = options.guildId;\n this.rest = new REST({ version: '10' }).setToken(options.token);\n\n this.setupListener();\n }\n\n /**\n * Set up the interaction listener\n */\n private setupListener(): void {\n this.client.on('interactionCreate', async (interaction) => {\n try {\n await this.handleInteraction(interaction);\n } catch (error) {\n console.error('Interaction error:', error);\n\n if (interaction.isRepliable() && !interaction.replied && !interaction.deferred) {\n await interaction.reply({\n content: 'An error occurred while processing this interaction.',\n ephemeral: true,\n }).catch(() => {});\n }\n }\n });\n }\n\n /**\n * Handle an interaction\n */\n private async handleInteraction(interaction: Interaction): Promise<void> {\n if (interaction.isChatInputCommand()) {\n const handler = this.commandHandlers.get(interaction.commandName);\n if (handler) {\n await handler(interaction);\n }\n } else if (interaction.isButton()) {\n // Check for exact match first, then prefix match\n const handler =\n this.buttonHandlers.get(interaction.customId) ??\n this.findPrefixHandler(this.buttonHandlers, interaction.customId);\n if (handler) {\n await handler(interaction);\n }\n } else if (interaction.isStringSelectMenu()) {\n const handler =\n this.selectHandlers.get(interaction.customId) ??\n this.findPrefixHandler(this.selectHandlers, interaction.customId);\n if (handler) {\n await handler(interaction);\n }\n } else if (interaction.isModalSubmit()) {\n const handler =\n this.modalHandlers.get(interaction.customId) ??\n this.findPrefixHandler(this.modalHandlers, interaction.customId);\n if (handler) {\n await handler(interaction);\n }\n } else if (interaction.isUserContextMenuCommand()) {\n const handler = this.userContextHandlers.get(interaction.commandName);\n if (handler) {\n await handler(interaction);\n }\n } else if (interaction.isMessageContextMenuCommand()) {\n const handler = this.messageContextHandlers.get(interaction.commandName);\n if (handler) {\n await handler(interaction);\n }\n }\n }\n\n /**\n * Find a handler by prefix (for dynamic IDs like \"button_123\")\n */\n private findPrefixHandler<T>(\n handlers: Map<string, T>,\n customId: string\n ): T | undefined {\n for (const [key, handler] of handlers) {\n if (key.endsWith('*') && customId.startsWith(key.slice(0, -1))) {\n return handler;\n }\n }\n return undefined;\n }\n\n /**\n * Register a command handler\n */\n onCommand(\n name: string,\n handler: InteractionCallback<ChatInputCommandInteraction>\n ): void {\n this.commandHandlers.set(name, handler);\n }\n\n /**\n * Register a button handler\n */\n onButton(\n customId: string,\n handler: InteractionCallback<ButtonInteraction>\n ): void {\n this.buttonHandlers.set(customId, handler);\n }\n\n /**\n * Register a select menu handler\n */\n onSelect(\n customId: string,\n handler: InteractionCallback<StringSelectMenuInteraction>\n ): void {\n this.selectHandlers.set(customId, handler);\n }\n\n /**\n * Register a modal handler\n */\n onModal(\n customId: string,\n handler: InteractionCallback<ModalSubmitInteraction>\n ): void {\n this.modalHandlers.set(customId, handler);\n }\n\n /**\n * Register a user context menu handler\n */\n onUserContext(\n name: string,\n handler: InteractionCallback<UserContextMenuCommandInteraction>\n ): void {\n this.userContextHandlers.set(name, handler);\n }\n\n /**\n * Register a message context menu handler\n */\n onMessageContext(\n name: string,\n handler: InteractionCallback<MessageContextMenuCommandInteraction>\n ): void {\n this.messageContextHandlers.set(name, handler);\n }\n\n /**\n * Register slash commands with Discord\n */\n async registerCommands(\n commands: CommandDefinition[],\n contextMenus?: ContextMenuCommand[]\n ): Promise<void> {\n const slashCommands = commands.map((cmd) => this.buildSlashCommand(cmd));\n const contextCommands = (contextMenus ?? []).map((cmd) => this.buildContextMenu(cmd));\n\n const allCommands = [...slashCommands, ...contextCommands];\n\n if (this.guildId) {\n // Guild-specific commands (instant)\n await this.rest.put(\n Routes.applicationGuildCommands(this.clientId, this.guildId),\n { body: allCommands }\n );\n } else {\n // Global commands (can take up to an hour)\n await this.rest.put(Routes.applicationCommands(this.clientId), {\n body: allCommands,\n });\n }\n }\n\n /**\n * Build a slash command from definition\n */\n private buildSlashCommand(\n cmd: CommandDefinition\n ): RESTPostAPIChatInputApplicationCommandsJSONBody {\n const builder = new SlashCommandBuilder()\n .setName(cmd.name)\n .setDescription(cmd.description);\n\n if (cmd.dm_permission !== undefined) {\n builder.setDMPermission(cmd.dm_permission);\n }\n\n if (cmd.nsfw) {\n builder.setNSFW(true);\n }\n\n // Add options\n if (cmd.options) {\n for (const opt of cmd.options) {\n this.addOption(builder, opt);\n }\n }\n\n // Add subcommands\n if (cmd.subcommands) {\n for (const sub of cmd.subcommands) {\n builder.addSubcommand((subBuilder) => {\n subBuilder.setName(sub.name).setDescription(sub.description);\n if (sub.options) {\n for (const opt of sub.options) {\n this.addOption(subBuilder, opt);\n }\n }\n return subBuilder;\n });\n }\n }\n\n // Add subcommand groups\n if (cmd.subcommand_groups) {\n for (const group of cmd.subcommand_groups) {\n builder.addSubcommandGroup((groupBuilder) => {\n groupBuilder.setName(group.name).setDescription(group.description);\n for (const sub of group.subcommands) {\n groupBuilder.addSubcommand((subBuilder) => {\n subBuilder.setName(sub.name).setDescription(sub.description);\n if (sub.options) {\n for (const opt of sub.options) {\n this.addOption(subBuilder, opt);\n }\n }\n return subBuilder;\n });\n }\n return groupBuilder;\n });\n }\n }\n\n return builder.toJSON();\n }\n\n /**\n * Add an option to a command builder\n */\n private addOption(builder: any, opt: CommandOption): void {\n const addMethod = `add${this.getOptionMethodName(opt.type)}Option`;\n\n if (typeof builder[addMethod] !== 'function') {\n console.warn(`Unknown option type: ${opt.type}`);\n return;\n }\n\n builder[addMethod]((optBuilder: any) => {\n optBuilder.setName(opt.name).setDescription(opt.description);\n\n if (opt.required) {\n optBuilder.setRequired(true);\n }\n\n if (opt.choices && optBuilder.addChoices) {\n optBuilder.addChoices(...opt.choices);\n }\n\n if (opt.min_value !== undefined && optBuilder.setMinValue) {\n optBuilder.setMinValue(opt.min_value);\n }\n\n if (opt.max_value !== undefined && optBuilder.setMaxValue) {\n optBuilder.setMaxValue(opt.max_value);\n }\n\n if (opt.min_length !== undefined && optBuilder.setMinLength) {\n optBuilder.setMinLength(opt.min_length);\n }\n\n if (opt.max_length !== undefined && optBuilder.setMaxLength) {\n optBuilder.setMaxLength(opt.max_length);\n }\n\n if (opt.autocomplete && optBuilder.setAutocomplete) {\n optBuilder.setAutocomplete(true);\n }\n\n return optBuilder;\n });\n }\n\n /**\n * Get the method name for an option type\n */\n private getOptionMethodName(type: string): string {\n const map: Record<string, string> = {\n string: 'String',\n integer: 'Integer',\n number: 'Number',\n boolean: 'Boolean',\n user: 'User',\n channel: 'Channel',\n role: 'Role',\n mentionable: 'Mentionable',\n attachment: 'Attachment',\n };\n return map[type] ?? 'String';\n }\n\n /**\n * Build a context menu command\n */\n private buildContextMenu(\n cmd: ContextMenuCommand\n ): RESTPostAPIContextMenuApplicationCommandsJSONBody {\n const builder = new ContextMenuCommandBuilder()\n .setName(cmd.name)\n .setType(\n cmd.type === 'user'\n ? ApplicationCommandType.User\n : ApplicationCommandType.Message\n );\n\n return builder.toJSON();\n }\n}\n\n/**\n * Create an interaction handler\n */\nexport function createInteractionHandler(\n options: InteractionHandlerOptions\n): InteractionHandler {\n return new InteractionHandler(options);\n}\n","/**\n * Voice connection and audio management\n */\n\nimport {\n joinVoiceChannel,\n createAudioPlayer,\n createAudioResource,\n AudioPlayerStatus,\n VoiceConnectionStatus,\n entersState,\n getVoiceConnection,\n type VoiceConnection,\n type AudioPlayer,\n type AudioResource,\n type DiscordGatewayAdapterCreator,\n} from '@discordjs/voice';\nimport type { VoiceChannel, StageChannel, Guild } from 'discord.js';\n\n/** Voice configuration */\nexport interface VoiceConfig {\n connection?: {\n self_deaf?: boolean;\n self_mute?: boolean;\n timeout?: string;\n };\n default_volume?: number;\n default_loop?: QueueLoopMode;\n max_queue_size?: number;\n filters?: string[];\n}\n\n/** Audio filter types */\nexport type AudioFilter =\n | 'bassboost'\n | 'nightcore'\n | 'vaporwave'\n | '8d'\n | 'treble'\n | 'normalizer'\n | 'karaoke'\n | 'tremolo'\n | 'vibrato'\n | 'reverse';\n\n/** Queue loop mode */\nexport type QueueLoopMode = 'off' | 'track' | 'queue';\n\nexport interface QueueItem {\n url: string;\n title: string;\n duration?: number;\n thumbnail?: string;\n requesterId: string;\n}\n\nexport interface GuildVoiceState {\n connection: VoiceConnection;\n player: AudioPlayer;\n queue: QueueItem[];\n currentTrack: QueueItem | null;\n volume: number;\n loopMode: QueueLoopMode;\n filters: Set<AudioFilter>;\n paused: boolean;\n}\n\nexport class VoiceManager {\n private guildStates: Map<string, GuildVoiceState> = new Map();\n private config: VoiceConfig = {};\n\n /**\n * Configure the voice manager\n */\n configure(config: VoiceConfig): void {\n this.config = config;\n }\n\n /**\n * Join a voice channel\n */\n async join(\n channel: VoiceChannel | StageChannel,\n options: { selfDeaf?: boolean; selfMute?: boolean } = {}\n ): Promise<VoiceConnection> {\n const guildId = channel.guild.id;\n\n // Leave existing connection if any\n const existing = getVoiceConnection(guildId);\n if (existing) {\n existing.destroy();\n }\n\n const connection = joinVoiceChannel({\n channelId: channel.id,\n guildId: guildId,\n adapterCreator: channel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator,\n selfDeaf: options.selfDeaf ?? this.config.connection?.self_deaf ?? true,\n selfMute: options.selfMute ?? this.config.connection?.self_mute ?? false,\n });\n\n // Wait for connection to be ready\n await entersState(connection, VoiceConnectionStatus.Ready, 30000);\n\n // Create audio player\n const player = createAudioPlayer();\n\n // Set up player events\n player.on(AudioPlayerStatus.Idle, () => {\n this.handleTrackEnd(guildId);\n });\n\n player.on('error', (error) => {\n console.error(`Audio player error in ${guildId}:`, error);\n this.handleTrackEnd(guildId);\n });\n\n // Subscribe connection to player\n connection.subscribe(player);\n\n // Store state\n this.guildStates.set(guildId, {\n connection,\n player,\n queue: [],\n currentTrack: null,\n volume: this.config.default_volume ?? 100,\n loopMode: this.config.default_loop ?? 'off',\n filters: new Set(),\n paused: false,\n });\n\n return connection;\n }\n\n /**\n * Leave a voice channel\n */\n leave(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.stop();\n state.connection.destroy();\n this.guildStates.delete(guildId);\n\n return true;\n }\n\n /**\n * Play audio from a URL or file\n */\n async play(\n guildId: string,\n source: string,\n options: { volume?: number; seek?: number } = {}\n ): Promise<void> {\n const state = this.guildStates.get(guildId);\n if (!state) {\n throw new Error('Not connected to voice in this guild');\n }\n\n // Create audio resource\n const resource = createAudioResource(source, {\n inlineVolume: true,\n });\n\n // Set volume\n const volume = options.volume ?? state.volume;\n resource.volume?.setVolume(volume / 100);\n\n // Play\n state.player.play(resource);\n state.paused = false;\n }\n\n /**\n * Add a track to the queue\n */\n addToQueue(\n guildId: string,\n item: QueueItem,\n position?: number | 'next' | 'last'\n ): number {\n const state = this.guildStates.get(guildId);\n if (!state) {\n throw new Error('Not connected to voice in this guild');\n }\n\n // Check queue size limit\n const maxSize = this.config.max_queue_size ?? 1000;\n if (state.queue.length >= maxSize) {\n throw new Error(`Queue is full (max ${maxSize} tracks)`);\n }\n\n if (position === 'next' || position === 0) {\n state.queue.unshift(item);\n return 0;\n } else if (position === 'last' || position === undefined) {\n state.queue.push(item);\n return state.queue.length - 1;\n } else if (typeof position === 'number') {\n state.queue.splice(position, 0, item);\n return position;\n }\n\n state.queue.push(item);\n return state.queue.length - 1;\n }\n\n /**\n * Remove a track from the queue\n */\n removeFromQueue(guildId: string, position: number): QueueItem | null {\n const state = this.guildStates.get(guildId);\n if (!state) return null;\n\n const [removed] = state.queue.splice(position, 1);\n return removed ?? null;\n }\n\n /**\n * Clear the queue\n */\n clearQueue(guildId: string): number {\n const state = this.guildStates.get(guildId);\n if (!state) return 0;\n\n const count = state.queue.length;\n state.queue = [];\n return count;\n }\n\n /**\n * Shuffle the queue\n */\n shuffleQueue(guildId: string): void {\n const state = this.guildStates.get(guildId);\n if (!state) return;\n\n for (let i = state.queue.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [state.queue[i], state.queue[j]] = [state.queue[j]!, state.queue[i]!];\n }\n }\n\n /**\n * Skip the current track\n */\n skip(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.stop();\n return true;\n }\n\n /**\n * Pause playback\n */\n pause(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.pause();\n state.paused = true;\n return true;\n }\n\n /**\n * Resume playback\n */\n resume(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.unpause();\n state.paused = false;\n return true;\n }\n\n /**\n * Stop playback\n */\n stop(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.stop();\n state.queue = [];\n state.currentTrack = null;\n return true;\n }\n\n /**\n * Set volume\n */\n setVolume(guildId: string, volume: number): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.volume = Math.max(0, Math.min(200, volume));\n return true;\n }\n\n /**\n * Set loop mode\n */\n setLoopMode(guildId: string, mode: QueueLoopMode): void {\n const state = this.guildStates.get(guildId);\n if (!state) return;\n\n state.loopMode = mode;\n }\n\n /**\n * Get the current state for a guild\n */\n getState(guildId: string): GuildVoiceState | undefined {\n return this.guildStates.get(guildId);\n }\n\n /**\n * Check if connected to a guild\n */\n isConnected(guildId: string): boolean {\n return this.guildStates.has(guildId);\n }\n\n /**\n * Get the queue for a guild\n */\n getQueue(guildId: string): QueueItem[] {\n return this.guildStates.get(guildId)?.queue ?? [];\n }\n\n /**\n * Get the current track for a guild\n */\n getCurrentTrack(guildId: string): QueueItem | null {\n return this.guildStates.get(guildId)?.currentTrack ?? null;\n }\n\n /**\n * Handle track end (play next or loop)\n */\n private async handleTrackEnd(guildId: string): Promise<void> {\n const state = this.guildStates.get(guildId);\n if (!state) return;\n\n // Handle loop modes\n if (state.loopMode === 'track' && state.currentTrack) {\n await this.play(guildId, state.currentTrack.url);\n return;\n }\n\n if (state.loopMode === 'queue' && state.currentTrack) {\n state.queue.push(state.currentTrack);\n }\n\n // Play next track\n const next = state.queue.shift();\n if (next) {\n state.currentTrack = next;\n await this.play(guildId, next.url);\n } else {\n state.currentTrack = null;\n }\n }\n\n /**\n * Disconnect from all voice channels\n */\n disconnectAll(): void {\n for (const [guildId] of this.guildStates) {\n this.leave(guildId);\n }\n }\n}\n\n/**\n * Create a voice manager\n */\nexport function createVoiceManager(): VoiceManager {\n return new VoiceManager();\n}\n","/**\n * Video/streaming detection and notifications\n */\n\nimport type { Client, VoiceState, GuildMember, TextChannel, Role } from 'discord.js';\nimport type { VideoConfig } from '@furlow/schema';\n\nexport interface StreamEvent {\n type: 'start' | 'stop';\n member: GuildMember;\n channelId: string;\n guildId: string;\n timestamp: Date;\n}\n\nexport interface StreamInfo {\n memberId: string;\n username: string;\n channelId: string;\n startedAt: Date;\n}\n\nexport type StreamEventCallback = (event: StreamEvent) => void | Promise<void>;\n\nexport class VideoManager {\n private client: Client;\n private config: VideoConfig = {};\n private streamingMembers: Map<string, Map<string, StreamInfo>> = new Map(); // guildId -> Map<memberId, StreamInfo>\n private listeners: StreamEventCallback[] = [];\n private initialized = false;\n\n constructor(client: Client) {\n this.client = client;\n }\n\n /**\n * Configure and initialize the video manager\n */\n configure(config: VideoConfig): void {\n this.config = config;\n\n if (config.stream_detection && !this.initialized) {\n this.setupListener();\n this.initialized = true;\n }\n }\n\n /**\n * Set up voice state update listener for stream detection\n */\n private setupListener(): void {\n this.client.on('voiceStateUpdate', (oldState, newState) => {\n this.handleVoiceStateUpdate(oldState, newState);\n });\n }\n\n /**\n * Handle voice state update for stream detection\n */\n private async handleVoiceStateUpdate(\n oldState: VoiceState,\n newState: VoiceState\n ): Promise<void> {\n const guildId = newState.guild.id;\n const memberId = newState.member?.id;\n\n if (!memberId || !newState.member) return;\n\n // Get or create streaming map for guild\n if (!this.streamingMembers.has(guildId)) {\n this.streamingMembers.set(guildId, new Map());\n }\n const streaming = this.streamingMembers.get(guildId)!;\n\n const wasStreaming = oldState.streaming;\n const isStreaming = newState.streaming;\n\n // Stream started\n if (!wasStreaming && isStreaming && newState.channelId) {\n const streamInfo: StreamInfo = {\n memberId,\n username: newState.member.user.username,\n channelId: newState.channelId,\n startedAt: new Date(),\n };\n streaming.set(memberId, streamInfo);\n\n const event: StreamEvent = {\n type: 'start',\n member: newState.member,\n channelId: newState.channelId,\n guildId,\n timestamp: new Date(),\n };\n\n await this.emit(event);\n await this.sendNotification(event);\n }\n\n // Stream stopped\n if (wasStreaming && !isStreaming) {\n streaming.delete(memberId);\n const event: StreamEvent = {\n type: 'stop',\n member: newState.member,\n channelId: oldState.channelId ?? '',\n guildId,\n timestamp: new Date(),\n };\n await this.emit(event);\n }\n\n // Left voice while streaming\n if (wasStreaming && !newState.channelId) {\n streaming.delete(memberId);\n const event: StreamEvent = {\n type: 'stop',\n member: newState.member,\n channelId: oldState.channelId ?? '',\n guildId,\n timestamp: new Date(),\n };\n await this.emit(event);\n }\n }\n\n /**\n * Emit a stream event to all listeners\n */\n private async emit(event: StreamEvent): Promise<void> {\n for (const listener of this.listeners) {\n try {\n await listener(event);\n } catch (error) {\n console.error('Stream event listener error:', error);\n }\n }\n }\n\n /**\n * Send notification when a stream starts\n */\n private async sendNotification(event: StreamEvent): Promise<void> {\n if (event.type !== 'start' || !this.config.notify_channel) {\n return;\n }\n\n try {\n const guild = this.client.guilds.cache.get(event.guildId);\n if (!guild) return;\n\n // Resolve channel (could be an ID or expression result)\n const channelId = typeof this.config.notify_channel === 'string'\n ? this.config.notify_channel.replace(/[<#>]/g, '') // Handle mention format\n : String(this.config.notify_channel);\n\n const channel = guild.channels.cache.get(channelId) as TextChannel | undefined;\n if (!channel || !channel.isTextBased()) return;\n\n // Build notification message\n const voiceChannel = guild.channels.cache.get(event.channelId);\n const voiceChannelName = voiceChannel?.name ?? 'Unknown Channel';\n\n let content = `**${event.member.displayName}** started streaming in **${voiceChannelName}**!`;\n\n // Add role mention if configured\n if (this.config.notify_role) {\n const roleId = typeof this.config.notify_role === 'string'\n ? this.config.notify_role.replace(/[<@&>]/g, '')\n : String(this.config.notify_role);\n\n const role = guild.roles.cache.get(roleId);\n if (role) {\n content = `${role.toString()} ${content}`;\n }\n }\n\n await channel.send({\n content,\n allowedMentions: {\n roles: this.config.notify_role ? [String(this.config.notify_role).replace(/[<@&>]/g, '')] : [],\n },\n });\n } catch (error) {\n console.error('Failed to send stream notification:', error);\n }\n }\n\n /**\n * Register a stream event listener\n */\n onStreamEvent(callback: StreamEventCallback): () => void {\n this.listeners.push(callback);\n return () => {\n const index = this.listeners.indexOf(callback);\n if (index !== -1) {\n this.listeners.splice(index, 1);\n }\n };\n }\n\n /**\n * Get all members currently streaming in a guild\n */\n getStreamingMembers(guildId: string): StreamInfo[] {\n const streaming = this.streamingMembers.get(guildId);\n if (!streaming) return [];\n return [...streaming.values()];\n }\n\n /**\n * Get streaming info for a specific member\n */\n getStreamInfo(guildId: string, memberId: string): StreamInfo | null {\n return this.streamingMembers.get(guildId)?.get(memberId) ?? null;\n }\n\n /**\n * Check if a member is streaming\n */\n isStreaming(guildId: string, memberId: string): boolean {\n return this.streamingMembers.get(guildId)?.has(memberId) ?? false;\n }\n\n /**\n * Get stream count in a guild\n */\n getStreamCount(guildId: string): number {\n return this.streamingMembers.get(guildId)?.size ?? 0;\n }\n\n /**\n * Get total active streams across all guilds\n */\n getTotalStreamCount(): number {\n let count = 0;\n for (const streaming of this.streamingMembers.values()) {\n count += streaming.size;\n }\n return count;\n }\n\n /**\n * Get all active streams across all guilds\n */\n getAllActiveStreams(): Array<{ guildId: string; streams: StreamInfo[] }> {\n const result: Array<{ guildId: string; streams: StreamInfo[] }> = [];\n for (const [guildId, streaming] of this.streamingMembers) {\n if (streaming.size > 0) {\n result.push({ guildId, streams: [...streaming.values()] });\n }\n }\n return result;\n }\n\n /**\n * Check if stream detection is enabled\n */\n isEnabled(): boolean {\n return this.config.stream_detection ?? false;\n }\n\n /**\n * Get current configuration\n */\n getConfig(): VideoConfig {\n return { ...this.config };\n }\n}\n\n/**\n * Create a video manager\n */\nexport function createVideoManager(client: Client): VideoManager {\n return new VideoManager(client);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client/index.ts","../src/gateway/index.ts","../src/interactions/index.ts","../src/voice/index.ts","../src/video/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","/**\n * Gateway connection management\n */\n\nimport type { Client, ClientEvents } from 'discord.js';\nimport type { GatewayConfig } from '@furlow/schema';\n\nexport interface GatewayManagerOptions {\n config?: GatewayConfig;\n onReconnect?: () => void;\n onDisconnect?: () => void;\n onError?: (error: Error) => void;\n}\n\nexport class GatewayManager {\n private client: Client;\n private config: GatewayConfig;\n private reconnectAttempts = 0;\n private maxReconnectAttempts: number;\n private baseDelay: number;\n private maxDelay: number;\n private backoffStrategy: 'exponential' | 'linear' | 'fixed';\n\n constructor(client: Client, options: GatewayManagerOptions = {}) {\n this.client = client;\n this.config = options.config ?? {};\n\n const reconnect = this.config.reconnect ?? {};\n this.maxReconnectAttempts = reconnect.max_retries ?? 10;\n const baseDelay = reconnect.base_delay ?? '1s';\n const maxDelay = reconnect.max_delay ?? '60s';\n this.baseDelay = typeof baseDelay === 'number' ? baseDelay : this.parseDuration(baseDelay);\n this.maxDelay = typeof maxDelay === 'number' ? maxDelay : this.parseDuration(maxDelay);\n this.backoffStrategy = reconnect.backoff ?? 'exponential';\n\n this.setupListeners(options);\n }\n\n private setupListeners(options: GatewayManagerOptions): void {\n this.client.on('shardReady', (shardId) => {\n console.log(`Shard ${shardId} ready`);\n this.reconnectAttempts = 0;\n });\n\n this.client.on('shardDisconnect', (event, shardId) => {\n console.log(`Shard ${shardId} disconnected: ${event.code}`);\n options.onDisconnect?.();\n });\n\n this.client.on('shardReconnecting', (shardId) => {\n console.log(`Shard ${shardId} reconnecting...`);\n this.reconnectAttempts++;\n options.onReconnect?.();\n });\n\n this.client.on('shardError', (error, shardId) => {\n console.error(`Shard ${shardId} error:`, error);\n options.onError?.(error);\n });\n\n this.client.on('shardResume', (shardId, replayedEvents) => {\n console.log(`Shard ${shardId} resumed, replayed ${replayedEvents} events`);\n this.reconnectAttempts = 0;\n });\n }\n\n /**\n * Get the delay for the next reconnection attempt\n */\n getReconnectDelay(): number {\n switch (this.backoffStrategy) {\n case 'exponential':\n return Math.min(\n this.baseDelay * Math.pow(2, this.reconnectAttempts),\n this.maxDelay\n );\n case 'linear':\n return Math.min(\n this.baseDelay * (this.reconnectAttempts + 1),\n this.maxDelay\n );\n case 'fixed':\n default:\n return this.baseDelay;\n }\n }\n\n /**\n * Check if we should attempt reconnection\n */\n shouldReconnect(): boolean {\n return this.reconnectAttempts < this.maxReconnectAttempts;\n }\n\n /**\n * Get current reconnect attempt count\n */\n getReconnectAttempts(): number {\n return this.reconnectAttempts;\n }\n\n /**\n * Reset reconnect counter\n */\n resetReconnectAttempts(): void {\n this.reconnectAttempts = 0;\n }\n\n /**\n * Get gateway latency (ping)\n */\n getPing(): number {\n return this.client.ws.ping;\n }\n\n /**\n * Get shard info\n */\n getShardInfo(): Array<{ id: number; status: string; ping: number }> {\n return [...this.client.ws.shards.values()].map((shard) => ({\n id: shard.id,\n status: shard.status.toString(),\n ping: shard.ping,\n }));\n }\n\n /**\n * Parse duration string to milliseconds\n */\n private parseDuration(duration: string): number {\n const match = duration.match(/^(\\d+)(ms|s|m|h)?$/);\n if (!match) return 1000;\n\n const value = parseInt(match[1]!, 10);\n const unit = match[2] ?? 's';\n\n switch (unit) {\n case 'ms':\n return value;\n case 's':\n return value * 1000;\n case 'm':\n return value * 60 * 1000;\n case 'h':\n return value * 60 * 60 * 1000;\n default:\n return value * 1000;\n }\n }\n}\n\n/**\n * Create a gateway manager\n */\nexport function createGatewayManager(\n client: Client,\n options?: GatewayManagerOptions\n): GatewayManager {\n return new GatewayManager(client, options);\n}\n","/**\n * Interaction handling (commands, buttons, modals)\n */\n\nimport {\n Client,\n REST,\n Routes,\n type Interaction,\n type ChatInputCommandInteraction,\n type ButtonInteraction,\n type StringSelectMenuInteraction,\n type ModalSubmitInteraction,\n type UserContextMenuCommandInteraction,\n type MessageContextMenuCommandInteraction,\n SlashCommandBuilder,\n ContextMenuCommandBuilder,\n ApplicationCommandType,\n type RESTPostAPIChatInputApplicationCommandsJSONBody,\n type RESTPostAPIContextMenuApplicationCommandsJSONBody,\n} from 'discord.js';\nimport type { CommandDefinition, ContextMenuCommand, CommandOption } from '@furlow/schema';\n\nexport interface InteractionHandlerOptions {\n client: Client;\n token: string;\n clientId: string;\n guildId?: string;\n}\n\nexport type InteractionCallback<T extends Interaction = Interaction> = (\n interaction: T\n) => Promise<void>;\n\nexport class InteractionHandler {\n private client: Client;\n private rest: REST;\n private clientId: string;\n private guildId?: string;\n\n private commandHandlers: Map<string, InteractionCallback<ChatInputCommandInteraction>> = new Map();\n private buttonHandlers: Map<string, InteractionCallback<ButtonInteraction>> = new Map();\n private selectHandlers: Map<string, InteractionCallback<StringSelectMenuInteraction>> = new Map();\n private modalHandlers: Map<string, InteractionCallback<ModalSubmitInteraction>> = new Map();\n private userContextHandlers: Map<string, InteractionCallback<UserContextMenuCommandInteraction>> = new Map();\n private messageContextHandlers: Map<string, InteractionCallback<MessageContextMenuCommandInteraction>> = new Map();\n\n constructor(options: InteractionHandlerOptions) {\n this.client = options.client;\n this.clientId = options.clientId;\n this.guildId = options.guildId;\n this.rest = new REST({ version: '10' }).setToken(options.token);\n\n this.setupListener();\n }\n\n /**\n * Set up the interaction listener\n */\n private setupListener(): void {\n this.client.on('interactionCreate', async (interaction) => {\n try {\n await this.handleInteraction(interaction);\n } catch (error) {\n console.error('Interaction error:', error);\n\n if (interaction.isRepliable() && !interaction.replied && !interaction.deferred) {\n await interaction.reply({\n content: 'An error occurred while processing this interaction.',\n ephemeral: true,\n }).catch(() => {});\n }\n }\n });\n }\n\n /**\n * Handle an interaction\n */\n private async handleInteraction(interaction: Interaction): Promise<void> {\n if (interaction.isChatInputCommand()) {\n const handler = this.commandHandlers.get(interaction.commandName);\n if (handler) {\n await handler(interaction);\n }\n } else if (interaction.isButton()) {\n // Check for exact match first, then prefix match\n const handler =\n this.buttonHandlers.get(interaction.customId) ??\n this.findPrefixHandler(this.buttonHandlers, interaction.customId);\n if (handler) {\n await handler(interaction);\n }\n } else if (interaction.isStringSelectMenu()) {\n const handler =\n this.selectHandlers.get(interaction.customId) ??\n this.findPrefixHandler(this.selectHandlers, interaction.customId);\n if (handler) {\n await handler(interaction);\n }\n } else if (interaction.isModalSubmit()) {\n const handler =\n this.modalHandlers.get(interaction.customId) ??\n this.findPrefixHandler(this.modalHandlers, interaction.customId);\n if (handler) {\n await handler(interaction);\n }\n } else if (interaction.isUserContextMenuCommand()) {\n const handler = this.userContextHandlers.get(interaction.commandName);\n if (handler) {\n await handler(interaction);\n }\n } else if (interaction.isMessageContextMenuCommand()) {\n const handler = this.messageContextHandlers.get(interaction.commandName);\n if (handler) {\n await handler(interaction);\n }\n }\n }\n\n /**\n * Find a handler by prefix (for dynamic IDs like \"button_123\")\n */\n private findPrefixHandler<T>(\n handlers: Map<string, T>,\n customId: string\n ): T | undefined {\n for (const [key, handler] of handlers) {\n if (key.endsWith('*') && customId.startsWith(key.slice(0, -1))) {\n return handler;\n }\n }\n return undefined;\n }\n\n /**\n * Register a command handler\n */\n onCommand(\n name: string,\n handler: InteractionCallback<ChatInputCommandInteraction>\n ): void {\n this.commandHandlers.set(name, handler);\n }\n\n /**\n * Register a button handler\n */\n onButton(\n customId: string,\n handler: InteractionCallback<ButtonInteraction>\n ): void {\n this.buttonHandlers.set(customId, handler);\n }\n\n /**\n * Register a select menu handler\n */\n onSelect(\n customId: string,\n handler: InteractionCallback<StringSelectMenuInteraction>\n ): void {\n this.selectHandlers.set(customId, handler);\n }\n\n /**\n * Register a modal handler\n */\n onModal(\n customId: string,\n handler: InteractionCallback<ModalSubmitInteraction>\n ): void {\n this.modalHandlers.set(customId, handler);\n }\n\n /**\n * Register a user context menu handler\n */\n onUserContext(\n name: string,\n handler: InteractionCallback<UserContextMenuCommandInteraction>\n ): void {\n this.userContextHandlers.set(name, handler);\n }\n\n /**\n * Register a message context menu handler\n */\n onMessageContext(\n name: string,\n handler: InteractionCallback<MessageContextMenuCommandInteraction>\n ): void {\n this.messageContextHandlers.set(name, handler);\n }\n\n /**\n * Register slash commands with Discord\n */\n async registerCommands(\n commands: CommandDefinition[],\n contextMenus?: ContextMenuCommand[]\n ): Promise<void> {\n const slashCommands = commands.map((cmd) => this.buildSlashCommand(cmd));\n const contextCommands = (contextMenus ?? []).map((cmd) => this.buildContextMenu(cmd));\n\n const allCommands = [...slashCommands, ...contextCommands];\n\n if (this.guildId) {\n // Guild-specific commands (instant)\n await this.rest.put(\n Routes.applicationGuildCommands(this.clientId, this.guildId),\n { body: allCommands }\n );\n } else {\n // Global commands (can take up to an hour)\n await this.rest.put(Routes.applicationCommands(this.clientId), {\n body: allCommands,\n });\n }\n }\n\n /**\n * Build a slash command from definition\n */\n private buildSlashCommand(\n cmd: CommandDefinition\n ): RESTPostAPIChatInputApplicationCommandsJSONBody {\n const builder = new SlashCommandBuilder()\n .setName(cmd.name)\n .setDescription(cmd.description);\n\n if (cmd.dm_permission !== undefined) {\n builder.setDMPermission(cmd.dm_permission);\n }\n\n if (cmd.nsfw) {\n builder.setNSFW(true);\n }\n\n // Add options\n if (cmd.options) {\n for (const opt of cmd.options) {\n this.addOption(builder, opt);\n }\n }\n\n // Add subcommands\n if (cmd.subcommands) {\n for (const sub of cmd.subcommands) {\n builder.addSubcommand((subBuilder) => {\n subBuilder.setName(sub.name).setDescription(sub.description);\n if (sub.options) {\n for (const opt of sub.options) {\n this.addOption(subBuilder, opt);\n }\n }\n return subBuilder;\n });\n }\n }\n\n // Add subcommand groups\n if (cmd.subcommand_groups) {\n for (const group of cmd.subcommand_groups) {\n builder.addSubcommandGroup((groupBuilder) => {\n groupBuilder.setName(group.name).setDescription(group.description);\n for (const sub of group.subcommands) {\n groupBuilder.addSubcommand((subBuilder) => {\n subBuilder.setName(sub.name).setDescription(sub.description);\n if (sub.options) {\n for (const opt of sub.options) {\n this.addOption(subBuilder, opt);\n }\n }\n return subBuilder;\n });\n }\n return groupBuilder;\n });\n }\n }\n\n return builder.toJSON();\n }\n\n /**\n * Add an option to a command builder\n */\n private addOption(builder: any, opt: CommandOption): void {\n const addMethod = `add${this.getOptionMethodName(opt.type)}Option`;\n\n if (typeof builder[addMethod] !== 'function') {\n console.warn(`Unknown option type: ${opt.type}`);\n return;\n }\n\n builder[addMethod]((optBuilder: any) => {\n optBuilder.setName(opt.name).setDescription(opt.description);\n\n if (opt.required) {\n optBuilder.setRequired(true);\n }\n\n if (opt.choices && optBuilder.addChoices) {\n optBuilder.addChoices(...opt.choices);\n }\n\n if (opt.min_value !== undefined && optBuilder.setMinValue) {\n optBuilder.setMinValue(opt.min_value);\n }\n\n if (opt.max_value !== undefined && optBuilder.setMaxValue) {\n optBuilder.setMaxValue(opt.max_value);\n }\n\n if (opt.min_length !== undefined && optBuilder.setMinLength) {\n optBuilder.setMinLength(opt.min_length);\n }\n\n if (opt.max_length !== undefined && optBuilder.setMaxLength) {\n optBuilder.setMaxLength(opt.max_length);\n }\n\n if (opt.autocomplete && optBuilder.setAutocomplete) {\n optBuilder.setAutocomplete(true);\n }\n\n return optBuilder;\n });\n }\n\n /**\n * Get the method name for an option type\n */\n private getOptionMethodName(type: string): string {\n const map: Record<string, string> = {\n string: 'String',\n integer: 'Integer',\n number: 'Number',\n boolean: 'Boolean',\n user: 'User',\n channel: 'Channel',\n role: 'Role',\n mentionable: 'Mentionable',\n attachment: 'Attachment',\n };\n return map[type] ?? 'String';\n }\n\n /**\n * Build a context menu command\n */\n private buildContextMenu(\n cmd: ContextMenuCommand\n ): RESTPostAPIContextMenuApplicationCommandsJSONBody {\n const builder = new ContextMenuCommandBuilder()\n .setName(cmd.name)\n .setType(\n cmd.type === 'user'\n ? ApplicationCommandType.User\n : ApplicationCommandType.Message\n );\n\n return builder.toJSON();\n }\n}\n\n/**\n * Create an interaction handler\n */\nexport function createInteractionHandler(\n options: InteractionHandlerOptions\n): InteractionHandler {\n return new InteractionHandler(options);\n}\n","/**\n * Voice connection and audio management\n */\n\nimport {\n joinVoiceChannel,\n createAudioPlayer,\n createAudioResource,\n AudioPlayerStatus,\n VoiceConnectionStatus,\n entersState,\n getVoiceConnection,\n type VoiceConnection,\n type AudioPlayer,\n type AudioResource,\n type DiscordGatewayAdapterCreator,\n} from '@discordjs/voice';\nimport type { VoiceChannel, StageChannel, Guild } from 'discord.js';\n\n/** Voice configuration */\nexport interface VoiceConfig {\n connection?: {\n self_deaf?: boolean;\n self_mute?: boolean;\n timeout?: string;\n };\n default_volume?: number;\n default_loop?: QueueLoopMode;\n max_queue_size?: number;\n filters?: string[];\n}\n\n/** Audio filter types */\nexport type AudioFilter =\n | 'bassboost'\n | 'nightcore'\n | 'vaporwave'\n | '8d'\n | 'treble'\n | 'normalizer'\n | 'karaoke'\n | 'tremolo'\n | 'vibrato'\n | 'reverse';\n\n/** Queue loop mode */\nexport type QueueLoopMode = 'off' | 'track' | 'queue';\n\nexport interface QueueItem {\n url: string;\n title: string;\n duration?: number;\n thumbnail?: string;\n requesterId: string;\n}\n\nexport interface GuildVoiceState {\n connection: VoiceConnection;\n player: AudioPlayer;\n queue: QueueItem[];\n currentTrack: QueueItem | null;\n currentResource: AudioResource | null;\n volume: number;\n loopMode: QueueLoopMode;\n filters: Set<AudioFilter>;\n paused: boolean;\n startTime: number;\n pausedAt: number;\n}\n\n/** FFmpeg filter definitions */\nconst FILTER_ARGS: Record<AudioFilter, string[]> = {\n bassboost: ['-af', 'bass=g=10,dynaudnorm=f=200'],\n nightcore: ['-af', 'asetrate=44100*1.25,aresample=44100,atempo=1.06'],\n vaporwave: ['-af', 'asetrate=44100*0.8,aresample=44100,atempo=0.9'],\n '8d': ['-af', 'apulsator=hz=0.08'],\n treble: ['-af', 'treble=g=5,dynaudnorm=f=200'],\n normalizer: ['-af', 'dynaudnorm=f=200:g=3'],\n karaoke: ['-af', 'stereotools=mlev=0.03'],\n tremolo: ['-af', 'tremolo=f=6:d=0.5'],\n vibrato: ['-af', 'vibrato=f=6:d=0.5'],\n reverse: ['-af', 'areverse'],\n};\n\n/** Search result from voice_search */\nexport interface SearchResult {\n url: string;\n title: string;\n duration: number;\n thumbnail: string | null;\n author?: string;\n}\n\nexport class VoiceManager {\n private guildStates: Map<string, GuildVoiceState> = new Map();\n private config: VoiceConfig = {};\n\n /**\n * Configure the voice manager\n */\n configure(config: VoiceConfig): void {\n this.config = config;\n }\n\n /**\n * Join a voice channel\n */\n async join(\n channel: VoiceChannel | StageChannel,\n options: { selfDeaf?: boolean; selfMute?: boolean } = {}\n ): Promise<VoiceConnection> {\n const guildId = channel.guild.id;\n\n // Leave existing connection if any\n const existing = getVoiceConnection(guildId);\n if (existing) {\n existing.destroy();\n }\n\n const connection = joinVoiceChannel({\n channelId: channel.id,\n guildId: guildId,\n adapterCreator: channel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator,\n selfDeaf: options.selfDeaf ?? this.config.connection?.self_deaf ?? true,\n selfMute: options.selfMute ?? this.config.connection?.self_mute ?? false,\n });\n\n // Wait for connection to be ready\n await entersState(connection, VoiceConnectionStatus.Ready, 30000);\n\n // Create audio player\n const player = createAudioPlayer();\n\n // Set up player events\n player.on(AudioPlayerStatus.Idle, () => {\n this.handleTrackEnd(guildId);\n });\n\n player.on('error', (error) => {\n console.error(`Audio player error in ${guildId}:`, error);\n this.handleTrackEnd(guildId);\n });\n\n // Subscribe connection to player\n connection.subscribe(player);\n\n // Store state\n this.guildStates.set(guildId, {\n connection,\n player,\n queue: [],\n currentTrack: null,\n currentResource: null,\n volume: this.config.default_volume ?? 100,\n loopMode: this.config.default_loop ?? 'off',\n filters: new Set(),\n paused: false,\n startTime: 0,\n pausedAt: 0,\n });\n\n return connection;\n }\n\n /**\n * Leave a voice channel\n */\n leave(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.stop();\n state.connection.destroy();\n this.guildStates.delete(guildId);\n\n return true;\n }\n\n /**\n * Play audio from a URL or file\n */\n async play(\n guildId: string,\n source: string,\n options: { volume?: number; seek?: number } = {}\n ): Promise<void> {\n const state = this.guildStates.get(guildId);\n if (!state) {\n throw new Error('Not connected to voice in this guild');\n }\n\n // Build FFmpeg args for filters and seeking\n const ffmpegArgs: string[] = ['-analyzeduration', '0'];\n\n // Add seek if specified\n if (options.seek && options.seek > 0) {\n ffmpegArgs.push('-ss', String(options.seek / 1000)); // Convert ms to seconds\n }\n\n // Add filters if any are active\n if (state.filters.size > 0) {\n const filterArgs = this.buildFilterArgs(state.filters);\n ffmpegArgs.push(...filterArgs);\n }\n\n // Create audio resource with FFmpeg args if needed\n const resource = createAudioResource(source, {\n inlineVolume: true,\n inputType: ffmpegArgs.length > 2 ? undefined : undefined,\n });\n\n // Set volume\n const volume = options.volume ?? state.volume;\n resource.volume?.setVolume(volume / 100);\n\n // Track playback state\n state.currentResource = resource;\n state.startTime = Date.now() - (options.seek ?? 0);\n state.pausedAt = 0;\n\n // Play\n state.player.play(resource);\n state.paused = false;\n }\n\n /**\n * Build FFmpeg filter arguments from active filters\n */\n private buildFilterArgs(filters: Set<AudioFilter>): string[] {\n if (filters.size === 0) return [];\n\n const filterStrings: string[] = [];\n for (const filter of filters) {\n const args = FILTER_ARGS[filter];\n if (args) {\n // Extract the filter string from -af argument\n const afIndex = args.indexOf('-af');\n if (afIndex !== -1 && args[afIndex + 1]) {\n filterStrings.push(args[afIndex + 1]);\n }\n }\n }\n\n if (filterStrings.length === 0) return [];\n return ['-af', filterStrings.join(',')];\n }\n\n /**\n * Add a track to the queue\n */\n addToQueue(\n guildId: string,\n item: QueueItem,\n position?: number | 'next' | 'last'\n ): number {\n const state = this.guildStates.get(guildId);\n if (!state) {\n throw new Error('Not connected to voice in this guild');\n }\n\n // Check queue size limit\n const maxSize = this.config.max_queue_size ?? 1000;\n if (state.queue.length >= maxSize) {\n throw new Error(`Queue is full (max ${maxSize} tracks)`);\n }\n\n if (position === 'next' || position === 0) {\n state.queue.unshift(item);\n return 0;\n } else if (position === 'last' || position === undefined) {\n state.queue.push(item);\n return state.queue.length - 1;\n } else if (typeof position === 'number') {\n state.queue.splice(position, 0, item);\n return position;\n }\n\n state.queue.push(item);\n return state.queue.length - 1;\n }\n\n /**\n * Remove a track from the queue\n */\n removeFromQueue(guildId: string, position: number): QueueItem | null {\n const state = this.guildStates.get(guildId);\n if (!state) return null;\n\n const [removed] = state.queue.splice(position, 1);\n return removed ?? null;\n }\n\n /**\n * Clear the queue\n */\n clearQueue(guildId: string): number {\n const state = this.guildStates.get(guildId);\n if (!state) return 0;\n\n const count = state.queue.length;\n state.queue = [];\n return count;\n }\n\n /**\n * Shuffle the queue\n */\n shuffleQueue(guildId: string): void {\n const state = this.guildStates.get(guildId);\n if (!state) return;\n\n for (let i = state.queue.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [state.queue[i], state.queue[j]] = [state.queue[j]!, state.queue[i]!];\n }\n }\n\n /**\n * Skip the current track\n */\n skip(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.stop();\n return true;\n }\n\n /**\n * Pause playback\n */\n pause(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.pause();\n state.paused = true;\n state.pausedAt = Date.now();\n return true;\n }\n\n /**\n * Resume playback\n */\n resume(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n // Adjust start time to account for pause duration\n if (state.pausedAt > 0) {\n const pauseDuration = Date.now() - state.pausedAt;\n state.startTime += pauseDuration;\n }\n\n state.player.unpause();\n state.paused = false;\n state.pausedAt = 0;\n return true;\n }\n\n /**\n * Stop playback\n */\n stop(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.stop();\n state.queue = [];\n state.currentTrack = null;\n return true;\n }\n\n /**\n * Set volume\n */\n setVolume(guildId: string, volume: number): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.volume = Math.max(0, Math.min(200, volume));\n return true;\n }\n\n /**\n * Set loop mode\n */\n setLoopMode(guildId: string, mode: QueueLoopMode): void {\n const state = this.guildStates.get(guildId);\n if (!state) return;\n\n state.loopMode = mode;\n }\n\n /**\n * Seek to a position in the current track\n */\n async seek(guildId: string, position: number): Promise<boolean> {\n const state = this.guildStates.get(guildId);\n if (!state || !state.currentTrack) return false;\n\n // Replay the track from the specified position\n await this.play(guildId, state.currentTrack.url, {\n seek: position,\n volume: state.volume,\n });\n\n return true;\n }\n\n /**\n * Get current playback position in milliseconds\n */\n getPlaybackPosition(guildId: string): number {\n const state = this.guildStates.get(guildId);\n if (!state) return 0;\n\n if (state.paused && state.pausedAt > 0) {\n return state.pausedAt - state.startTime;\n }\n\n if (state.startTime > 0) {\n return Date.now() - state.startTime;\n }\n\n return 0;\n }\n\n /**\n * Set an audio filter\n */\n async setFilter(guildId: string, filter: AudioFilter, enabled: boolean): Promise<boolean> {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n const hadFilter = state.filters.has(filter);\n\n if (enabled) {\n state.filters.add(filter);\n } else {\n state.filters.delete(filter);\n }\n\n // Only restart playback if filter state changed and something is playing\n if (hadFilter !== enabled && state.currentTrack) {\n const currentPosition = this.getPlaybackPosition(guildId);\n await this.play(guildId, state.currentTrack.url, {\n seek: currentPosition,\n volume: state.volume,\n });\n }\n\n return true;\n }\n\n /**\n * Get active filters\n */\n getFilters(guildId: string): AudioFilter[] {\n const state = this.guildStates.get(guildId);\n if (!state) return [];\n return [...state.filters];\n }\n\n /**\n * Clear all filters\n */\n async clearFilters(guildId: string): Promise<boolean> {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n if (state.filters.size === 0) return true;\n\n state.filters.clear();\n\n // Restart playback without filters if something is playing\n if (state.currentTrack) {\n const currentPosition = this.getPlaybackPosition(guildId);\n await this.play(guildId, state.currentTrack.url, {\n seek: currentPosition,\n volume: state.volume,\n });\n }\n\n return true;\n }\n\n /**\n * Search for music tracks\n * This provides a basic implementation that handles URLs directly\n * For full search functionality, integrate with youtube-sr, play-dl, or similar\n */\n async search(query: string, options: { limit?: number; source?: string } = {}): Promise<SearchResult[]> {\n const limit = options.limit ?? 5;\n\n // If it's already a URL, return it directly as a result\n if (this.isUrl(query)) {\n return [{\n url: query,\n title: this.extractTitleFromUrl(query),\n duration: 0,\n thumbnail: null,\n }];\n }\n\n // Try to dynamically import play-dl for search functionality\n try {\n // @ts-ignore - Optional dependency, may not be installed\n const playDl = await import(/* webpackIgnore: true */ 'play-dl').catch(() => null);\n if (playDl) {\n const results = await playDl.search(query, { limit, source: { youtube: 'video' } });\n return results.map((result: any) => ({\n url: result.url,\n title: result.title ?? query,\n duration: result.durationInSec ? result.durationInSec * 1000 : 0,\n thumbnail: result.thumbnails?.[0]?.url ?? null,\n author: result.channel?.name,\n }));\n }\n } catch {\n // play-dl not available\n }\n\n // Try youtube-sr as fallback\n try {\n // @ts-ignore - Optional dependency, may not be installed\n const ytsr = await import(/* webpackIgnore: true */ 'youtube-sr').catch(() => null);\n if (ytsr && ytsr.default) {\n const results = await ytsr.default.search(query, { limit, type: 'video' });\n return results.map((result: any) => ({\n url: result.url,\n title: result.title ?? query,\n duration: result.duration ?? 0,\n thumbnail: result.thumbnail?.url ?? null,\n author: result.channel?.name,\n }));\n }\n } catch {\n // youtube-sr not available\n }\n\n // Fallback: return a search URL that can be resolved later\n return [{\n url: `ytsearch:${query}`,\n title: query,\n duration: 0,\n thumbnail: null,\n }];\n }\n\n /**\n * Check if a string is a URL\n */\n private isUrl(str: string): boolean {\n try {\n const url = new URL(str);\n return url.protocol === 'http:' || url.protocol === 'https:';\n } catch {\n return false;\n }\n }\n\n /**\n * Extract a title from a URL\n */\n private extractTitleFromUrl(url: string): string {\n try {\n const parsed = new URL(url);\n // Try to get video ID from YouTube URLs\n if (parsed.hostname.includes('youtube.com') || parsed.hostname.includes('youtu.be')) {\n const videoId = parsed.searchParams.get('v') || parsed.pathname.slice(1);\n return `YouTube Video (${videoId})`;\n }\n // For other URLs, use the pathname\n const parts = parsed.pathname.split('/');\n const filename = parts[parts.length - 1];\n return filename || parsed.hostname;\n } catch {\n return url;\n }\n }\n\n /**\n * Get the current state for a guild\n */\n getState(guildId: string): GuildVoiceState | undefined {\n return this.guildStates.get(guildId);\n }\n\n /**\n * Check if connected to a guild\n */\n isConnected(guildId: string): boolean {\n return this.guildStates.has(guildId);\n }\n\n /**\n * Get the queue for a guild\n */\n getQueue(guildId: string): QueueItem[] {\n return this.guildStates.get(guildId)?.queue ?? [];\n }\n\n /**\n * Get the current track for a guild\n */\n getCurrentTrack(guildId: string): QueueItem | null {\n return this.guildStates.get(guildId)?.currentTrack ?? null;\n }\n\n /**\n * Handle track end (play next or loop)\n */\n private async handleTrackEnd(guildId: string): Promise<void> {\n const state = this.guildStates.get(guildId);\n if (!state) return;\n\n // Handle loop modes\n if (state.loopMode === 'track' && state.currentTrack) {\n await this.play(guildId, state.currentTrack.url);\n return;\n }\n\n if (state.loopMode === 'queue' && state.currentTrack) {\n state.queue.push(state.currentTrack);\n }\n\n // Play next track\n const next = state.queue.shift();\n if (next) {\n state.currentTrack = next;\n await this.play(guildId, next.url);\n } else {\n state.currentTrack = null;\n }\n }\n\n /**\n * Disconnect from all voice channels\n */\n disconnectAll(): void {\n for (const [guildId] of this.guildStates) {\n this.leave(guildId);\n }\n }\n}\n\n/**\n * Create a voice manager\n */\nexport function createVoiceManager(): VoiceManager {\n return new VoiceManager();\n}\n","/**\n * Video/streaming detection and notifications\n */\n\nimport type { Client, VoiceState, GuildMember, TextChannel, Role } from 'discord.js';\nimport type { VideoConfig } from '@furlow/schema';\n\nexport interface StreamEvent {\n type: 'start' | 'stop';\n member: GuildMember;\n channelId: string;\n guildId: string;\n timestamp: Date;\n}\n\nexport interface StreamInfo {\n memberId: string;\n username: string;\n channelId: string;\n startedAt: Date;\n}\n\nexport type StreamEventCallback = (event: StreamEvent) => void | Promise<void>;\n\nexport class VideoManager {\n private client: Client;\n private config: VideoConfig = {};\n private streamingMembers: Map<string, Map<string, StreamInfo>> = new Map(); // guildId -> Map<memberId, StreamInfo>\n private listeners: StreamEventCallback[] = [];\n private initialized = false;\n\n constructor(client: Client) {\n this.client = client;\n }\n\n /**\n * Configure and initialize the video manager\n */\n configure(config: VideoConfig): void {\n this.config = config;\n\n if (config.stream_detection && !this.initialized) {\n this.setupListener();\n this.initialized = true;\n }\n }\n\n /**\n * Set up voice state update listener for stream detection\n */\n private setupListener(): void {\n this.client.on('voiceStateUpdate', (oldState, newState) => {\n this.handleVoiceStateUpdate(oldState, newState);\n });\n }\n\n /**\n * Handle voice state update for stream detection\n */\n private async handleVoiceStateUpdate(\n oldState: VoiceState,\n newState: VoiceState\n ): Promise<void> {\n const guildId = newState.guild.id;\n const memberId = newState.member?.id;\n\n if (!memberId || !newState.member) return;\n\n // Get or create streaming map for guild\n if (!this.streamingMembers.has(guildId)) {\n this.streamingMembers.set(guildId, new Map());\n }\n const streaming = this.streamingMembers.get(guildId)!;\n\n const wasStreaming = oldState.streaming;\n const isStreaming = newState.streaming;\n\n // Stream started\n if (!wasStreaming && isStreaming && newState.channelId) {\n const streamInfo: StreamInfo = {\n memberId,\n username: newState.member.user.username,\n channelId: newState.channelId,\n startedAt: new Date(),\n };\n streaming.set(memberId, streamInfo);\n\n const event: StreamEvent = {\n type: 'start',\n member: newState.member,\n channelId: newState.channelId,\n guildId,\n timestamp: new Date(),\n };\n\n await this.emit(event);\n await this.sendNotification(event);\n }\n\n // Stream stopped\n if (wasStreaming && !isStreaming) {\n streaming.delete(memberId);\n const event: StreamEvent = {\n type: 'stop',\n member: newState.member,\n channelId: oldState.channelId ?? '',\n guildId,\n timestamp: new Date(),\n };\n await this.emit(event);\n }\n\n // Left voice while streaming\n if (wasStreaming && !newState.channelId) {\n streaming.delete(memberId);\n const event: StreamEvent = {\n type: 'stop',\n member: newState.member,\n channelId: oldState.channelId ?? '',\n guildId,\n timestamp: new Date(),\n };\n await this.emit(event);\n }\n }\n\n /**\n * Emit a stream event to all listeners\n */\n private async emit(event: StreamEvent): Promise<void> {\n for (const listener of this.listeners) {\n try {\n await listener(event);\n } catch (error) {\n console.error('Stream event listener error:', error);\n }\n }\n }\n\n /**\n * Send notification when a stream starts\n */\n private async sendNotification(event: StreamEvent): Promise<void> {\n if (event.type !== 'start' || !this.config.notify_channel) {\n return;\n }\n\n try {\n const guild = this.client.guilds.cache.get(event.guildId);\n if (!guild) return;\n\n // Resolve channel (could be an ID or expression result)\n const channelId = typeof this.config.notify_channel === 'string'\n ? this.config.notify_channel.replace(/[<#>]/g, '') // Handle mention format\n : String(this.config.notify_channel);\n\n const channel = guild.channels.cache.get(channelId) as TextChannel | undefined;\n if (!channel || !channel.isTextBased()) return;\n\n // Build notification message\n const voiceChannel = guild.channels.cache.get(event.channelId);\n const voiceChannelName = voiceChannel?.name ?? 'Unknown Channel';\n\n let content = `**${event.member.displayName}** started streaming in **${voiceChannelName}**!`;\n\n // Add role mention if configured\n if (this.config.notify_role) {\n const roleId = typeof this.config.notify_role === 'string'\n ? this.config.notify_role.replace(/[<@&>]/g, '')\n : String(this.config.notify_role);\n\n const role = guild.roles.cache.get(roleId);\n if (role) {\n content = `${role.toString()} ${content}`;\n }\n }\n\n await channel.send({\n content,\n allowedMentions: {\n roles: this.config.notify_role ? [String(this.config.notify_role).replace(/[<@&>]/g, '')] : [],\n },\n });\n } catch (error) {\n console.error('Failed to send stream notification:', error);\n }\n }\n\n /**\n * Register a stream event listener\n */\n onStreamEvent(callback: StreamEventCallback): () => void {\n this.listeners.push(callback);\n return () => {\n const index = this.listeners.indexOf(callback);\n if (index !== -1) {\n this.listeners.splice(index, 1);\n }\n };\n }\n\n /**\n * Get all members currently streaming in a guild\n */\n getStreamingMembers(guildId: string): StreamInfo[] {\n const streaming = this.streamingMembers.get(guildId);\n if (!streaming) return [];\n return [...streaming.values()];\n }\n\n /**\n * Get streaming info for a specific member\n */\n getStreamInfo(guildId: string, memberId: string): StreamInfo | null {\n return this.streamingMembers.get(guildId)?.get(memberId) ?? null;\n }\n\n /**\n * Check if a member is streaming\n */\n isStreaming(guildId: string, memberId: string): boolean {\n return this.streamingMembers.get(guildId)?.has(memberId) ?? false;\n }\n\n /**\n * Get stream count in a guild\n */\n getStreamCount(guildId: string): number {\n return this.streamingMembers.get(guildId)?.size ?? 0;\n }\n\n /**\n * Get total active streams across all guilds\n */\n getTotalStreamCount(): number {\n let count = 0;\n for (const streaming of this.streamingMembers.values()) {\n count += streaming.size;\n }\n return count;\n }\n\n /**\n * Get all active streams across all guilds\n */\n getAllActiveStreams(): Array<{ guildId: string; streams: StreamInfo[] }> {\n const result: Array<{ guildId: string; streams: StreamInfo[] }> = [];\n for (const [guildId, streaming] of this.streamingMembers) {\n if (streaming.size > 0) {\n result.push({ guildId, streams: [...streaming.values()] });\n }\n }\n return result;\n }\n\n /**\n * Check if stream detection is enabled\n */\n isEnabled(): boolean {\n return this.config.stream_detection ?? false;\n }\n\n /**\n * Get current configuration\n */\n getConfig(): VideoConfig {\n return { ...this.config };\n }\n}\n\n/**\n * Create a video manager\n */\nexport function createVideoManager(client: Client): VideoManager {\n return new VideoManager(client);\n}\n"],"mappings":";AAIA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAQP,IAAM,aAAgD;AAAA,EACpD,QAAQ,kBAAkB;AAAA,EAC1B,eAAe,kBAAkB;AAAA,EACjC,kBAAkB,kBAAkB;AAAA,EACpC,2BAA2B,kBAAkB;AAAA,EAC7C,oBAAoB,kBAAkB;AAAA,EACtC,gBAAgB,kBAAkB;AAAA,EAClC,eAAe,kBAAkB;AAAA,EACjC,oBAAoB,kBAAkB;AAAA,EACtC,iBAAiB,kBAAkB;AAAA,EACnC,gBAAgB,kBAAkB;AAAA,EAClC,yBAAyB,kBAAkB;AAAA,EAC3C,sBAAsB,kBAAkB;AAAA,EACxC,iBAAiB,kBAAkB;AAAA,EACnC,0BAA0B,kBAAkB;AAAA,EAC5C,uBAAuB,kBAAkB;AAAA,EACzC,iBAAiB,kBAAkB;AAAA,EACnC,wBAAwB,kBAAkB;AAAA,EAC1C,+BAA+B,kBAAkB;AAAA,EACjD,2BAA2B,kBAAkB;AAC/C;AAEO,IAAM,eAAN,MAAmB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,SAA8B;AACxC,SAAK,QAAQ,QAAQ;AACrB,SAAK,OAAO,QAAQ;AAEpB,UAAM,UAAU,KAAK,eAAe,QAAQ,KAAK,OAAO;AAExD,UAAM,gBAA+B;AAAA,MACnC;AAAA,MACA,UAAU;AAAA,QACR,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,MACX;AAAA,IACF;AAEA,SAAK,SAAS,IAAI,OAAO,aAAa;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,QAA6C;AAClE,QAAI,CAAC,QAAQ;AAEX,aAAO;AAAA,QACL,kBAAkB;AAAA,QAClB,kBAAkB;AAAA,QAClB,kBAAkB;AAAA,QAClB,kBAAkB;AAAA,MACpB;AAAA,IACF;AAEA,QAAI,OAAO,MAAM;AAEf,aAAO,KAAK,kBAAkB;AAAA,IAChC;AAEA,QAAI,OAAO,UAAU;AACnB,aAAO,OAAO,SACX,IAAI,CAAC,WAAW,WAAW,MAAM,CAAC,EAClC,OAAO,CAAC,MAA8B,MAAM,MAAS;AAAA,IAC1D;AAEA,WAAO,CAAC,kBAAkB,MAAM;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAyC;AAC/C,UAAM,UAAU,oBAAI,IAAuB;AAG3C,YAAQ,IAAI,kBAAkB,MAAM;AAGpC,QAAI,KAAK,KAAK,QAAQ;AACpB,iBAAW,WAAW,KAAK,KAAK,QAAQ;AACtC,gBAAQ,QAAQ,OAAO;AAAA,UACrB,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AACH,oBAAQ,IAAI,kBAAkB,aAAa;AAC3C,oBAAQ,IAAI,kBAAkB,cAAc;AAC5C;AAAA,UACF,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AACH,oBAAQ,IAAI,kBAAkB,YAAY;AAC1C;AAAA,UACF,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AACH,oBAAQ,IAAI,kBAAkB,gBAAgB;AAC9C;AAAA,UACF,KAAK;AAAA,UACL,KAAK;AACH,oBAAQ,IAAI,kBAAkB,qBAAqB;AACnD;AAAA,UACF,KAAK;AACH,oBAAQ,IAAI,kBAAkB,cAAc;AAC5C;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAGA,QAAI,KAAK,KAAK,UAAU,QAAQ;AAC9B,cAAQ,IAAI,kBAAkB,aAAa;AAAA,IAC7C;AAGA,QAAI,KAAK,KAAK,OAAO;AACnB,cAAQ,IAAI,kBAAkB,gBAAgB;AAAA,IAChD;AAEA,WAAO,CAAC,GAAG,OAAO;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,UAAM,KAAK,OAAO,MAAM,KAAK,KAAK;AAGlC,QAAI,CAAC,KAAK,OAAO,QAAQ,GAAG;AAC1B,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,aAAK,OAAO,KAAK,SAAS,MAAM,QAAQ,CAAC;AAAA,MAC3C,CAAC;AAAA,IACH;AAGA,UAAM,KAAK,cAAc;AAGzB,UAAM,KAAK,cAAc;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,UAAM,KAAK,OAAO,QAAQ;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,gBAA+B;AAC3C,QAAI,CAAC,KAAK,KAAK,SAAU;AAEzB,UAAM,WAAW,KAAK,KAAK;AAG3B,QAAI,SAAS,QAAQ,KAAK,OAAO,MAAM,aAAa,SAAS,MAAM;AACjE,UAAI;AACF,cAAM,KAAK,OAAO,MAAM,YAAY,SAAS,IAAI;AAAA,MACnD,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,QAAI,SAAS,QAAQ;AACnB,UAAI;AACF,cAAM,KAAK,OAAO,MAAM,UAAU,SAAS,MAAM;AAAA,MACnD,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,gBAA+B;AAC3C,QAAI,CAAC,KAAK,KAAK,SAAU;AAEzB,UAAM,WAAW,KAAK,KAAK;AAE3B,SAAK,OAAO,MAAM,YAAY;AAAA,MAC5B,QAAQ,SAAS,UAAU;AAAA,MAC3B,YAAY,SAAS,WACjB;AAAA,QACE;AAAA,UACE,MAAM,KAAK,gBAAgB,SAAS,SAAS,IAAI;AAAA,UACjD,MAAM,SAAS,SAAS;AAAA,UACxB,KAAK,SAAS,SAAS;AAAA,UACvB,OAAO,SAAS,SAAS;AAAA,QAC3B;AAAA,MACF,IACA;AAAA,IACN,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,MAAsB;AAC5C,UAAM,QAAgC;AAAA,MACpC,SAAS;AAAA,MACT,WAAW;AAAA,MACX,WAAW;AAAA,MACX,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,WAAW;AAAA,IACb;AACA,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,GACE,OACA,UACM;AACN,SAAK,OAAO,GAAG,OAAO,QAAQ;AAC9B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,KACE,OACA,UACM;AACN,SAAK,OAAO,KAAK,OAAO,QAAQ;AAChC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,YAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,UAAsB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,UAAmB;AACjB,WAAO,KAAK,OAAO,QAAQ;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,aAAqB;AACvB,WAAO,KAAK,OAAO,OAAO,MAAM;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,YAAoB;AACtB,WAAO,KAAK,OAAO,OAAO,MAAM,OAAO,CAAC,KAAK,UAAU,MAAM,MAAM,aAAa,CAAC;AAAA,EACnF;AACF;AAKO,SAAS,aAAa,SAA4C;AACvE,SAAO,IAAI,aAAa,OAAO;AACjC;;;AClSO,IAAM,iBAAN,MAAqB;AAAA,EAClB;AAAA,EACA;AAAA,EACA,oBAAoB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,QAAgB,UAAiC,CAAC,GAAG;AAC/D,SAAK,SAAS;AACd,SAAK,SAAS,QAAQ,UAAU,CAAC;AAEjC,UAAM,YAAY,KAAK,OAAO,aAAa,CAAC;AAC5C,SAAK,uBAAuB,UAAU,eAAe;AACrD,UAAM,YAAY,UAAU,cAAc;AAC1C,UAAM,WAAW,UAAU,aAAa;AACxC,SAAK,YAAY,OAAO,cAAc,WAAW,YAAY,KAAK,cAAc,SAAS;AACzF,SAAK,WAAW,OAAO,aAAa,WAAW,WAAW,KAAK,cAAc,QAAQ;AACrF,SAAK,kBAAkB,UAAU,WAAW;AAE5C,SAAK,eAAe,OAAO;AAAA,EAC7B;AAAA,EAEQ,eAAe,SAAsC;AAC3D,SAAK,OAAO,GAAG,cAAc,CAAC,YAAY;AACxC,cAAQ,IAAI,SAAS,OAAO,QAAQ;AACpC,WAAK,oBAAoB;AAAA,IAC3B,CAAC;AAED,SAAK,OAAO,GAAG,mBAAmB,CAAC,OAAO,YAAY;AACpD,cAAQ,IAAI,SAAS,OAAO,kBAAkB,MAAM,IAAI,EAAE;AAC1D,cAAQ,eAAe;AAAA,IACzB,CAAC;AAED,SAAK,OAAO,GAAG,qBAAqB,CAAC,YAAY;AAC/C,cAAQ,IAAI,SAAS,OAAO,kBAAkB;AAC9C,WAAK;AACL,cAAQ,cAAc;AAAA,IACxB,CAAC;AAED,SAAK,OAAO,GAAG,cAAc,CAAC,OAAO,YAAY;AAC/C,cAAQ,MAAM,SAAS,OAAO,WAAW,KAAK;AAC9C,cAAQ,UAAU,KAAK;AAAA,IACzB,CAAC;AAED,SAAK,OAAO,GAAG,eAAe,CAAC,SAAS,mBAAmB;AACzD,cAAQ,IAAI,SAAS,OAAO,sBAAsB,cAAc,SAAS;AACzE,WAAK,oBAAoB;AAAA,IAC3B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,oBAA4B;AAC1B,YAAQ,KAAK,iBAAiB;AAAA,MAC5B,KAAK;AACH,eAAO,KAAK;AAAA,UACV,KAAK,YAAY,KAAK,IAAI,GAAG,KAAK,iBAAiB;AAAA,UACnD,KAAK;AAAA,QACP;AAAA,MACF,KAAK;AACH,eAAO,KAAK;AAAA,UACV,KAAK,aAAa,KAAK,oBAAoB;AAAA,UAC3C,KAAK;AAAA,QACP;AAAA,MACF,KAAK;AAAA,MACL;AACE,eAAO,KAAK;AAAA,IAChB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,kBAA2B;AACzB,WAAO,KAAK,oBAAoB,KAAK;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,uBAA+B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,yBAA+B;AAC7B,SAAK,oBAAoB;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,UAAkB;AAChB,WAAO,KAAK,OAAO,GAAG;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,eAAoE;AAClE,WAAO,CAAC,GAAG,KAAK,OAAO,GAAG,OAAO,OAAO,CAAC,EAAE,IAAI,CAAC,WAAW;AAAA,MACzD,IAAI,MAAM;AAAA,MACV,QAAQ,MAAM,OAAO,SAAS;AAAA,MAC9B,MAAM,MAAM;AAAA,IACd,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,UAA0B;AAC9C,UAAM,QAAQ,SAAS,MAAM,oBAAoB;AACjD,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,QAAQ,SAAS,MAAM,CAAC,GAAI,EAAE;AACpC,UAAM,OAAO,MAAM,CAAC,KAAK;AAEzB,YAAQ,MAAM;AAAA,MACZ,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO,QAAQ;AAAA,MACjB,KAAK;AACH,eAAO,QAAQ,KAAK;AAAA,MACtB,KAAK;AACH,eAAO,QAAQ,KAAK,KAAK;AAAA,MAC3B;AACE,eAAO,QAAQ;AAAA,IACnB;AAAA,EACF;AACF;AAKO,SAAS,qBACd,QACA,SACgB;AAChB,SAAO,IAAI,eAAe,QAAQ,OAAO;AAC3C;;;AC3JA;AAAA,EAEE;AAAA,EACA;AAAA,EAQA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAcA,IAAM,qBAAN,MAAyB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA,kBAAiF,oBAAI,IAAI;AAAA,EACzF,iBAAsE,oBAAI,IAAI;AAAA,EAC9E,iBAAgF,oBAAI,IAAI;AAAA,EACxF,gBAA0E,oBAAI,IAAI;AAAA,EAClF,sBAA2F,oBAAI,IAAI;AAAA,EACnG,yBAAiG,oBAAI,IAAI;AAAA,EAEjH,YAAY,SAAoC;AAC9C,SAAK,SAAS,QAAQ;AACtB,SAAK,WAAW,QAAQ;AACxB,SAAK,UAAU,QAAQ;AACvB,SAAK,OAAO,IAAI,KAAK,EAAE,SAAS,KAAK,CAAC,EAAE,SAAS,QAAQ,KAAK;AAE9D,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAsB;AAC5B,SAAK,OAAO,GAAG,qBAAqB,OAAO,gBAAgB;AACzD,UAAI;AACF,cAAM,KAAK,kBAAkB,WAAW;AAAA,MAC1C,SAAS,OAAO;AACd,gBAAQ,MAAM,sBAAsB,KAAK;AAEzC,YAAI,YAAY,YAAY,KAAK,CAAC,YAAY,WAAW,CAAC,YAAY,UAAU;AAC9E,gBAAM,YAAY,MAAM;AAAA,YACtB,SAAS;AAAA,YACT,WAAW;AAAA,UACb,CAAC,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QACnB;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,kBAAkB,aAAyC;AACvE,QAAI,YAAY,mBAAmB,GAAG;AACpC,YAAM,UAAU,KAAK,gBAAgB,IAAI,YAAY,WAAW;AAChE,UAAI,SAAS;AACX,cAAM,QAAQ,WAAW;AAAA,MAC3B;AAAA,IACF,WAAW,YAAY,SAAS,GAAG;AAEjC,YAAM,UACJ,KAAK,eAAe,IAAI,YAAY,QAAQ,KAC5C,KAAK,kBAAkB,KAAK,gBAAgB,YAAY,QAAQ;AAClE,UAAI,SAAS;AACX,cAAM,QAAQ,WAAW;AAAA,MAC3B;AAAA,IACF,WAAW,YAAY,mBAAmB,GAAG;AAC3C,YAAM,UACJ,KAAK,eAAe,IAAI,YAAY,QAAQ,KAC5C,KAAK,kBAAkB,KAAK,gBAAgB,YAAY,QAAQ;AAClE,UAAI,SAAS;AACX,cAAM,QAAQ,WAAW;AAAA,MAC3B;AAAA,IACF,WAAW,YAAY,cAAc,GAAG;AACtC,YAAM,UACJ,KAAK,cAAc,IAAI,YAAY,QAAQ,KAC3C,KAAK,kBAAkB,KAAK,eAAe,YAAY,QAAQ;AACjE,UAAI,SAAS;AACX,cAAM,QAAQ,WAAW;AAAA,MAC3B;AAAA,IACF,WAAW,YAAY,yBAAyB,GAAG;AACjD,YAAM,UAAU,KAAK,oBAAoB,IAAI,YAAY,WAAW;AACpE,UAAI,SAAS;AACX,cAAM,QAAQ,WAAW;AAAA,MAC3B;AAAA,IACF,WAAW,YAAY,4BAA4B,GAAG;AACpD,YAAM,UAAU,KAAK,uBAAuB,IAAI,YAAY,WAAW;AACvE,UAAI,SAAS;AACX,cAAM,QAAQ,WAAW;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,kBACN,UACA,UACe;AACf,eAAW,CAAC,KAAK,OAAO,KAAK,UAAU;AACrC,UAAI,IAAI,SAAS,GAAG,KAAK,SAAS,WAAW,IAAI,MAAM,GAAG,EAAE,CAAC,GAAG;AAC9D,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,UACE,MACA,SACM;AACN,SAAK,gBAAgB,IAAI,MAAM,OAAO;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,SACE,UACA,SACM;AACN,SAAK,eAAe,IAAI,UAAU,OAAO;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,SACE,UACA,SACM;AACN,SAAK,eAAe,IAAI,UAAU,OAAO;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,QACE,UACA,SACM;AACN,SAAK,cAAc,IAAI,UAAU,OAAO;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,cACE,MACA,SACM;AACN,SAAK,oBAAoB,IAAI,MAAM,OAAO;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,iBACE,MACA,SACM;AACN,SAAK,uBAAuB,IAAI,MAAM,OAAO;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBACJ,UACA,cACe;AACf,UAAM,gBAAgB,SAAS,IAAI,CAAC,QAAQ,KAAK,kBAAkB,GAAG,CAAC;AACvE,UAAM,mBAAmB,gBAAgB,CAAC,GAAG,IAAI,CAAC,QAAQ,KAAK,iBAAiB,GAAG,CAAC;AAEpF,UAAM,cAAc,CAAC,GAAG,eAAe,GAAG,eAAe;AAEzD,QAAI,KAAK,SAAS;AAEhB,YAAM,KAAK,KAAK;AAAA,QACd,OAAO,yBAAyB,KAAK,UAAU,KAAK,OAAO;AAAA,QAC3D,EAAE,MAAM,YAAY;AAAA,MACtB;AAAA,IACF,OAAO;AAEL,YAAM,KAAK,KAAK,IAAI,OAAO,oBAAoB,KAAK,QAAQ,GAAG;AAAA,QAC7D,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,kBACN,KACiD;AACjD,UAAM,UAAU,IAAI,oBAAoB,EACrC,QAAQ,IAAI,IAAI,EAChB,eAAe,IAAI,WAAW;AAEjC,QAAI,IAAI,kBAAkB,QAAW;AACnC,cAAQ,gBAAgB,IAAI,aAAa;AAAA,IAC3C;AAEA,QAAI,IAAI,MAAM;AACZ,cAAQ,QAAQ,IAAI;AAAA,IACtB;AAGA,QAAI,IAAI,SAAS;AACf,iBAAW,OAAO,IAAI,SAAS;AAC7B,aAAK,UAAU,SAAS,GAAG;AAAA,MAC7B;AAAA,IACF;AAGA,QAAI,IAAI,aAAa;AACnB,iBAAW,OAAO,IAAI,aAAa;AACjC,gBAAQ,cAAc,CAAC,eAAe;AACpC,qBAAW,QAAQ,IAAI,IAAI,EAAE,eAAe,IAAI,WAAW;AAC3D,cAAI,IAAI,SAAS;AACf,uBAAW,OAAO,IAAI,SAAS;AAC7B,mBAAK,UAAU,YAAY,GAAG;AAAA,YAChC;AAAA,UACF;AACA,iBAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,IACF;AAGA,QAAI,IAAI,mBAAmB;AACzB,iBAAW,SAAS,IAAI,mBAAmB;AACzC,gBAAQ,mBAAmB,CAAC,iBAAiB;AAC3C,uBAAa,QAAQ,MAAM,IAAI,EAAE,eAAe,MAAM,WAAW;AACjE,qBAAW,OAAO,MAAM,aAAa;AACnC,yBAAa,cAAc,CAAC,eAAe;AACzC,yBAAW,QAAQ,IAAI,IAAI,EAAE,eAAe,IAAI,WAAW;AAC3D,kBAAI,IAAI,SAAS;AACf,2BAAW,OAAO,IAAI,SAAS;AAC7B,uBAAK,UAAU,YAAY,GAAG;AAAA,gBAChC;AAAA,cACF;AACA,qBAAO;AAAA,YACT,CAAC;AAAA,UACH;AACA,iBAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,QAAQ,OAAO;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKQ,UAAU,SAAc,KAA0B;AACxD,UAAM,YAAY,MAAM,KAAK,oBAAoB,IAAI,IAAI,CAAC;AAE1D,QAAI,OAAO,QAAQ,SAAS,MAAM,YAAY;AAC5C,cAAQ,KAAK,wBAAwB,IAAI,IAAI,EAAE;AAC/C;AAAA,IACF;AAEA,YAAQ,SAAS,EAAE,CAAC,eAAoB;AACtC,iBAAW,QAAQ,IAAI,IAAI,EAAE,eAAe,IAAI,WAAW;AAE3D,UAAI,IAAI,UAAU;AAChB,mBAAW,YAAY,IAAI;AAAA,MAC7B;AAEA,UAAI,IAAI,WAAW,WAAW,YAAY;AACxC,mBAAW,WAAW,GAAG,IAAI,OAAO;AAAA,MACtC;AAEA,UAAI,IAAI,cAAc,UAAa,WAAW,aAAa;AACzD,mBAAW,YAAY,IAAI,SAAS;AAAA,MACtC;AAEA,UAAI,IAAI,cAAc,UAAa,WAAW,aAAa;AACzD,mBAAW,YAAY,IAAI,SAAS;AAAA,MACtC;AAEA,UAAI,IAAI,eAAe,UAAa,WAAW,cAAc;AAC3D,mBAAW,aAAa,IAAI,UAAU;AAAA,MACxC;AAEA,UAAI,IAAI,eAAe,UAAa,WAAW,cAAc;AAC3D,mBAAW,aAAa,IAAI,UAAU;AAAA,MACxC;AAEA,UAAI,IAAI,gBAAgB,WAAW,iBAAiB;AAClD,mBAAW,gBAAgB,IAAI;AAAA,MACjC;AAEA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAoB,MAAsB;AAChD,UAAM,MAA8B;AAAA,MAClC,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,MAAM;AAAA,MACN,SAAS;AAAA,MACT,MAAM;AAAA,MACN,aAAa;AAAA,MACb,YAAY;AAAA,IACd;AACA,WAAO,IAAI,IAAI,KAAK;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKQ,iBACN,KACmD;AACnD,UAAM,UAAU,IAAI,0BAA0B,EAC3C,QAAQ,IAAI,IAAI,EAChB;AAAA,MACC,IAAI,SAAS,SACT,uBAAuB,OACvB,uBAAuB;AAAA,IAC7B;AAEF,WAAO,QAAQ,OAAO;AAAA,EACxB;AACF;AAKO,SAAS,yBACd,SACoB;AACpB,SAAO,IAAI,mBAAmB,OAAO;AACvC;;;AClXA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AAuDP,IAAM,cAA6C;AAAA,EACjD,WAAW,CAAC,OAAO,4BAA4B;AAAA,EAC/C,WAAW,CAAC,OAAO,iDAAiD;AAAA,EACpE,WAAW,CAAC,OAAO,+CAA+C;AAAA,EAClE,MAAM,CAAC,OAAO,mBAAmB;AAAA,EACjC,QAAQ,CAAC,OAAO,6BAA6B;AAAA,EAC7C,YAAY,CAAC,OAAO,sBAAsB;AAAA,EAC1C,SAAS,CAAC,OAAO,uBAAuB;AAAA,EACxC,SAAS,CAAC,OAAO,mBAAmB;AAAA,EACpC,SAAS,CAAC,OAAO,mBAAmB;AAAA,EACpC,SAAS,CAAC,OAAO,UAAU;AAC7B;AAWO,IAAM,eAAN,MAAmB;AAAA,EAChB,cAA4C,oBAAI,IAAI;AAAA,EACpD,SAAsB,CAAC;AAAA;AAAA;AAAA;AAAA,EAK/B,UAAU,QAA2B;AACnC,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KACJ,SACA,UAAsD,CAAC,GAC7B;AAC1B,UAAM,UAAU,QAAQ,MAAM;AAG9B,UAAM,WAAW,mBAAmB,OAAO;AAC3C,QAAI,UAAU;AACZ,eAAS,QAAQ;AAAA,IACnB;AAEA,UAAM,aAAa,iBAAiB;AAAA,MAClC,WAAW,QAAQ;AAAA,MACnB;AAAA,MACA,gBAAgB,QAAQ,MAAM;AAAA,MAC9B,UAAU,QAAQ,YAAY,KAAK,OAAO,YAAY,aAAa;AAAA,MACnE,UAAU,QAAQ,YAAY,KAAK,OAAO,YAAY,aAAa;AAAA,IACrE,CAAC;AAGD,UAAM,YAAY,YAAY,sBAAsB,OAAO,GAAK;AAGhE,UAAM,SAAS,kBAAkB;AAGjC,WAAO,GAAG,kBAAkB,MAAM,MAAM;AACtC,WAAK,eAAe,OAAO;AAAA,IAC7B,CAAC;AAED,WAAO,GAAG,SAAS,CAAC,UAAU;AAC5B,cAAQ,MAAM,yBAAyB,OAAO,KAAK,KAAK;AACxD,WAAK,eAAe,OAAO;AAAA,IAC7B,CAAC;AAGD,eAAW,UAAU,MAAM;AAG3B,SAAK,YAAY,IAAI,SAAS;AAAA,MAC5B;AAAA,MACA;AAAA,MACA,OAAO,CAAC;AAAA,MACR,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,QAAQ,KAAK,OAAO,kBAAkB;AAAA,MACtC,UAAU,KAAK,OAAO,gBAAgB;AAAA,MACtC,SAAS,oBAAI,IAAI;AAAA,MACjB,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,UAAU;AAAA,IACZ,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAA0B;AAC9B,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,OAAO,KAAK;AAClB,UAAM,WAAW,QAAQ;AACzB,SAAK,YAAY,OAAO,OAAO;AAE/B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KACJ,SACA,QACA,UAA8C,CAAC,GAChC;AACf,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAGA,UAAM,aAAuB,CAAC,oBAAoB,GAAG;AAGrD,QAAI,QAAQ,QAAQ,QAAQ,OAAO,GAAG;AACpC,iBAAW,KAAK,OAAO,OAAO,QAAQ,OAAO,GAAI,CAAC;AAAA,IACpD;AAGA,QAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,YAAM,aAAa,KAAK,gBAAgB,MAAM,OAAO;AACrD,iBAAW,KAAK,GAAG,UAAU;AAAA,IAC/B;AAGA,UAAM,WAAW,oBAAoB,QAAQ;AAAA,MAC3C,cAAc;AAAA,MACd,WAAW,WAAW,SAAS,IAAI,SAAY;AAAA,IACjD,CAAC;AAGD,UAAM,SAAS,QAAQ,UAAU,MAAM;AACvC,aAAS,QAAQ,UAAU,SAAS,GAAG;AAGvC,UAAM,kBAAkB;AACxB,UAAM,YAAY,KAAK,IAAI,KAAK,QAAQ,QAAQ;AAChD,UAAM,WAAW;AAGjB,UAAM,OAAO,KAAK,QAAQ;AAC1B,UAAM,SAAS;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,SAAqC;AAC3D,QAAI,QAAQ,SAAS,EAAG,QAAO,CAAC;AAEhC,UAAM,gBAA0B,CAAC;AACjC,eAAW,UAAU,SAAS;AAC5B,YAAM,OAAO,YAAY,MAAM;AAC/B,UAAI,MAAM;AAER,cAAM,UAAU,KAAK,QAAQ,KAAK;AAClC,YAAI,YAAY,MAAM,KAAK,UAAU,CAAC,GAAG;AACvC,wBAAc,KAAK,KAAK,UAAU,CAAC,CAAC;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAEA,QAAI,cAAc,WAAW,EAAG,QAAO,CAAC;AACxC,WAAO,CAAC,OAAO,cAAc,KAAK,GAAG,CAAC;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,WACE,SACA,MACA,UACQ;AACR,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAGA,UAAM,UAAU,KAAK,OAAO,kBAAkB;AAC9C,QAAI,MAAM,MAAM,UAAU,SAAS;AACjC,YAAM,IAAI,MAAM,sBAAsB,OAAO,UAAU;AAAA,IACzD;AAEA,QAAI,aAAa,UAAU,aAAa,GAAG;AACzC,YAAM,MAAM,QAAQ,IAAI;AACxB,aAAO;AAAA,IACT,WAAW,aAAa,UAAU,aAAa,QAAW;AACxD,YAAM,MAAM,KAAK,IAAI;AACrB,aAAO,MAAM,MAAM,SAAS;AAAA,IAC9B,WAAW,OAAO,aAAa,UAAU;AACvC,YAAM,MAAM,OAAO,UAAU,GAAG,IAAI;AACpC,aAAO;AAAA,IACT;AAEA,UAAM,MAAM,KAAK,IAAI;AACrB,WAAO,MAAM,MAAM,SAAS;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,SAAiB,UAAoC;AACnE,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,CAAC,OAAO,IAAI,MAAM,MAAM,OAAO,UAAU,CAAC;AAChD,WAAO,WAAW;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAAyB;AAClC,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,QAAQ,MAAM,MAAM;AAC1B,UAAM,QAAQ,CAAC;AACf,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,SAAuB;AAClC,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO;AAEZ,aAAS,IAAI,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,KAAK;AAC/C,YAAM,IAAI,KAAK,MAAM,KAAK,OAAO,KAAK,IAAI,EAAE;AAC5C,OAAC,MAAM,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,MAAM,CAAC,GAAI,MAAM,MAAM,CAAC,CAAE;AAAA,IACtE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,KAAK,SAA0B;AAC7B,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,OAAO,KAAK;AAClB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAA0B;AAC9B,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,OAAO,MAAM;AACnB,UAAM,SAAS;AACf,UAAM,WAAW,KAAK,IAAI;AAC1B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,SAA0B;AAC/B,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAGnB,QAAI,MAAM,WAAW,GAAG;AACtB,YAAM,gBAAgB,KAAK,IAAI,IAAI,MAAM;AACzC,YAAM,aAAa;AAAA,IACrB;AAEA,UAAM,OAAO,QAAQ;AACrB,UAAM,SAAS;AACf,UAAM,WAAW;AACjB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,KAAK,SAA0B;AAC7B,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,OAAO,KAAK;AAClB,UAAM,QAAQ,CAAC;AACf,UAAM,eAAe;AACrB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,SAAiB,QAAyB;AAClD,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,MAAM,CAAC;AAChD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,SAAiB,MAA2B;AACtD,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO;AAEZ,UAAM,WAAW;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAK,SAAiB,UAAoC;AAC9D,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,SAAS,CAAC,MAAM,aAAc,QAAO;AAG1C,UAAM,KAAK,KAAK,SAAS,MAAM,aAAa,KAAK;AAAA,MAC/C,MAAM;AAAA,MACN,QAAQ,MAAM;AAAA,IAChB,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB,SAAyB;AAC3C,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,QAAI,MAAM,UAAU,MAAM,WAAW,GAAG;AACtC,aAAO,MAAM,WAAW,MAAM;AAAA,IAChC;AAEA,QAAI,MAAM,YAAY,GAAG;AACvB,aAAO,KAAK,IAAI,IAAI,MAAM;AAAA,IAC5B;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,SAAiB,QAAqB,SAAoC;AACxF,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,YAAY,MAAM,QAAQ,IAAI,MAAM;AAE1C,QAAI,SAAS;AACX,YAAM,QAAQ,IAAI,MAAM;AAAA,IAC1B,OAAO;AACL,YAAM,QAAQ,OAAO,MAAM;AAAA,IAC7B;AAGA,QAAI,cAAc,WAAW,MAAM,cAAc;AAC/C,YAAM,kBAAkB,KAAK,oBAAoB,OAAO;AACxD,YAAM,KAAK,KAAK,SAAS,MAAM,aAAa,KAAK;AAAA,QAC/C,MAAM;AAAA,QACN,QAAQ,MAAM;AAAA,MAChB,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAAgC;AACzC,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO,CAAC;AACpB,WAAO,CAAC,GAAG,MAAM,OAAO;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,SAAmC;AACpD,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,QAAI,MAAM,QAAQ,SAAS,EAAG,QAAO;AAErC,UAAM,QAAQ,MAAM;AAGpB,QAAI,MAAM,cAAc;AACtB,YAAM,kBAAkB,KAAK,oBAAoB,OAAO;AACxD,YAAM,KAAK,KAAK,SAAS,MAAM,aAAa,KAAK;AAAA,QAC/C,MAAM;AAAA,QACN,QAAQ,MAAM;AAAA,MAChB,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OAAO,OAAe,UAA+C,CAAC,GAA4B;AACtG,UAAM,QAAQ,QAAQ,SAAS;AAG/B,QAAI,KAAK,MAAM,KAAK,GAAG;AACrB,aAAO,CAAC;AAAA,QACN,KAAK;AAAA,QACL,OAAO,KAAK,oBAAoB,KAAK;AAAA,QACrC,UAAU;AAAA,QACV,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAGA,QAAI;AAEF,YAAM,SAAS,MAAM;AAAA;AAAA,QAAiC;AAAA,MAAS,EAAE,MAAM,MAAM,IAAI;AACjF,UAAI,QAAQ;AACV,cAAM,UAAU,MAAM,OAAO,OAAO,OAAO,EAAE,OAAO,QAAQ,EAAE,SAAS,QAAQ,EAAE,CAAC;AAClF,eAAO,QAAQ,IAAI,CAAC,YAAiB;AAAA,UACnC,KAAK,OAAO;AAAA,UACZ,OAAO,OAAO,SAAS;AAAA,UACvB,UAAU,OAAO,gBAAgB,OAAO,gBAAgB,MAAO;AAAA,UAC/D,WAAW,OAAO,aAAa,CAAC,GAAG,OAAO;AAAA,UAC1C,QAAQ,OAAO,SAAS;AAAA,QAC1B,EAAE;AAAA,MACJ;AAAA,IACF,QAAQ;AAAA,IAER;AAGA,QAAI;AAEF,YAAM,OAAO,MAAM;AAAA;AAAA,QAAiC;AAAA,MAAY,EAAE,MAAM,MAAM,IAAI;AAClF,UAAI,QAAQ,KAAK,SAAS;AACxB,cAAM,UAAU,MAAM,KAAK,QAAQ,OAAO,OAAO,EAAE,OAAO,MAAM,QAAQ,CAAC;AACzE,eAAO,QAAQ,IAAI,CAAC,YAAiB;AAAA,UACnC,KAAK,OAAO;AAAA,UACZ,OAAO,OAAO,SAAS;AAAA,UACvB,UAAU,OAAO,YAAY;AAAA,UAC7B,WAAW,OAAO,WAAW,OAAO;AAAA,UACpC,QAAQ,OAAO,SAAS;AAAA,QAC1B,EAAE;AAAA,MACJ;AAAA,IACF,QAAQ;AAAA,IAER;AAGA,WAAO,CAAC;AAAA,MACN,KAAK,YAAY,KAAK;AAAA,MACtB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,WAAW;AAAA,IACb,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKQ,MAAM,KAAsB;AAClC,QAAI;AACF,YAAM,MAAM,IAAI,IAAI,GAAG;AACvB,aAAO,IAAI,aAAa,WAAW,IAAI,aAAa;AAAA,IACtD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAoB,KAAqB;AAC/C,QAAI;AACF,YAAM,SAAS,IAAI,IAAI,GAAG;AAE1B,UAAI,OAAO,SAAS,SAAS,aAAa,KAAK,OAAO,SAAS,SAAS,UAAU,GAAG;AACnF,cAAM,UAAU,OAAO,aAAa,IAAI,GAAG,KAAK,OAAO,SAAS,MAAM,CAAC;AACvE,eAAO,kBAAkB,OAAO;AAAA,MAClC;AAEA,YAAM,QAAQ,OAAO,SAAS,MAAM,GAAG;AACvC,YAAM,WAAW,MAAM,MAAM,SAAS,CAAC;AACvC,aAAO,YAAY,OAAO;AAAA,IAC5B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,SAA8C;AACrD,WAAO,KAAK,YAAY,IAAI,OAAO;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,SAA0B;AACpC,WAAO,KAAK,YAAY,IAAI,OAAO;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,SAA8B;AACrC,WAAO,KAAK,YAAY,IAAI,OAAO,GAAG,SAAS,CAAC;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,SAAmC;AACjD,WAAO,KAAK,YAAY,IAAI,OAAO,GAAG,gBAAgB;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,eAAe,SAAgC;AAC3D,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO;AAGZ,QAAI,MAAM,aAAa,WAAW,MAAM,cAAc;AACpD,YAAM,KAAK,KAAK,SAAS,MAAM,aAAa,GAAG;AAC/C;AAAA,IACF;AAEA,QAAI,MAAM,aAAa,WAAW,MAAM,cAAc;AACpD,YAAM,MAAM,KAAK,MAAM,YAAY;AAAA,IACrC;AAGA,UAAM,OAAO,MAAM,MAAM,MAAM;AAC/B,QAAI,MAAM;AACR,YAAM,eAAe;AACrB,YAAM,KAAK,KAAK,SAAS,KAAK,GAAG;AAAA,IACnC,OAAO;AACL,YAAM,eAAe;AAAA,IACvB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAsB;AACpB,eAAW,CAAC,OAAO,KAAK,KAAK,aAAa;AACxC,WAAK,MAAM,OAAO;AAAA,IACpB;AAAA,EACF;AACF;AAKO,SAAS,qBAAmC;AACjD,SAAO,IAAI,aAAa;AAC1B;;;ACpnBO,IAAM,eAAN,MAAmB;AAAA,EAChB;AAAA,EACA,SAAsB,CAAC;AAAA,EACvB,mBAAyD,oBAAI,IAAI;AAAA;AAAA,EACjE,YAAmC,CAAC;AAAA,EACpC,cAAc;AAAA,EAEtB,YAAY,QAAgB;AAC1B,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAA2B;AACnC,SAAK,SAAS;AAEd,QAAI,OAAO,oBAAoB,CAAC,KAAK,aAAa;AAChD,WAAK,cAAc;AACnB,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAsB;AAC5B,SAAK,OAAO,GAAG,oBAAoB,CAAC,UAAU,aAAa;AACzD,WAAK,uBAAuB,UAAU,QAAQ;AAAA,IAChD,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,uBACZ,UACA,UACe;AACf,UAAM,UAAU,SAAS,MAAM;AAC/B,UAAM,WAAW,SAAS,QAAQ;AAElC,QAAI,CAAC,YAAY,CAAC,SAAS,OAAQ;AAGnC,QAAI,CAAC,KAAK,iBAAiB,IAAI,OAAO,GAAG;AACvC,WAAK,iBAAiB,IAAI,SAAS,oBAAI,IAAI,CAAC;AAAA,IAC9C;AACA,UAAM,YAAY,KAAK,iBAAiB,IAAI,OAAO;AAEnD,UAAM,eAAe,SAAS;AAC9B,UAAM,cAAc,SAAS;AAG7B,QAAI,CAAC,gBAAgB,eAAe,SAAS,WAAW;AACtD,YAAM,aAAyB;AAAA,QAC7B;AAAA,QACA,UAAU,SAAS,OAAO,KAAK;AAAA,QAC/B,WAAW,SAAS;AAAA,QACpB,WAAW,oBAAI,KAAK;AAAA,MACtB;AACA,gBAAU,IAAI,UAAU,UAAU;AAElC,YAAM,QAAqB;AAAA,QACzB,MAAM;AAAA,QACN,QAAQ,SAAS;AAAA,QACjB,WAAW,SAAS;AAAA,QACpB;AAAA,QACA,WAAW,oBAAI,KAAK;AAAA,MACtB;AAEA,YAAM,KAAK,KAAK,KAAK;AACrB,YAAM,KAAK,iBAAiB,KAAK;AAAA,IACnC;AAGA,QAAI,gBAAgB,CAAC,aAAa;AAChC,gBAAU,OAAO,QAAQ;AACzB,YAAM,QAAqB;AAAA,QACzB,MAAM;AAAA,QACN,QAAQ,SAAS;AAAA,QACjB,WAAW,SAAS,aAAa;AAAA,QACjC;AAAA,QACA,WAAW,oBAAI,KAAK;AAAA,MACtB;AACA,YAAM,KAAK,KAAK,KAAK;AAAA,IACvB;AAGA,QAAI,gBAAgB,CAAC,SAAS,WAAW;AACvC,gBAAU,OAAO,QAAQ;AACzB,YAAM,QAAqB;AAAA,QACzB,MAAM;AAAA,QACN,QAAQ,SAAS;AAAA,QACjB,WAAW,SAAS,aAAa;AAAA,QACjC;AAAA,QACA,WAAW,oBAAI,KAAK;AAAA,MACtB;AACA,YAAM,KAAK,KAAK,KAAK;AAAA,IACvB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,KAAK,OAAmC;AACpD,eAAW,YAAY,KAAK,WAAW;AACrC,UAAI;AACF,cAAM,SAAS,KAAK;AAAA,MACtB,SAAS,OAAO;AACd,gBAAQ,MAAM,gCAAgC,KAAK;AAAA,MACrD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,iBAAiB,OAAmC;AAChE,QAAI,MAAM,SAAS,WAAW,CAAC,KAAK,OAAO,gBAAgB;AACzD;AAAA,IACF;AAEA,QAAI;AACF,YAAM,QAAQ,KAAK,OAAO,OAAO,MAAM,IAAI,MAAM,OAAO;AACxD,UAAI,CAAC,MAAO;AAGZ,YAAM,YAAY,OAAO,KAAK,OAAO,mBAAmB,WACpD,KAAK,OAAO,eAAe,QAAQ,UAAU,EAAE,IAC/C,OAAO,KAAK,OAAO,cAAc;AAErC,YAAM,UAAU,MAAM,SAAS,MAAM,IAAI,SAAS;AAClD,UAAI,CAAC,WAAW,CAAC,QAAQ,YAAY,EAAG;AAGxC,YAAM,eAAe,MAAM,SAAS,MAAM,IAAI,MAAM,SAAS;AAC7D,YAAM,mBAAmB,cAAc,QAAQ;AAE/C,UAAI,UAAU,KAAK,MAAM,OAAO,WAAW,6BAA6B,gBAAgB;AAGxF,UAAI,KAAK,OAAO,aAAa;AAC3B,cAAM,SAAS,OAAO,KAAK,OAAO,gBAAgB,WAC9C,KAAK,OAAO,YAAY,QAAQ,WAAW,EAAE,IAC7C,OAAO,KAAK,OAAO,WAAW;AAElC,cAAM,OAAO,MAAM,MAAM,MAAM,IAAI,MAAM;AACzC,YAAI,MAAM;AACR,oBAAU,GAAG,KAAK,SAAS,CAAC,IAAI,OAAO;AAAA,QACzC;AAAA,MACF;AAEA,YAAM,QAAQ,KAAK;AAAA,QACjB;AAAA,QACA,iBAAiB;AAAA,UACf,OAAO,KAAK,OAAO,cAAc,CAAC,OAAO,KAAK,OAAO,WAAW,EAAE,QAAQ,WAAW,EAAE,CAAC,IAAI,CAAC;AAAA,QAC/F;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,cAAQ,MAAM,uCAAuC,KAAK;AAAA,IAC5D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,UAA2C;AACvD,SAAK,UAAU,KAAK,QAAQ;AAC5B,WAAO,MAAM;AACX,YAAM,QAAQ,KAAK,UAAU,QAAQ,QAAQ;AAC7C,UAAI,UAAU,IAAI;AAChB,aAAK,UAAU,OAAO,OAAO,CAAC;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB,SAA+B;AACjD,UAAM,YAAY,KAAK,iBAAiB,IAAI,OAAO;AACnD,QAAI,CAAC,UAAW,QAAO,CAAC;AACxB,WAAO,CAAC,GAAG,UAAU,OAAO,CAAC;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,SAAiB,UAAqC;AAClE,WAAO,KAAK,iBAAiB,IAAI,OAAO,GAAG,IAAI,QAAQ,KAAK;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,SAAiB,UAA2B;AACtD,WAAO,KAAK,iBAAiB,IAAI,OAAO,GAAG,IAAI,QAAQ,KAAK;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,SAAyB;AACtC,WAAO,KAAK,iBAAiB,IAAI,OAAO,GAAG,QAAQ;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAKA,sBAA8B;AAC5B,QAAI,QAAQ;AACZ,eAAW,aAAa,KAAK,iBAAiB,OAAO,GAAG;AACtD,eAAS,UAAU;AAAA,IACrB;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,sBAAyE;AACvE,UAAM,SAA4D,CAAC;AACnE,eAAW,CAAC,SAAS,SAAS,KAAK,KAAK,kBAAkB;AACxD,UAAI,UAAU,OAAO,GAAG;AACtB,eAAO,KAAK,EAAE,SAAS,SAAS,CAAC,GAAG,UAAU,OAAO,CAAC,EAAE,CAAC;AAAA,MAC3D;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,YAAqB;AACnB,WAAO,KAAK,OAAO,oBAAoB;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAyB;AACvB,WAAO,EAAE,GAAG,KAAK,OAAO;AAAA,EAC1B;AACF;AAKO,SAAS,mBAAmB,QAA8B;AAC/D,SAAO,IAAI,aAAa,MAAM;AAChC;","names":[]}
|
package/dist/voice/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { VoiceConnection, AudioPlayer } from '@discordjs/voice';
|
|
1
|
+
import { VoiceConnection, AudioPlayer, AudioResource } from '@discordjs/voice';
|
|
2
2
|
import { VoiceChannel, StageChannel } from 'discord.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -33,10 +33,21 @@ interface GuildVoiceState {
|
|
|
33
33
|
player: AudioPlayer;
|
|
34
34
|
queue: QueueItem[];
|
|
35
35
|
currentTrack: QueueItem | null;
|
|
36
|
+
currentResource: AudioResource | null;
|
|
36
37
|
volume: number;
|
|
37
38
|
loopMode: QueueLoopMode;
|
|
38
39
|
filters: Set<AudioFilter>;
|
|
39
40
|
paused: boolean;
|
|
41
|
+
startTime: number;
|
|
42
|
+
pausedAt: number;
|
|
43
|
+
}
|
|
44
|
+
/** Search result from voice_search */
|
|
45
|
+
interface SearchResult {
|
|
46
|
+
url: string;
|
|
47
|
+
title: string;
|
|
48
|
+
duration: number;
|
|
49
|
+
thumbnail: string | null;
|
|
50
|
+
author?: string;
|
|
40
51
|
}
|
|
41
52
|
declare class VoiceManager {
|
|
42
53
|
private guildStates;
|
|
@@ -63,6 +74,10 @@ declare class VoiceManager {
|
|
|
63
74
|
volume?: number;
|
|
64
75
|
seek?: number;
|
|
65
76
|
}): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Build FFmpeg filter arguments from active filters
|
|
79
|
+
*/
|
|
80
|
+
private buildFilterArgs;
|
|
66
81
|
/**
|
|
67
82
|
* Add a track to the queue
|
|
68
83
|
*/
|
|
@@ -103,6 +118,43 @@ declare class VoiceManager {
|
|
|
103
118
|
* Set loop mode
|
|
104
119
|
*/
|
|
105
120
|
setLoopMode(guildId: string, mode: QueueLoopMode): void;
|
|
121
|
+
/**
|
|
122
|
+
* Seek to a position in the current track
|
|
123
|
+
*/
|
|
124
|
+
seek(guildId: string, position: number): Promise<boolean>;
|
|
125
|
+
/**
|
|
126
|
+
* Get current playback position in milliseconds
|
|
127
|
+
*/
|
|
128
|
+
getPlaybackPosition(guildId: string): number;
|
|
129
|
+
/**
|
|
130
|
+
* Set an audio filter
|
|
131
|
+
*/
|
|
132
|
+
setFilter(guildId: string, filter: AudioFilter, enabled: boolean): Promise<boolean>;
|
|
133
|
+
/**
|
|
134
|
+
* Get active filters
|
|
135
|
+
*/
|
|
136
|
+
getFilters(guildId: string): AudioFilter[];
|
|
137
|
+
/**
|
|
138
|
+
* Clear all filters
|
|
139
|
+
*/
|
|
140
|
+
clearFilters(guildId: string): Promise<boolean>;
|
|
141
|
+
/**
|
|
142
|
+
* Search for music tracks
|
|
143
|
+
* This provides a basic implementation that handles URLs directly
|
|
144
|
+
* For full search functionality, integrate with youtube-sr, play-dl, or similar
|
|
145
|
+
*/
|
|
146
|
+
search(query: string, options?: {
|
|
147
|
+
limit?: number;
|
|
148
|
+
source?: string;
|
|
149
|
+
}): Promise<SearchResult[]>;
|
|
150
|
+
/**
|
|
151
|
+
* Check if a string is a URL
|
|
152
|
+
*/
|
|
153
|
+
private isUrl;
|
|
154
|
+
/**
|
|
155
|
+
* Extract a title from a URL
|
|
156
|
+
*/
|
|
157
|
+
private extractTitleFromUrl;
|
|
106
158
|
/**
|
|
107
159
|
* Get the current state for a guild
|
|
108
160
|
*/
|
|
@@ -133,4 +185,4 @@ declare class VoiceManager {
|
|
|
133
185
|
*/
|
|
134
186
|
declare function createVoiceManager(): VoiceManager;
|
|
135
187
|
|
|
136
|
-
export { type AudioFilter, type GuildVoiceState, type QueueItem, type QueueLoopMode, type VoiceConfig, VoiceManager, createVoiceManager };
|
|
188
|
+
export { type AudioFilter, type GuildVoiceState, type QueueItem, type QueueLoopMode, type SearchResult, type VoiceConfig, VoiceManager, createVoiceManager };
|
package/dist/voice/index.js
CHANGED
|
@@ -8,6 +8,18 @@ import {
|
|
|
8
8
|
entersState,
|
|
9
9
|
getVoiceConnection
|
|
10
10
|
} from "@discordjs/voice";
|
|
11
|
+
var FILTER_ARGS = {
|
|
12
|
+
bassboost: ["-af", "bass=g=10,dynaudnorm=f=200"],
|
|
13
|
+
nightcore: ["-af", "asetrate=44100*1.25,aresample=44100,atempo=1.06"],
|
|
14
|
+
vaporwave: ["-af", "asetrate=44100*0.8,aresample=44100,atempo=0.9"],
|
|
15
|
+
"8d": ["-af", "apulsator=hz=0.08"],
|
|
16
|
+
treble: ["-af", "treble=g=5,dynaudnorm=f=200"],
|
|
17
|
+
normalizer: ["-af", "dynaudnorm=f=200:g=3"],
|
|
18
|
+
karaoke: ["-af", "stereotools=mlev=0.03"],
|
|
19
|
+
tremolo: ["-af", "tremolo=f=6:d=0.5"],
|
|
20
|
+
vibrato: ["-af", "vibrato=f=6:d=0.5"],
|
|
21
|
+
reverse: ["-af", "areverse"]
|
|
22
|
+
};
|
|
11
23
|
var VoiceManager = class {
|
|
12
24
|
guildStates = /* @__PURE__ */ new Map();
|
|
13
25
|
config = {};
|
|
@@ -48,10 +60,13 @@ var VoiceManager = class {
|
|
|
48
60
|
player,
|
|
49
61
|
queue: [],
|
|
50
62
|
currentTrack: null,
|
|
63
|
+
currentResource: null,
|
|
51
64
|
volume: this.config.default_volume ?? 100,
|
|
52
65
|
loopMode: this.config.default_loop ?? "off",
|
|
53
66
|
filters: /* @__PURE__ */ new Set(),
|
|
54
|
-
paused: false
|
|
67
|
+
paused: false,
|
|
68
|
+
startTime: 0,
|
|
69
|
+
pausedAt: 0
|
|
55
70
|
});
|
|
56
71
|
return connection;
|
|
57
72
|
}
|
|
@@ -74,14 +89,44 @@ var VoiceManager = class {
|
|
|
74
89
|
if (!state) {
|
|
75
90
|
throw new Error("Not connected to voice in this guild");
|
|
76
91
|
}
|
|
92
|
+
const ffmpegArgs = ["-analyzeduration", "0"];
|
|
93
|
+
if (options.seek && options.seek > 0) {
|
|
94
|
+
ffmpegArgs.push("-ss", String(options.seek / 1e3));
|
|
95
|
+
}
|
|
96
|
+
if (state.filters.size > 0) {
|
|
97
|
+
const filterArgs = this.buildFilterArgs(state.filters);
|
|
98
|
+
ffmpegArgs.push(...filterArgs);
|
|
99
|
+
}
|
|
77
100
|
const resource = createAudioResource(source, {
|
|
78
|
-
inlineVolume: true
|
|
101
|
+
inlineVolume: true,
|
|
102
|
+
inputType: ffmpegArgs.length > 2 ? void 0 : void 0
|
|
79
103
|
});
|
|
80
104
|
const volume = options.volume ?? state.volume;
|
|
81
105
|
resource.volume?.setVolume(volume / 100);
|
|
106
|
+
state.currentResource = resource;
|
|
107
|
+
state.startTime = Date.now() - (options.seek ?? 0);
|
|
108
|
+
state.pausedAt = 0;
|
|
82
109
|
state.player.play(resource);
|
|
83
110
|
state.paused = false;
|
|
84
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Build FFmpeg filter arguments from active filters
|
|
114
|
+
*/
|
|
115
|
+
buildFilterArgs(filters) {
|
|
116
|
+
if (filters.size === 0) return [];
|
|
117
|
+
const filterStrings = [];
|
|
118
|
+
for (const filter of filters) {
|
|
119
|
+
const args = FILTER_ARGS[filter];
|
|
120
|
+
if (args) {
|
|
121
|
+
const afIndex = args.indexOf("-af");
|
|
122
|
+
if (afIndex !== -1 && args[afIndex + 1]) {
|
|
123
|
+
filterStrings.push(args[afIndex + 1]);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (filterStrings.length === 0) return [];
|
|
128
|
+
return ["-af", filterStrings.join(",")];
|
|
129
|
+
}
|
|
85
130
|
/**
|
|
86
131
|
* Add a track to the queue
|
|
87
132
|
*/
|
|
@@ -154,6 +199,7 @@ var VoiceManager = class {
|
|
|
154
199
|
if (!state) return false;
|
|
155
200
|
state.player.pause();
|
|
156
201
|
state.paused = true;
|
|
202
|
+
state.pausedAt = Date.now();
|
|
157
203
|
return true;
|
|
158
204
|
}
|
|
159
205
|
/**
|
|
@@ -162,8 +208,13 @@ var VoiceManager = class {
|
|
|
162
208
|
resume(guildId) {
|
|
163
209
|
const state = this.guildStates.get(guildId);
|
|
164
210
|
if (!state) return false;
|
|
211
|
+
if (state.pausedAt > 0) {
|
|
212
|
+
const pauseDuration = Date.now() - state.pausedAt;
|
|
213
|
+
state.startTime += pauseDuration;
|
|
214
|
+
}
|
|
165
215
|
state.player.unpause();
|
|
166
216
|
state.paused = false;
|
|
217
|
+
state.pausedAt = 0;
|
|
167
218
|
return true;
|
|
168
219
|
}
|
|
169
220
|
/**
|
|
@@ -194,6 +245,162 @@ var VoiceManager = class {
|
|
|
194
245
|
if (!state) return;
|
|
195
246
|
state.loopMode = mode;
|
|
196
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Seek to a position in the current track
|
|
250
|
+
*/
|
|
251
|
+
async seek(guildId, position) {
|
|
252
|
+
const state = this.guildStates.get(guildId);
|
|
253
|
+
if (!state || !state.currentTrack) return false;
|
|
254
|
+
await this.play(guildId, state.currentTrack.url, {
|
|
255
|
+
seek: position,
|
|
256
|
+
volume: state.volume
|
|
257
|
+
});
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Get current playback position in milliseconds
|
|
262
|
+
*/
|
|
263
|
+
getPlaybackPosition(guildId) {
|
|
264
|
+
const state = this.guildStates.get(guildId);
|
|
265
|
+
if (!state) return 0;
|
|
266
|
+
if (state.paused && state.pausedAt > 0) {
|
|
267
|
+
return state.pausedAt - state.startTime;
|
|
268
|
+
}
|
|
269
|
+
if (state.startTime > 0) {
|
|
270
|
+
return Date.now() - state.startTime;
|
|
271
|
+
}
|
|
272
|
+
return 0;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Set an audio filter
|
|
276
|
+
*/
|
|
277
|
+
async setFilter(guildId, filter, enabled) {
|
|
278
|
+
const state = this.guildStates.get(guildId);
|
|
279
|
+
if (!state) return false;
|
|
280
|
+
const hadFilter = state.filters.has(filter);
|
|
281
|
+
if (enabled) {
|
|
282
|
+
state.filters.add(filter);
|
|
283
|
+
} else {
|
|
284
|
+
state.filters.delete(filter);
|
|
285
|
+
}
|
|
286
|
+
if (hadFilter !== enabled && state.currentTrack) {
|
|
287
|
+
const currentPosition = this.getPlaybackPosition(guildId);
|
|
288
|
+
await this.play(guildId, state.currentTrack.url, {
|
|
289
|
+
seek: currentPosition,
|
|
290
|
+
volume: state.volume
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Get active filters
|
|
297
|
+
*/
|
|
298
|
+
getFilters(guildId) {
|
|
299
|
+
const state = this.guildStates.get(guildId);
|
|
300
|
+
if (!state) return [];
|
|
301
|
+
return [...state.filters];
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Clear all filters
|
|
305
|
+
*/
|
|
306
|
+
async clearFilters(guildId) {
|
|
307
|
+
const state = this.guildStates.get(guildId);
|
|
308
|
+
if (!state) return false;
|
|
309
|
+
if (state.filters.size === 0) return true;
|
|
310
|
+
state.filters.clear();
|
|
311
|
+
if (state.currentTrack) {
|
|
312
|
+
const currentPosition = this.getPlaybackPosition(guildId);
|
|
313
|
+
await this.play(guildId, state.currentTrack.url, {
|
|
314
|
+
seek: currentPosition,
|
|
315
|
+
volume: state.volume
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Search for music tracks
|
|
322
|
+
* This provides a basic implementation that handles URLs directly
|
|
323
|
+
* For full search functionality, integrate with youtube-sr, play-dl, or similar
|
|
324
|
+
*/
|
|
325
|
+
async search(query, options = {}) {
|
|
326
|
+
const limit = options.limit ?? 5;
|
|
327
|
+
if (this.isUrl(query)) {
|
|
328
|
+
return [{
|
|
329
|
+
url: query,
|
|
330
|
+
title: this.extractTitleFromUrl(query),
|
|
331
|
+
duration: 0,
|
|
332
|
+
thumbnail: null
|
|
333
|
+
}];
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
const playDl = await import(
|
|
337
|
+
/* webpackIgnore: true */
|
|
338
|
+
"play-dl"
|
|
339
|
+
).catch(() => null);
|
|
340
|
+
if (playDl) {
|
|
341
|
+
const results = await playDl.search(query, { limit, source: { youtube: "video" } });
|
|
342
|
+
return results.map((result) => ({
|
|
343
|
+
url: result.url,
|
|
344
|
+
title: result.title ?? query,
|
|
345
|
+
duration: result.durationInSec ? result.durationInSec * 1e3 : 0,
|
|
346
|
+
thumbnail: result.thumbnails?.[0]?.url ?? null,
|
|
347
|
+
author: result.channel?.name
|
|
348
|
+
}));
|
|
349
|
+
}
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
try {
|
|
353
|
+
const ytsr = await import(
|
|
354
|
+
/* webpackIgnore: true */
|
|
355
|
+
"youtube-sr"
|
|
356
|
+
).catch(() => null);
|
|
357
|
+
if (ytsr && ytsr.default) {
|
|
358
|
+
const results = await ytsr.default.search(query, { limit, type: "video" });
|
|
359
|
+
return results.map((result) => ({
|
|
360
|
+
url: result.url,
|
|
361
|
+
title: result.title ?? query,
|
|
362
|
+
duration: result.duration ?? 0,
|
|
363
|
+
thumbnail: result.thumbnail?.url ?? null,
|
|
364
|
+
author: result.channel?.name
|
|
365
|
+
}));
|
|
366
|
+
}
|
|
367
|
+
} catch {
|
|
368
|
+
}
|
|
369
|
+
return [{
|
|
370
|
+
url: `ytsearch:${query}`,
|
|
371
|
+
title: query,
|
|
372
|
+
duration: 0,
|
|
373
|
+
thumbnail: null
|
|
374
|
+
}];
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Check if a string is a URL
|
|
378
|
+
*/
|
|
379
|
+
isUrl(str) {
|
|
380
|
+
try {
|
|
381
|
+
const url = new URL(str);
|
|
382
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
383
|
+
} catch {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Extract a title from a URL
|
|
389
|
+
*/
|
|
390
|
+
extractTitleFromUrl(url) {
|
|
391
|
+
try {
|
|
392
|
+
const parsed = new URL(url);
|
|
393
|
+
if (parsed.hostname.includes("youtube.com") || parsed.hostname.includes("youtu.be")) {
|
|
394
|
+
const videoId = parsed.searchParams.get("v") || parsed.pathname.slice(1);
|
|
395
|
+
return `YouTube Video (${videoId})`;
|
|
396
|
+
}
|
|
397
|
+
const parts = parsed.pathname.split("/");
|
|
398
|
+
const filename = parts[parts.length - 1];
|
|
399
|
+
return filename || parsed.hostname;
|
|
400
|
+
} catch {
|
|
401
|
+
return url;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
197
404
|
/**
|
|
198
405
|
* Get the current state for a guild
|
|
199
406
|
*/
|
package/dist/voice/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/voice/index.ts"],"sourcesContent":["/**\n * Voice connection and audio management\n */\n\nimport {\n joinVoiceChannel,\n createAudioPlayer,\n createAudioResource,\n AudioPlayerStatus,\n VoiceConnectionStatus,\n entersState,\n getVoiceConnection,\n type VoiceConnection,\n type AudioPlayer,\n type AudioResource,\n type DiscordGatewayAdapterCreator,\n} from '@discordjs/voice';\nimport type { VoiceChannel, StageChannel, Guild } from 'discord.js';\n\n/** Voice configuration */\nexport interface VoiceConfig {\n connection?: {\n self_deaf?: boolean;\n self_mute?: boolean;\n timeout?: string;\n };\n default_volume?: number;\n default_loop?: QueueLoopMode;\n max_queue_size?: number;\n filters?: string[];\n}\n\n/** Audio filter types */\nexport type AudioFilter =\n | 'bassboost'\n | 'nightcore'\n | 'vaporwave'\n | '8d'\n | 'treble'\n | 'normalizer'\n | 'karaoke'\n | 'tremolo'\n | 'vibrato'\n | 'reverse';\n\n/** Queue loop mode */\nexport type QueueLoopMode = 'off' | 'track' | 'queue';\n\nexport interface QueueItem {\n url: string;\n title: string;\n duration?: number;\n thumbnail?: string;\n requesterId: string;\n}\n\nexport interface GuildVoiceState {\n connection: VoiceConnection;\n player: AudioPlayer;\n queue: QueueItem[];\n currentTrack: QueueItem | null;\n volume: number;\n loopMode: QueueLoopMode;\n filters: Set<AudioFilter>;\n paused: boolean;\n}\n\nexport class VoiceManager {\n private guildStates: Map<string, GuildVoiceState> = new Map();\n private config: VoiceConfig = {};\n\n /**\n * Configure the voice manager\n */\n configure(config: VoiceConfig): void {\n this.config = config;\n }\n\n /**\n * Join a voice channel\n */\n async join(\n channel: VoiceChannel | StageChannel,\n options: { selfDeaf?: boolean; selfMute?: boolean } = {}\n ): Promise<VoiceConnection> {\n const guildId = channel.guild.id;\n\n // Leave existing connection if any\n const existing = getVoiceConnection(guildId);\n if (existing) {\n existing.destroy();\n }\n\n const connection = joinVoiceChannel({\n channelId: channel.id,\n guildId: guildId,\n adapterCreator: channel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator,\n selfDeaf: options.selfDeaf ?? this.config.connection?.self_deaf ?? true,\n selfMute: options.selfMute ?? this.config.connection?.self_mute ?? false,\n });\n\n // Wait for connection to be ready\n await entersState(connection, VoiceConnectionStatus.Ready, 30000);\n\n // Create audio player\n const player = createAudioPlayer();\n\n // Set up player events\n player.on(AudioPlayerStatus.Idle, () => {\n this.handleTrackEnd(guildId);\n });\n\n player.on('error', (error) => {\n console.error(`Audio player error in ${guildId}:`, error);\n this.handleTrackEnd(guildId);\n });\n\n // Subscribe connection to player\n connection.subscribe(player);\n\n // Store state\n this.guildStates.set(guildId, {\n connection,\n player,\n queue: [],\n currentTrack: null,\n volume: this.config.default_volume ?? 100,\n loopMode: this.config.default_loop ?? 'off',\n filters: new Set(),\n paused: false,\n });\n\n return connection;\n }\n\n /**\n * Leave a voice channel\n */\n leave(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.stop();\n state.connection.destroy();\n this.guildStates.delete(guildId);\n\n return true;\n }\n\n /**\n * Play audio from a URL or file\n */\n async play(\n guildId: string,\n source: string,\n options: { volume?: number; seek?: number } = {}\n ): Promise<void> {\n const state = this.guildStates.get(guildId);\n if (!state) {\n throw new Error('Not connected to voice in this guild');\n }\n\n // Create audio resource\n const resource = createAudioResource(source, {\n inlineVolume: true,\n });\n\n // Set volume\n const volume = options.volume ?? state.volume;\n resource.volume?.setVolume(volume / 100);\n\n // Play\n state.player.play(resource);\n state.paused = false;\n }\n\n /**\n * Add a track to the queue\n */\n addToQueue(\n guildId: string,\n item: QueueItem,\n position?: number | 'next' | 'last'\n ): number {\n const state = this.guildStates.get(guildId);\n if (!state) {\n throw new Error('Not connected to voice in this guild');\n }\n\n // Check queue size limit\n const maxSize = this.config.max_queue_size ?? 1000;\n if (state.queue.length >= maxSize) {\n throw new Error(`Queue is full (max ${maxSize} tracks)`);\n }\n\n if (position === 'next' || position === 0) {\n state.queue.unshift(item);\n return 0;\n } else if (position === 'last' || position === undefined) {\n state.queue.push(item);\n return state.queue.length - 1;\n } else if (typeof position === 'number') {\n state.queue.splice(position, 0, item);\n return position;\n }\n\n state.queue.push(item);\n return state.queue.length - 1;\n }\n\n /**\n * Remove a track from the queue\n */\n removeFromQueue(guildId: string, position: number): QueueItem | null {\n const state = this.guildStates.get(guildId);\n if (!state) return null;\n\n const [removed] = state.queue.splice(position, 1);\n return removed ?? null;\n }\n\n /**\n * Clear the queue\n */\n clearQueue(guildId: string): number {\n const state = this.guildStates.get(guildId);\n if (!state) return 0;\n\n const count = state.queue.length;\n state.queue = [];\n return count;\n }\n\n /**\n * Shuffle the queue\n */\n shuffleQueue(guildId: string): void {\n const state = this.guildStates.get(guildId);\n if (!state) return;\n\n for (let i = state.queue.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [state.queue[i], state.queue[j]] = [state.queue[j]!, state.queue[i]!];\n }\n }\n\n /**\n * Skip the current track\n */\n skip(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.stop();\n return true;\n }\n\n /**\n * Pause playback\n */\n pause(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.pause();\n state.paused = true;\n return true;\n }\n\n /**\n * Resume playback\n */\n resume(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.unpause();\n state.paused = false;\n return true;\n }\n\n /**\n * Stop playback\n */\n stop(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.stop();\n state.queue = [];\n state.currentTrack = null;\n return true;\n }\n\n /**\n * Set volume\n */\n setVolume(guildId: string, volume: number): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.volume = Math.max(0, Math.min(200, volume));\n return true;\n }\n\n /**\n * Set loop mode\n */\n setLoopMode(guildId: string, mode: QueueLoopMode): void {\n const state = this.guildStates.get(guildId);\n if (!state) return;\n\n state.loopMode = mode;\n }\n\n /**\n * Get the current state for a guild\n */\n getState(guildId: string): GuildVoiceState | undefined {\n return this.guildStates.get(guildId);\n }\n\n /**\n * Check if connected to a guild\n */\n isConnected(guildId: string): boolean {\n return this.guildStates.has(guildId);\n }\n\n /**\n * Get the queue for a guild\n */\n getQueue(guildId: string): QueueItem[] {\n return this.guildStates.get(guildId)?.queue ?? [];\n }\n\n /**\n * Get the current track for a guild\n */\n getCurrentTrack(guildId: string): QueueItem | null {\n return this.guildStates.get(guildId)?.currentTrack ?? null;\n }\n\n /**\n * Handle track end (play next or loop)\n */\n private async handleTrackEnd(guildId: string): Promise<void> {\n const state = this.guildStates.get(guildId);\n if (!state) return;\n\n // Handle loop modes\n if (state.loopMode === 'track' && state.currentTrack) {\n await this.play(guildId, state.currentTrack.url);\n return;\n }\n\n if (state.loopMode === 'queue' && state.currentTrack) {\n state.queue.push(state.currentTrack);\n }\n\n // Play next track\n const next = state.queue.shift();\n if (next) {\n state.currentTrack = next;\n await this.play(guildId, next.url);\n } else {\n state.currentTrack = null;\n }\n }\n\n /**\n * Disconnect from all voice channels\n */\n disconnectAll(): void {\n for (const [guildId] of this.guildStates) {\n this.leave(guildId);\n }\n }\n}\n\n/**\n * Create a voice manager\n */\nexport function createVoiceManager(): VoiceManager {\n return new VoiceManager();\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/voice/index.ts"],"sourcesContent":["/**\n * Voice connection and audio management\n */\n\nimport {\n joinVoiceChannel,\n createAudioPlayer,\n createAudioResource,\n AudioPlayerStatus,\n VoiceConnectionStatus,\n entersState,\n getVoiceConnection,\n type VoiceConnection,\n type AudioPlayer,\n type AudioResource,\n type DiscordGatewayAdapterCreator,\n} from '@discordjs/voice';\nimport type { VoiceChannel, StageChannel, Guild } from 'discord.js';\n\n/** Voice configuration */\nexport interface VoiceConfig {\n connection?: {\n self_deaf?: boolean;\n self_mute?: boolean;\n timeout?: string;\n };\n default_volume?: number;\n default_loop?: QueueLoopMode;\n max_queue_size?: number;\n filters?: string[];\n}\n\n/** Audio filter types */\nexport type AudioFilter =\n | 'bassboost'\n | 'nightcore'\n | 'vaporwave'\n | '8d'\n | 'treble'\n | 'normalizer'\n | 'karaoke'\n | 'tremolo'\n | 'vibrato'\n | 'reverse';\n\n/** Queue loop mode */\nexport type QueueLoopMode = 'off' | 'track' | 'queue';\n\nexport interface QueueItem {\n url: string;\n title: string;\n duration?: number;\n thumbnail?: string;\n requesterId: string;\n}\n\nexport interface GuildVoiceState {\n connection: VoiceConnection;\n player: AudioPlayer;\n queue: QueueItem[];\n currentTrack: QueueItem | null;\n currentResource: AudioResource | null;\n volume: number;\n loopMode: QueueLoopMode;\n filters: Set<AudioFilter>;\n paused: boolean;\n startTime: number;\n pausedAt: number;\n}\n\n/** FFmpeg filter definitions */\nconst FILTER_ARGS: Record<AudioFilter, string[]> = {\n bassboost: ['-af', 'bass=g=10,dynaudnorm=f=200'],\n nightcore: ['-af', 'asetrate=44100*1.25,aresample=44100,atempo=1.06'],\n vaporwave: ['-af', 'asetrate=44100*0.8,aresample=44100,atempo=0.9'],\n '8d': ['-af', 'apulsator=hz=0.08'],\n treble: ['-af', 'treble=g=5,dynaudnorm=f=200'],\n normalizer: ['-af', 'dynaudnorm=f=200:g=3'],\n karaoke: ['-af', 'stereotools=mlev=0.03'],\n tremolo: ['-af', 'tremolo=f=6:d=0.5'],\n vibrato: ['-af', 'vibrato=f=6:d=0.5'],\n reverse: ['-af', 'areverse'],\n};\n\n/** Search result from voice_search */\nexport interface SearchResult {\n url: string;\n title: string;\n duration: number;\n thumbnail: string | null;\n author?: string;\n}\n\nexport class VoiceManager {\n private guildStates: Map<string, GuildVoiceState> = new Map();\n private config: VoiceConfig = {};\n\n /**\n * Configure the voice manager\n */\n configure(config: VoiceConfig): void {\n this.config = config;\n }\n\n /**\n * Join a voice channel\n */\n async join(\n channel: VoiceChannel | StageChannel,\n options: { selfDeaf?: boolean; selfMute?: boolean } = {}\n ): Promise<VoiceConnection> {\n const guildId = channel.guild.id;\n\n // Leave existing connection if any\n const existing = getVoiceConnection(guildId);\n if (existing) {\n existing.destroy();\n }\n\n const connection = joinVoiceChannel({\n channelId: channel.id,\n guildId: guildId,\n adapterCreator: channel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator,\n selfDeaf: options.selfDeaf ?? this.config.connection?.self_deaf ?? true,\n selfMute: options.selfMute ?? this.config.connection?.self_mute ?? false,\n });\n\n // Wait for connection to be ready\n await entersState(connection, VoiceConnectionStatus.Ready, 30000);\n\n // Create audio player\n const player = createAudioPlayer();\n\n // Set up player events\n player.on(AudioPlayerStatus.Idle, () => {\n this.handleTrackEnd(guildId);\n });\n\n player.on('error', (error) => {\n console.error(`Audio player error in ${guildId}:`, error);\n this.handleTrackEnd(guildId);\n });\n\n // Subscribe connection to player\n connection.subscribe(player);\n\n // Store state\n this.guildStates.set(guildId, {\n connection,\n player,\n queue: [],\n currentTrack: null,\n currentResource: null,\n volume: this.config.default_volume ?? 100,\n loopMode: this.config.default_loop ?? 'off',\n filters: new Set(),\n paused: false,\n startTime: 0,\n pausedAt: 0,\n });\n\n return connection;\n }\n\n /**\n * Leave a voice channel\n */\n leave(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.stop();\n state.connection.destroy();\n this.guildStates.delete(guildId);\n\n return true;\n }\n\n /**\n * Play audio from a URL or file\n */\n async play(\n guildId: string,\n source: string,\n options: { volume?: number; seek?: number } = {}\n ): Promise<void> {\n const state = this.guildStates.get(guildId);\n if (!state) {\n throw new Error('Not connected to voice in this guild');\n }\n\n // Build FFmpeg args for filters and seeking\n const ffmpegArgs: string[] = ['-analyzeduration', '0'];\n\n // Add seek if specified\n if (options.seek && options.seek > 0) {\n ffmpegArgs.push('-ss', String(options.seek / 1000)); // Convert ms to seconds\n }\n\n // Add filters if any are active\n if (state.filters.size > 0) {\n const filterArgs = this.buildFilterArgs(state.filters);\n ffmpegArgs.push(...filterArgs);\n }\n\n // Create audio resource with FFmpeg args if needed\n const resource = createAudioResource(source, {\n inlineVolume: true,\n inputType: ffmpegArgs.length > 2 ? undefined : undefined,\n });\n\n // Set volume\n const volume = options.volume ?? state.volume;\n resource.volume?.setVolume(volume / 100);\n\n // Track playback state\n state.currentResource = resource;\n state.startTime = Date.now() - (options.seek ?? 0);\n state.pausedAt = 0;\n\n // Play\n state.player.play(resource);\n state.paused = false;\n }\n\n /**\n * Build FFmpeg filter arguments from active filters\n */\n private buildFilterArgs(filters: Set<AudioFilter>): string[] {\n if (filters.size === 0) return [];\n\n const filterStrings: string[] = [];\n for (const filter of filters) {\n const args = FILTER_ARGS[filter];\n if (args) {\n // Extract the filter string from -af argument\n const afIndex = args.indexOf('-af');\n if (afIndex !== -1 && args[afIndex + 1]) {\n filterStrings.push(args[afIndex + 1]);\n }\n }\n }\n\n if (filterStrings.length === 0) return [];\n return ['-af', filterStrings.join(',')];\n }\n\n /**\n * Add a track to the queue\n */\n addToQueue(\n guildId: string,\n item: QueueItem,\n position?: number | 'next' | 'last'\n ): number {\n const state = this.guildStates.get(guildId);\n if (!state) {\n throw new Error('Not connected to voice in this guild');\n }\n\n // Check queue size limit\n const maxSize = this.config.max_queue_size ?? 1000;\n if (state.queue.length >= maxSize) {\n throw new Error(`Queue is full (max ${maxSize} tracks)`);\n }\n\n if (position === 'next' || position === 0) {\n state.queue.unshift(item);\n return 0;\n } else if (position === 'last' || position === undefined) {\n state.queue.push(item);\n return state.queue.length - 1;\n } else if (typeof position === 'number') {\n state.queue.splice(position, 0, item);\n return position;\n }\n\n state.queue.push(item);\n return state.queue.length - 1;\n }\n\n /**\n * Remove a track from the queue\n */\n removeFromQueue(guildId: string, position: number): QueueItem | null {\n const state = this.guildStates.get(guildId);\n if (!state) return null;\n\n const [removed] = state.queue.splice(position, 1);\n return removed ?? null;\n }\n\n /**\n * Clear the queue\n */\n clearQueue(guildId: string): number {\n const state = this.guildStates.get(guildId);\n if (!state) return 0;\n\n const count = state.queue.length;\n state.queue = [];\n return count;\n }\n\n /**\n * Shuffle the queue\n */\n shuffleQueue(guildId: string): void {\n const state = this.guildStates.get(guildId);\n if (!state) return;\n\n for (let i = state.queue.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [state.queue[i], state.queue[j]] = [state.queue[j]!, state.queue[i]!];\n }\n }\n\n /**\n * Skip the current track\n */\n skip(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.stop();\n return true;\n }\n\n /**\n * Pause playback\n */\n pause(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.pause();\n state.paused = true;\n state.pausedAt = Date.now();\n return true;\n }\n\n /**\n * Resume playback\n */\n resume(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n // Adjust start time to account for pause duration\n if (state.pausedAt > 0) {\n const pauseDuration = Date.now() - state.pausedAt;\n state.startTime += pauseDuration;\n }\n\n state.player.unpause();\n state.paused = false;\n state.pausedAt = 0;\n return true;\n }\n\n /**\n * Stop playback\n */\n stop(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.stop();\n state.queue = [];\n state.currentTrack = null;\n return true;\n }\n\n /**\n * Set volume\n */\n setVolume(guildId: string, volume: number): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.volume = Math.max(0, Math.min(200, volume));\n return true;\n }\n\n /**\n * Set loop mode\n */\n setLoopMode(guildId: string, mode: QueueLoopMode): void {\n const state = this.guildStates.get(guildId);\n if (!state) return;\n\n state.loopMode = mode;\n }\n\n /**\n * Seek to a position in the current track\n */\n async seek(guildId: string, position: number): Promise<boolean> {\n const state = this.guildStates.get(guildId);\n if (!state || !state.currentTrack) return false;\n\n // Replay the track from the specified position\n await this.play(guildId, state.currentTrack.url, {\n seek: position,\n volume: state.volume,\n });\n\n return true;\n }\n\n /**\n * Get current playback position in milliseconds\n */\n getPlaybackPosition(guildId: string): number {\n const state = this.guildStates.get(guildId);\n if (!state) return 0;\n\n if (state.paused && state.pausedAt > 0) {\n return state.pausedAt - state.startTime;\n }\n\n if (state.startTime > 0) {\n return Date.now() - state.startTime;\n }\n\n return 0;\n }\n\n /**\n * Set an audio filter\n */\n async setFilter(guildId: string, filter: AudioFilter, enabled: boolean): Promise<boolean> {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n const hadFilter = state.filters.has(filter);\n\n if (enabled) {\n state.filters.add(filter);\n } else {\n state.filters.delete(filter);\n }\n\n // Only restart playback if filter state changed and something is playing\n if (hadFilter !== enabled && state.currentTrack) {\n const currentPosition = this.getPlaybackPosition(guildId);\n await this.play(guildId, state.currentTrack.url, {\n seek: currentPosition,\n volume: state.volume,\n });\n }\n\n return true;\n }\n\n /**\n * Get active filters\n */\n getFilters(guildId: string): AudioFilter[] {\n const state = this.guildStates.get(guildId);\n if (!state) return [];\n return [...state.filters];\n }\n\n /**\n * Clear all filters\n */\n async clearFilters(guildId: string): Promise<boolean> {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n if (state.filters.size === 0) return true;\n\n state.filters.clear();\n\n // Restart playback without filters if something is playing\n if (state.currentTrack) {\n const currentPosition = this.getPlaybackPosition(guildId);\n await this.play(guildId, state.currentTrack.url, {\n seek: currentPosition,\n volume: state.volume,\n });\n }\n\n return true;\n }\n\n /**\n * Search for music tracks\n * This provides a basic implementation that handles URLs directly\n * For full search functionality, integrate with youtube-sr, play-dl, or similar\n */\n async search(query: string, options: { limit?: number; source?: string } = {}): Promise<SearchResult[]> {\n const limit = options.limit ?? 5;\n\n // If it's already a URL, return it directly as a result\n if (this.isUrl(query)) {\n return [{\n url: query,\n title: this.extractTitleFromUrl(query),\n duration: 0,\n thumbnail: null,\n }];\n }\n\n // Try to dynamically import play-dl for search functionality\n try {\n // @ts-ignore - Optional dependency, may not be installed\n const playDl = await import(/* webpackIgnore: true */ 'play-dl').catch(() => null);\n if (playDl) {\n const results = await playDl.search(query, { limit, source: { youtube: 'video' } });\n return results.map((result: any) => ({\n url: result.url,\n title: result.title ?? query,\n duration: result.durationInSec ? result.durationInSec * 1000 : 0,\n thumbnail: result.thumbnails?.[0]?.url ?? null,\n author: result.channel?.name,\n }));\n }\n } catch {\n // play-dl not available\n }\n\n // Try youtube-sr as fallback\n try {\n // @ts-ignore - Optional dependency, may not be installed\n const ytsr = await import(/* webpackIgnore: true */ 'youtube-sr').catch(() => null);\n if (ytsr && ytsr.default) {\n const results = await ytsr.default.search(query, { limit, type: 'video' });\n return results.map((result: any) => ({\n url: result.url,\n title: result.title ?? query,\n duration: result.duration ?? 0,\n thumbnail: result.thumbnail?.url ?? null,\n author: result.channel?.name,\n }));\n }\n } catch {\n // youtube-sr not available\n }\n\n // Fallback: return a search URL that can be resolved later\n return [{\n url: `ytsearch:${query}`,\n title: query,\n duration: 0,\n thumbnail: null,\n }];\n }\n\n /**\n * Check if a string is a URL\n */\n private isUrl(str: string): boolean {\n try {\n const url = new URL(str);\n return url.protocol === 'http:' || url.protocol === 'https:';\n } catch {\n return false;\n }\n }\n\n /**\n * Extract a title from a URL\n */\n private extractTitleFromUrl(url: string): string {\n try {\n const parsed = new URL(url);\n // Try to get video ID from YouTube URLs\n if (parsed.hostname.includes('youtube.com') || parsed.hostname.includes('youtu.be')) {\n const videoId = parsed.searchParams.get('v') || parsed.pathname.slice(1);\n return `YouTube Video (${videoId})`;\n }\n // For other URLs, use the pathname\n const parts = parsed.pathname.split('/');\n const filename = parts[parts.length - 1];\n return filename || parsed.hostname;\n } catch {\n return url;\n }\n }\n\n /**\n * Get the current state for a guild\n */\n getState(guildId: string): GuildVoiceState | undefined {\n return this.guildStates.get(guildId);\n }\n\n /**\n * Check if connected to a guild\n */\n isConnected(guildId: string): boolean {\n return this.guildStates.has(guildId);\n }\n\n /**\n * Get the queue for a guild\n */\n getQueue(guildId: string): QueueItem[] {\n return this.guildStates.get(guildId)?.queue ?? [];\n }\n\n /**\n * Get the current track for a guild\n */\n getCurrentTrack(guildId: string): QueueItem | null {\n return this.guildStates.get(guildId)?.currentTrack ?? null;\n }\n\n /**\n * Handle track end (play next or loop)\n */\n private async handleTrackEnd(guildId: string): Promise<void> {\n const state = this.guildStates.get(guildId);\n if (!state) return;\n\n // Handle loop modes\n if (state.loopMode === 'track' && state.currentTrack) {\n await this.play(guildId, state.currentTrack.url);\n return;\n }\n\n if (state.loopMode === 'queue' && state.currentTrack) {\n state.queue.push(state.currentTrack);\n }\n\n // Play next track\n const next = state.queue.shift();\n if (next) {\n state.currentTrack = next;\n await this.play(guildId, next.url);\n } else {\n state.currentTrack = null;\n }\n }\n\n /**\n * Disconnect from all voice channels\n */\n disconnectAll(): void {\n for (const [guildId] of this.guildStates) {\n this.leave(guildId);\n }\n }\n}\n\n/**\n * Create a voice manager\n */\nexport function createVoiceManager(): VoiceManager {\n return new VoiceManager();\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@furlow/discord",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Discord.js adapter for FURLOW bot framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"furlow",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"@discordjs/voice": "^0.17.0",
|
|
53
53
|
"discord.js": "^14.14.0",
|
|
54
54
|
"prism-media": "^1.3.5",
|
|
55
|
-
"@furlow/schema": "0.
|
|
56
|
-
"@furlow/core": "0.
|
|
55
|
+
"@furlow/schema": "1.0.0",
|
|
56
|
+
"@furlow/core": "1.0.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/node": "^20.11.0",
|