From eaae7a0ef64b51ec0f752e5f633b05ec49febc21 Mon Sep 17 00:00:00 2001 From: execaman <151807496+execaman@users.noreply.github.com> Date: Thu, 9 Oct 2025 15:46:16 +0530 Subject: [PATCH] feat: auto-destroy player on guild or channel delete --- src/Typings/Main/Player.ts | 5 ++-- src/Typings/Voice/VoiceManager.ts | 41 ++++++++++++++++++++++++++++++- src/Voice/VoiceManager.ts | 24 +++++++++++++++++- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/Typings/Main/Player.ts b/src/Typings/Main/Player.ts index d922954..1d0922b 100644 --- a/src/Typings/Main/Player.ts +++ b/src/Typings/Main/Player.ts @@ -1,6 +1,7 @@ import type { EmptyObject, JsonObject } from "../Utility"; import type { Exception, PlayerState, TrackEndReason } from "../API"; import type { CreateNodeOptions, NodeEventMap } from "../Node"; +import type { QueueDestroyReasons, VoiceDestroyReasons } from "../Voice"; import type { CreateQueueOptions } from "../Queue"; import type { Node } from "../../Node"; @@ -23,12 +24,12 @@ export interface PlayerEventMap extends Record { voiceConnect: [voice: VoiceState]; voiceClose: [voice: VoiceState, code: number, reason: string, byRemote: boolean]; voiceChange: [voice: VoiceState, previousNode: Node, wasPlaying: boolean]; - voiceDestroy: [voice: VoiceState, reason: string]; + voiceDestroy: [voice: VoiceState, reason: VoiceDestroyReasons]; queueCreate: [queue: Queue]; queueUpdate: [queue: Queue, state: PlayerState]; queueFinish: [queue: Queue]; - queueDestroy: [queue: Queue, reason: string]; + queueDestroy: [queue: Queue, reason: QueueDestroyReasons]; trackStart: [queue: Queue, track: Track]; trackStuck: [queue: Queue, track: Track, thresholdMs: number]; diff --git a/src/Typings/Voice/VoiceManager.ts b/src/Typings/Voice/VoiceManager.ts index d19411c..242476c 100644 --- a/src/Typings/Voice/VoiceManager.ts +++ b/src/Typings/Voice/VoiceManager.ts @@ -15,7 +15,11 @@ export interface CommonDispatchPayloadInfo { */ export interface ClientReadyPayload extends CommonDispatchPayloadInfo { t: "READY"; - d: { user: { id: string } }; + d: { + user: { + id: string; + }; + }; } /** @@ -48,6 +52,41 @@ export interface VoiceServerUpdatePayload extends CommonDispatchPayloadInfo { }; } +/** + * Discord guild delete payload + */ +export interface GuildDeletePayload extends CommonDispatchPayloadInfo { + t: "GUILD_DELETE"; + d: { + id: string; + unavailable?: true; + }; +} + +/** + * Discord channel delete payload (partial, essential only) + */ +export interface ChannelDeletePayload extends CommonDispatchPayloadInfo { + t: "CHANNEL_DELETE"; + d: { + id: string; + guild_id?: string; + }; +} + +/** + * Discord dispatch payload + */ +export type DiscordDispatchPayload = + | ClientReadyPayload + | VoiceStateUpdatePayload + | VoiceServerUpdatePayload + | GuildDeletePayload + | ChannelDeletePayload; + +export type VoiceDestroyReasons = ("destroyed" | "guildDeleted" | "channelDeleted") | (string & {}); +export type QueueDestroyReasons = VoiceDestroyReasons; + /** * VoiceManager intrinsic data */ diff --git a/src/Voice/VoiceManager.ts b/src/Voice/VoiceManager.ts index 7be5a3c..1bb3ee3 100644 --- a/src/Voice/VoiceManager.ts +++ b/src/Voice/VoiceManager.ts @@ -4,9 +4,13 @@ import { isString, noop } from "../Functions"; import { VoiceRegion, VoiceState } from "."; import type { + ChannelDeletePayload, ClientReadyPayload, ConnectOptions, CreateQueueOptions, + DiscordDispatchPayload, + GuildDeletePayload, + VoiceDestroyReasons, VoiceServerUpdatePayload, VoiceStateInfo, VoiceStateUpdatePayload, @@ -200,7 +204,7 @@ export class VoiceManager implements Partial> { * @param payload 'Dispatch' payload received from Discord */ handleDispatch(payload: unknown): void; - handleDispatch(payload: ClientReadyPayload | VoiceStateUpdatePayload | VoiceServerUpdatePayload) { + handleDispatch(payload: DiscordDispatchPayload) { if (payload.op !== 0) return; switch (payload.t) { case "VOICE_STATE_UPDATE": @@ -209,6 +213,12 @@ export class VoiceManager implements Partial> { case "VOICE_SERVER_UPDATE": this.#onServerUpdate(payload.d); return; + case "GUILD_DELETE": + this.#onGuildDelete(payload.d); + return; + case "CHANNEL_DELETE": + this.#onChannelDelete(payload.d); + return; case "READY": this.#onClientReady(payload.d); return; @@ -220,6 +230,18 @@ export class VoiceManager implements Partial> { return this.#player.init(data.user.id); } + async #onGuildDelete(data: GuildDeletePayload["d"]) { + if (data.unavailable) return; + return this.destroy(data.id, "guildDeleted" satisfies VoiceDestroyReasons); + } + + async #onChannelDelete(data: ChannelDeletePayload["d"]) { + if (!data.guild_id) return; + const voice = this.#voices.get(data.guild_id); + if (voice?.channelId !== data.id) return; + return voice.destroy("channelDeleted" satisfies VoiceDestroyReasons); + } + #onStateUpdate(data: VoiceStateUpdatePayload["d"]) { if (!data.guild_id || data.user_id !== this.#player.clientId) return; if (data.channel_id === null) {