diff --git a/src/Player.ts b/src/Player.ts index b8df62b..b78a590 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -140,7 +140,7 @@ class Player extends EventEmitter { try { prev.destroy(); - } catch { } // eslint-disable-line no-empty + } catch {} // eslint-disable-line no-empty this.queues.delete(guild.id); return prev; @@ -171,9 +171,9 @@ class Player extends EventEmitter { const playlist = !data.playlist ? null : new Playlist(this, { - ...data.playlist, - tracks: [] - }); + ...data.playlist, + tracks: [] + }); const tracks = data.data.map( (m) => @@ -279,13 +279,13 @@ class Player extends EventEmitter { author: spotifyPlaylist.type !== "playlist" ? { - name: spotifyPlaylist.artists[0]?.name ?? "Unknown Artist", - url: spotifyPlaylist.artists[0]?.external_urls?.spotify ?? null - } + name: spotifyPlaylist.artists[0]?.name ?? "Unknown Artist", + url: spotifyPlaylist.artists[0]?.external_urls?.spotify ?? null + } : { - name: spotifyPlaylist.owner?.display_name ?? spotifyPlaylist.owner?.id ?? "Unknown Artist", - url: spotifyPlaylist.owner?.external_urls?.spotify ?? null - }, + name: spotifyPlaylist.owner?.display_name ?? spotifyPlaylist.owner?.id ?? "Unknown Artist", + url: spotifyPlaylist.owner?.external_urls?.spotify ?? null + }, tracks: [], id: spotifyPlaylist.id, url: spotifyPlaylist.external_urls?.spotify ?? query, @@ -469,4 +469,4 @@ class Player extends EventEmitter { } } -export { Player }; \ No newline at end of file +export { Player }; diff --git a/src/VoiceInterface/AdapterCreator.ts b/src/VoiceInterface/AdapterCreator.ts new file mode 100644 index 0000000..cc7d4c7 --- /dev/null +++ b/src/VoiceInterface/AdapterCreator.ts @@ -0,0 +1,62 @@ +import { DiscordGatewayAdapterCreator, DiscordGatewayAdapterLibraryMethods } from "@discordjs/voice"; +import { VoiceChannel, Snowflake, Client, Constants, WebSocketShard, Guild, StageChannel } from "discord.js"; +import { GatewayVoiceServerUpdateDispatchData, GatewayVoiceStateUpdateDispatchData } from "discord-api-types/v8"; + +const adapters = new Map(); +const trackedClients = new Set(); + +function trackClient(client: Client) { + if (trackedClients.has(client)) return; + trackedClients.add(client); + client.ws.on(Constants.WSEvents.VOICE_SERVER_UPDATE, (payload: GatewayVoiceServerUpdateDispatchData) => { + adapters.get(payload.guild_id)?.onVoiceServerUpdate(payload); + }); + client.ws.on(Constants.WSEvents.VOICE_STATE_UPDATE, (payload: GatewayVoiceStateUpdateDispatchData) => { + if (payload.guild_id && payload.session_id && payload.user_id === client.user?.id) { + adapters.get(payload.guild_id)?.onVoiceStateUpdate(payload); + } + }); +} + +const trackedGuilds = new Map>(); + +function cleanupGuilds(shard: WebSocketShard) { + const guilds = trackedGuilds.get(shard); + if (guilds) { + for (const guildID of guilds.values()) { + adapters.get(guildID)?.destroy(); + } + } +} + +function trackGuild(guild: Guild) { + let guilds = trackedGuilds.get(guild.shard); + if (!guilds) { + const cleanup = () => cleanupGuilds(guild.shard); + guild.shard.on("close", cleanup); + guild.shard.on("destroyed", cleanup); + guilds = new Set(); + trackedGuilds.set(guild.shard, guilds); + } + guilds.add(guild.id); +} + +export function VoiceAdapterCreator(channel: VoiceChannel | StageChannel): DiscordGatewayAdapterCreator { + return (methods) => { + adapters.set(channel.guild.id, methods); + trackClient(channel.client); + trackGuild(channel.guild); + return { + sendPayload(data) { + if (channel.guild.shard.status === Constants.Status.READY) { + channel.guild.shard.send(data); + return true; + } + return false; + }, + destroy() { + return adapters.delete(channel.guild.id); + } + }; + }; +} diff --git a/src/VoiceInterface/VoiceUtils.ts b/src/VoiceInterface/VoiceUtils.ts index 085e0ad..5de378c 100644 --- a/src/VoiceInterface/VoiceUtils.ts +++ b/src/VoiceInterface/VoiceUtils.ts @@ -1,5 +1,5 @@ import { VoiceChannel, StageChannel, Collection, Snowflake } from "discord.js"; -import { entersState, joinVoiceChannel, VoiceConnection, VoiceConnectionStatus } from "@discordjs/voice"; +import { DiscordGatewayAdapterCreator, entersState, joinVoiceChannel, VoiceConnection, VoiceConnectionStatus } from "@discordjs/voice"; import { StreamDispatcher } from "./BasicStreamDispatcher"; class VoiceUtils { @@ -47,12 +47,13 @@ class VoiceUtils { options?: { deaf?: boolean; maxTime?: number; + adapter?: DiscordGatewayAdapterCreator; } ) { let conn = joinVoiceChannel({ guildId: channel.guild.id, channelId: channel.id, - adapterCreator: (channel.guild as any).voiceAdapterCreator, // eslint-disable-line @typescript-eslint/no-explicit-any + adapterCreator: options.adapter ?? (channel.guild as any).voiceAdapterCreator, // eslint-disable-line @typescript-eslint/no-explicit-any selfDeaf: Boolean(options.deaf) }); diff --git a/src/index.ts b/src/index.ts index 2cae5d4..8acfe26 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,4 +6,5 @@ export { Queue } from "./Structures/Queue"; export { Track } from "./Structures/Track"; export { VoiceUtils } from "./VoiceInterface/VoiceUtils"; export { VoiceEvents, StreamDispatcher } from "./VoiceInterface/BasicStreamDispatcher"; +export { VoiceAdapterCreator } from "./VoiceInterface/AdapterCreator"; export * from "./types/types"; diff --git a/src/types/types.ts b/src/types/types.ts index a3c043e..2bd2dd7 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -453,4 +453,4 @@ export interface PlaylistJSON { export interface DiscordPlayerInitOptions { autoRegisterExtractor?: boolean; ytdlOptions?: downloadOptions; -} \ No newline at end of file +} diff --git a/src/utils/AudioFilters.ts b/src/utils/AudioFilters.ts index f0860f4..8afefbf 100644 --- a/src/utils/AudioFilters.ts +++ b/src/utils/AudioFilters.ts @@ -113,4 +113,4 @@ const FilterList = { }; export default FilterList; -export { FilterList as AudioFilters }; \ No newline at end of file +export { FilterList as AudioFilters }; diff --git a/src/utils/Util.ts b/src/utils/Util.ts index 120e7d0..27d07b7 100644 --- a/src/utils/Util.ts +++ b/src/utils/Util.ts @@ -96,8 +96,8 @@ class Util { } static get noop() { - return () => { }; // eslint-disable-line @typescript-eslint/no-empty-function + return () => {}; // eslint-disable-line @typescript-eslint/no-empty-function } } -export { Util }; \ No newline at end of file +export { Util };