diff --git a/example/index.ts b/example/index.ts index 80f5a42..a62920d 100644 --- a/example/index.ts +++ b/example/index.ts @@ -1,7 +1,6 @@ -import { Client, GuildMember, Message, TextChannel } from "discord.js"; -import { Player, Queue, Track } from "../src/index"; -import { QueryType, QueueRepeatMode } from "../src/types/types"; +import { Client, GuildMember, TextChannel } from "discord.js"; import { config } from "./config"; +import { Player, Queue, QueryType, QueueRepeatMode } from "../src/index"; // use this in prod. // import { Player, Queue } from "discord-player"; @@ -23,10 +22,6 @@ client.on("warn", console.warn); const player = new Player(client); player.on("error", console.error); -player.on("debug", (queue, message) => { - console.log(`DEBUG :: ${queue.guild.name}`); - console.log(message); -}); player.on("trackStart", (queue, track) => { const guildQueue = queue as Queue; @@ -48,6 +43,11 @@ player.on("channelEmpty", (queue) => { guildQueue.metadata.send("❌ | Nobody is in the voice channel, leaving..."); }); +player.on("queueEnd", (queue) => { + const guildQueue = queue as Queue; + guildQueue.metadata.send("✅ | Queue finished!"); +}); + client.on("message", async (message) => { if (message.author.bot || !message.guild) return; if (!client.application?.owner) await client.application?.fetch(); diff --git a/package.json b/package.json index d56539a..d2a2257 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ }, "homepage": "https://discord-player.js.org", "dependencies": { - "@discordjs/voice": "^0.4.0", + "@discordjs/voice": "^0.5.0", "discord-ytdl-core": "^5.0.3", "soundcloud-scraper": "^5.0.0", "spotify-url-info": "^2.2.3", @@ -68,7 +68,7 @@ "@types/node": "^15.12.2", "@types/ws": "^7.4.4", "discord-api-types": "^0.18.1", - "discord.js": "^13.0.0-dev.02693bc02f45980d8165820a103220f0027b96b7", + "discord.js": "^13.0.0-dev.a3cbcca13da1af416c219bd64a0a6e84bb87a057", "discord.js-docgen": "discordjs/docgen#ts-patch", "jsdoc-babel": "^0.5.0", "prettier": "^2.3.1", diff --git a/src/Player.ts b/src/Player.ts index 230bd3a..7e4b9d0 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -1,4 +1,4 @@ -import { Client, Collection, Guild, Snowflake, VoiceState } from "discord.js"; +import { Client, Collection, Guild, GuildResolvable, Snowflake, VoiceState } from "discord.js"; import { TypedEmitter as EventEmitter } from "tiny-typed-emitter"; import { Queue } from "./Structures/Queue"; import { VoiceUtils } from "./VoiceInterface/VoiceUtils"; @@ -88,11 +88,13 @@ class DiscordPlayer extends EventEmitter { /** * Creates a queue for a guild if not available, else returns existing queue - * @param {Discord.Guild} guild The guild + * @param {GuildResolvable} guild The guild * @param {PlayerOptions} queueInitOptions Queue init options * @returns {Queue} */ - createQueue(guild: Guild, queueInitOptions?: PlayerOptions & { metadata?: T }): Queue { + createQueue(guild: GuildResolvable, queueInitOptions?: PlayerOptions & { metadata?: T }): Queue { + guild = this.client.guilds.resolve(guild); + if (!guild) throw new Error("Unknown Guild"); if (this.queues.has(guild.id)) return this.queues.get(guild.id) as Queue; const _meta = queueInitOptions.metadata; @@ -107,25 +109,29 @@ class DiscordPlayer extends EventEmitter { /** * Returns the queue if available - * @param {Discord.Snowflake} guild The guild id + * @param {GuildResolvable} guild The guild id * @returns {Queue} */ - getQueue(guild: Snowflake) { - return this.queues.get(guild) as Queue; + getQueue(guild: GuildResolvable) { + guild = this.client.guilds.resolve(guild); + if (!guild) throw new Error("Unknown Guild"); + return this.queues.get(guild.id) as Queue; } /** * Deletes a queue and returns deleted queue object - * @param {Discord.Snowflake} guild The guild id to remove + * @param {GuildResolvable} guild The guild id to remove * @returns {Queue} */ - deleteQueue(guild: Snowflake) { + deleteQueue(guild: GuildResolvable) { + guild = this.client.guilds.resolve(guild); + if (!guild) throw new Error("Unknown Guild"); const prev = this.getQueue(guild); try { prev.destroy(); } catch {} - this.queues.delete(guild); + this.queues.delete(guild.id); return prev; } diff --git a/src/Structures/Playlist.ts b/src/Structures/Playlist.ts index c381090..bcdb7d0 100644 --- a/src/Structures/Playlist.ts +++ b/src/Structures/Playlist.ts @@ -48,7 +48,7 @@ class Playlist { tracks: [] as TrackJSON[] }; - if (withTracks) payload.tracks = this.tracks.map((m) => m.toJSON()); + if (withTracks) payload.tracks = this.tracks.map((m) => m.toJSON(true)); return payload as PlaylistJSON; } diff --git a/src/Structures/Queue.ts b/src/Structures/Queue.ts index d050469..e586324 100644 --- a/src/Structures/Queue.ts +++ b/src/Structures/Queue.ts @@ -59,7 +59,7 @@ class Queue { useSafeSearch: false, disableAutoRegister: false, fetchBeforeQueued: false, - initialVolume: 100 + initialVolume: 50 } as PlayerOptions, options ); @@ -195,6 +195,13 @@ class Queue { return this.connection.volume; } + /** + * Alternative volume setter + */ + set volume(amount: number) { + this.setVolume(amount); + } + /** * Plays previous track * @returns {Promise} diff --git a/src/Structures/Track.ts b/src/Structures/Track.ts index 6bcaab0..9d3a2b6 100644 --- a/src/Structures/Track.ts +++ b/src/Structures/Track.ts @@ -155,7 +155,7 @@ class Track { * Raw JSON representation of this track * @returns {object} */ - toJSON() { + toJSON(hidePlaylist?: boolean) { return { title: this.title, description: this.description, @@ -166,7 +166,7 @@ class Track { durationMS: this.durationMS, views: this.views, requestedBy: this.requestedBy.id, - playlist: this.playlist?.toJSON(false) ?? null + playlist: hidePlaylist ? null : this.playlist?.toJSON(false) ?? null } as TrackJSON; } } diff --git a/src/VoiceInterface/BasicStreamDispatcher.ts b/src/VoiceInterface/BasicStreamDispatcher.ts index cabd202..57fc0b9 100644 --- a/src/VoiceInterface/BasicStreamDispatcher.ts +++ b/src/VoiceInterface/BasicStreamDispatcher.ts @@ -8,12 +8,14 @@ import { entersState, StreamType, VoiceConnection, - VoiceConnectionStatus + VoiceConnectionStatus, + VoiceConnectionDisconnectReason } from "@discordjs/voice"; import { StageChannel, VoiceChannel } from "discord.js"; import { Duplex, Readable } from "stream"; import { TypedEmitter as EventEmitter } from "tiny-typed-emitter"; import Track from "../Structures/Track"; +import { Util } from "../utils/Util"; export interface VoiceEvents { error: (error: AudioPlayerError) => any; @@ -26,8 +28,8 @@ class BasicStreamDispatcher extends EventEmitter { public readonly voiceConnection: VoiceConnection; public readonly audioPlayer: AudioPlayer; public readonly channel: VoiceChannel | StageChannel; - public connectPromise?: Promise; public audioResource?: AudioResource; + private readyLock: boolean = false; constructor(connection: VoiceConnection, channel: VoiceChannel | StageChannel) { super(); @@ -36,26 +38,31 @@ class BasicStreamDispatcher extends EventEmitter { this.audioPlayer = createAudioPlayer(); this.channel = channel; - this.voiceConnection.on("stateChange", (_, newState) => { + this.voiceConnection.on("stateChange", async (_, newState) => { if (newState.status === VoiceConnectionStatus.Disconnected) { - if (this.voiceConnection.reconnectAttempts < 5) { - setTimeout(() => { - if (this.voiceConnection.state.status === VoiceConnectionStatus.Disconnected) { - this.voiceConnection.reconnect(); - } - }, (this.voiceConnection.reconnectAttempts + 1) * 5000).unref(); + if (newState.reason === VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) { + try { + await entersState(this.voiceConnection, VoiceConnectionStatus.Connecting, 5000); + } catch { + this.voiceConnection.destroy(); + } + } else if (this.voiceConnection.rejoinAttempts < 5) { + await Util.wait((this.voiceConnection.rejoinAttempts + 1) * 5000); + this.voiceConnection.rejoin(); } else { this.voiceConnection.destroy(); } } else if (newState.status === VoiceConnectionStatus.Destroyed) { this.end(); - } else if (!this.connectPromise && (newState.status === VoiceConnectionStatus.Connecting || newState.status === VoiceConnectionStatus.Signalling)) { - this.connectPromise = entersState(this.voiceConnection, VoiceConnectionStatus.Ready, 20000) - .then(() => undefined) - .catch(() => { - if (this.voiceConnection.state.status !== VoiceConnectionStatus.Destroyed) this.voiceConnection.destroy(); - }) - .finally(() => (this.connectPromise = undefined)); + } else if (!this.readyLock && (newState.status === VoiceConnectionStatus.Connecting || newState.status === VoiceConnectionStatus.Signalling)) { + this.readyLock = true; + try { + await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, 20000); + } catch { + if (this.voiceConnection.state.status !== VoiceConnectionStatus.Destroyed) this.voiceConnection.destroy(); + } finally { + this.readyLock = false; + } } }); diff --git a/src/index.ts b/src/index.ts index 1d5c948..2cae5d4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,3 +6,4 @@ export { Queue } from "./Structures/Queue"; export { Track } from "./Structures/Track"; export { VoiceUtils } from "./VoiceInterface/VoiceUtils"; export { VoiceEvents, StreamDispatcher } from "./VoiceInterface/BasicStreamDispatcher"; +export * from "./types/types"; diff --git a/src/utils/Util.ts b/src/utils/Util.ts index 6489b6e..7d7488a 100644 --- a/src/utils/Util.ts +++ b/src/utils/Util.ts @@ -47,6 +47,10 @@ class Util { return null; } } + + static wait(time: number) { + return new Promise((r) => setTimeout(r, time).unref()); + } } export { Util }; diff --git a/yarn.lock b/yarn.lock index 2d1a3bd..97a7d61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1108,10 +1108,10 @@ "@discordjs/node-pre-gyp" "^0.4.0" node-addon-api "^3.2.1" -"@discordjs/voice@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@discordjs/voice/-/voice-0.4.0.tgz#d5e19f3ee08de2f869e7d057bfff93138fcaf69e" - integrity sha512-2GDs+QOCX525rp4yOzqu8RneTUDGdtyExnJMgXHSwCxmPQDWAMVd3dUiCNqLaCEjsQTMhDGEiThpuSDh6r49KQ== +"@discordjs/voice@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@discordjs/voice/-/voice-0.5.0.tgz#238d6f8c1dc7e30ff781edb08fb0540bca5aa23d" + integrity sha512-YPfY8ium1lExmRETz+vC4d3gGvHhGvWQMWLTOkNBoUkN6VyEpO/RVrL5EI+0qUefKAWwv2Gus/c7QM1xhAUwow== dependencies: "@types/ws" "^7.4.4" discord-api-types "^0.18.1" @@ -2007,10 +2007,10 @@ discord.js-docgen@discordjs/docgen#ts-patch: tsubaki "^1.3.2" yargs "^14.0.0" -discord.js@^13.0.0-dev.02693bc02f45980d8165820a103220f0027b96b7: - version "13.0.0-dev.02693bc02f45980d8165820a103220f0027b96b7" - resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-13.0.0-dev.02693bc02f45980d8165820a103220f0027b96b7.tgz#5baa6c758b970a1e6175d06373d4ab33eb6a4164" - integrity sha512-nzbmF5MLSjpdr8DS7SpC9q291NQ8XkHlledM4lz+uFJ4YgnMmTpi6e0FgF7v2kpTJPqTsXDRY3rWBv6Dat+08A== +discord.js@^13.0.0-dev.a3cbcca13da1af416c219bd64a0a6e84bb87a057: + version "13.0.0-dev.a3cbcca13da1af416c219bd64a0a6e84bb87a057" + resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-13.0.0-dev.a3cbcca13da1af416c219bd64a0a6e84bb87a057.tgz#7358b44985b2423f1fdb1a9c259d26de74d662f7" + integrity sha512-9EFBA08VUt9hIZzgQ1IcNv3/EwLvo4N9RikEzv/qNYjRvonnKi70tp0dpChduM8GFxgqrcTEa2mZJGfuumBBHA== dependencies: "@discordjs/collection" "^0.1.6" "@discordjs/form-data" "^3.0.1"