From 2a6d9a74f4eda15a8e71994b3600d66c11bafcc7 Mon Sep 17 00:00:00 2001 From: Snowflake107 Date: Thu, 24 Jun 2021 11:05:12 +0545 Subject: [PATCH] queue utils --- README.md | 2 +- docs/general/welcome.md | 2 +- docs/migrating/migrating.md | 4 +- example/music-bot/index.js | 19 ++++- example/music-bot/package.json | 2 +- package.json | 2 +- src/Player.ts | 1 + src/Structures/Queue.ts | 139 ++++++++++++++++++++++++++++++++- src/types/types.ts | 4 +- yarn.lock | 8 +- 10 files changed, 171 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 5e3c205..1bc1bdb 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ client.on("message", async (message) => { } const track = await client.player.search(args[0], { - searchEngine: QueryType.YOUTUBE_SEARCH + requestedBy: message.author }).then(x => x.tracks[1]); if (!track) return void message.reply("Track not found!"); diff --git a/docs/general/welcome.md b/docs/general/welcome.md index 5e3c205..1bc1bdb 100644 --- a/docs/general/welcome.md +++ b/docs/general/welcome.md @@ -92,7 +92,7 @@ client.on("message", async (message) => { } const track = await client.player.search(args[0], { - searchEngine: QueryType.YOUTUBE_SEARCH + requestedBy: message.author }).then(x => x.tracks[1]); if (!track) return void message.reply("Track not found!"); diff --git a/docs/migrating/migrating.md b/docs/migrating/migrating.md index 6fbe25d..77b9c4d 100644 --- a/docs/migrating/migrating.md +++ b/docs/migrating/migrating.md @@ -8,7 +8,9 @@ The new update brings new features as well as better management of different thi ```diff - player.play(message, query); + const queue = player.createQueue(message.guild); -+ const song = await player.search(query); ++ const song = await player.search(query, { ++ requestedBy: message.author +}); + + try { + await queue.connect(message.member.voice.channel); diff --git a/example/music-bot/index.js b/example/music-bot/index.js index fcd9f57..41a6286 100644 --- a/example/music-bot/index.js +++ b/example/music-bot/index.js @@ -254,7 +254,24 @@ client.on("interaction", async (interaction) => { await interaction.defer(); const queue = player.getQueue(interaction.guildID); if (!queue || !queue.playing) return void interaction.followUp({ content: "❌ | No music is being played!" }); - return void interaction.followUp({ content: `🎶 | Current song: **${queue.current.title}**!` }); + const progress = queue.createProgressBar(); + const perc = queue.getPlayerTimestamp(); + + return void interaction.followUp({ + embeds: [ + { + title: "Now Playing", + description: `🎶 | **${queue.current.title}**! (\`${perc.progress}%\`)`, + fields: [ + { + name: "\u200b", + value: progress + } + ], + color: 0xffffff + } + ] + }); } else if (interaction.commandName === "loop") { await interaction.defer(); const queue = player.getQueue(interaction.guildID); diff --git a/example/music-bot/package.json b/example/music-bot/package.json index 8470907..b494ecf 100644 --- a/example/music-bot/package.json +++ b/example/music-bot/package.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@discordjs/opus": "^0.5.3", - "discord-player": "^5.0.0-dev.3fd002187de27ed6dc3398fd8f4f5dfe309ba350", + "discord-player": "^5.0.0-dev.1d88db17117d79a26967895675c17f117e52878d", "discord.js": "^13.0.0-dev.c850ae10270076c4b2e10b130dd8f88eed4ed201" } } diff --git a/package.json b/package.json index 41c236e..099f711 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@typescript-eslint/eslint-plugin": "^4.28.0", "@typescript-eslint/parser": "^4.28.0", "discord-api-types": "^0.18.1", - "discord.js": "^13.0.0-dev.c850ae10270076c4b2e10b130dd8f88eed4ed201", + "discord.js": "^13.0.0-dev.6d3d00b44577a70e840f0187d6894043677c5329", "discord.js-docgen": "discordjs/docgen#ts-patch", "eslint": "^7.29.0", "jsdoc-babel": "^0.5.0", diff --git a/src/Player.ts b/src/Player.ts index 72fdce8..1f1b10e 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -188,6 +188,7 @@ class Player extends EventEmitter { // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const [_, extractor] of this.extractors) { + if (options.blockExtractor) break; if (!extractor.validate(query)) continue; const data = await extractor.handle(query); if (data && data.data.length) { diff --git a/src/Structures/Queue.ts b/src/Structures/Queue.ts index bcb44dc..8b6ee5d 100644 --- a/src/Structures/Queue.ts +++ b/src/Structures/Queue.ts @@ -2,7 +2,7 @@ import { Collection, Guild, StageChannel, VoiceChannel } from "discord.js"; import { Player } from "../Player"; import { StreamDispatcher } from "../VoiceInterface/BasicStreamDispatcher"; import Track from "./Track"; -import { PlayerOptions, PlayOptions, QueueFilters, QueueRepeatMode } from "../types/types"; +import { PlayerOptions, PlayerProgressbarOptions, PlayOptions, QueueFilters, QueueRepeatMode } from "../types/types"; import ytdl from "discord-ytdl-core"; import { AudioResource, StreamType } from "@discordjs/voice"; import { Util } from "../utils/Util"; @@ -300,6 +300,11 @@ class Queue { return NC ? playbackTime * NC : VW ? playbackTime * VW : playbackTime; } + set streamTime(time: number) { + this.#watchDestroyed(); + this.seek(time); + } + /** * Returns enabled filters * @returns {AudioFilters} @@ -398,6 +403,138 @@ class Queue { this.previousTracks = []; } + /** + * Stops the player + * @returns {void} + */ + stop() { + return this.destroy(); + } + + /** + * Shuffles this queue + * @returns {boolean} + */ + shuffle() { + if (!this.tracks.length || this.tracks.length < 3) return false; + const currentTrack = this.tracks.shift(); + + for (let i = this.tracks.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [this.tracks[i], this.tracks[j]] = [this.tracks[j], this.tracks[i]]; + } + + this.tracks.unshift(currentTrack); + + return true; + } + + /** + * Removes a track from the queue + * @param {Track|number} track The track to remove + * @returns {Track} + */ + remove(track: Track | number) { + let trackFound: Track = null; + if (typeof track === "number") { + trackFound = this.tracks[track]; + if (trackFound) { + this.tracks = this.tracks.filter((t) => t._trackID !== trackFound._trackID); + } + } else { + trackFound = this.tracks.find((s) => s._trackID === track._trackID); + if (trackFound) { + this.tracks = this.tracks.filter((s) => s._trackID !== trackFound._trackID); + } + } + + return trackFound; + } + + /** + * Jumps to particular track + * @param {Track|number} track The track + * @returns {void} + */ + jump(track: Track | number): void { + const foundTrack = this.remove(track); + if (!foundTrack) throw new Error("Track not found"); + this.tracks.splice(1, 0, foundTrack); + + return void this.skip(); + } + + /** + * @typedef {object} PlayerTimestamp + * @param {string} current The current progress + * @param {string} end The total time + * @param {number} progress Progress in % + */ + + /** + * Returns player stream timestamp + * @param {boolean} [queue=false] If it should generate timestamp for the queue + * @returns {PlayerTimestamp} + */ + getPlayerTimestamp(queue = false) { + const previousTracksTime = this.previousTracks.length > 0 ? this.previousTracks.reduce((p, c) => p + c.durationMS, 0) : 0; + const currentStreamTime = queue ? previousTracksTime + this.streamTime : this.streamTime; + const totalTracksTime = this.totalTime; + const totalTime = queue ? previousTracksTime + totalTracksTime : this.current.durationMS; + + const currentTimecode = Util.buildTimeCode(Util.parseMS(currentStreamTime)); + const endTimecode = Util.buildTimeCode(Util.parseMS(totalTime)); + + return { + current: currentTimecode, + end: endTimecode, + progress: Math.round((currentStreamTime / totalTime) * 100) + }; + } + + /** + * Creates progress bar string + * @param {PlayerProgressbarOptions} options The progress bar options + * @returns {string} + */ + createProgressBar(options: PlayerProgressbarOptions = { timecodes: true, queue: false }) { + const previousTracksTime = this.previousTracks.length > 0 ? this.previousTracks.reduce((p, c) => p + c.durationMS, 0) : 0; + const currentStreamTime = options.queue ? previousTracksTime + this.streamTime : this.streamTime; + const totalTracksTime = this.totalTime; + const totalTime = options.queue ? previousTracksTime + totalTracksTime : this.current.durationMS; + const length = typeof options.length === "number" ? (options.length <= 0 || options.length === Infinity ? 15 : options.length) : 15; + + const index = Math.round((currentStreamTime / totalTime) * length); + const indicator = typeof options.indicator === "string" && options.indicator.length > 0 ? options.indicator : "🔘"; + const line = typeof options.line === "string" && options.line.length > 0 ? options.line : "▬"; + + if (index >= 1 && index <= length) { + const bar = line.repeat(length - 1).split(""); + bar.splice(index, 0, indicator); + if (options.timecodes) { + const timestamp = this.getPlayerTimestamp(options.queue); + return `${timestamp.current} ┃ ${bar.join("")} ┃ ${timestamp.end}`; + } else { + return `${bar.join("")}`; + } + } else { + if (options.timecodes) { + const timestamp = this.getPlayerTimestamp(options.queue); + return `${timestamp.current} ┃ ${indicator}${line.repeat(length - 1)} ┃ ${timestamp.end}`; + } else { + return `${indicator}${line.repeat(length - 1)}`; + } + } + } + + /** + * Total duration + * @type {Number} + */ + get totalTime(): number { + return this.tracks.length > 0 ? this.tracks.map((t) => t.durationMS).reduce((p, c) => p + c) : 0; + } + /** * Play stream in a voice/stage channel * @param {Track} [src] The track to play (if empty, uses first track from the queue) diff --git a/src/types/types.ts b/src/types/types.ts index 2b68592..f999fb2 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -337,11 +337,13 @@ export interface PlayOptions { /** * @typedef {object} SearchOptions * @property {UserResolvable} requestedBy The user who requested this search - * @property {QueryType} searchEngine The query search engine + * @property {QueryType} [searchEngine=QueryType.AUTO] The query search engine + * @property {boolean} [blockExtractor=false] If it should block custom extractors */ export interface SearchOptions { requestedBy: UserResolvable; searchEngine?: QueryType; + blockExtractor?: boolean; } /** diff --git a/yarn.lock b/yarn.lock index 75438dd..25a2712 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2003,10 +2003,10 @@ discord.js-docgen@discordjs/docgen#ts-patch: tsubaki "^1.3.2" yargs "^14.0.0" -discord.js@^13.0.0-dev.c850ae10270076c4b2e10b130dd8f88eed4ed201: - version "13.0.0-dev.c850ae10270076c4b2e10b130dd8f88eed4ed201" - resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-13.0.0-dev.c850ae10270076c4b2e10b130dd8f88eed4ed201.tgz#8790b4f48ed36106ea08d2dc3148ee51ef321497" - integrity sha512-yp/s0AxcTNtvt9zcZv3kb0szLTFN2rqCoz4hclRE7C3umD7kgvcbF4O+k3RJ1ef6CAlrsZxvept/zYGqIn/dTA== +discord.js@^13.0.0-dev.6d3d00b44577a70e840f0187d6894043677c5329: + version "13.0.0-dev.6d3d00b44577a70e840f0187d6894043677c5329" + resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-13.0.0-dev.6d3d00b44577a70e840f0187d6894043677c5329.tgz#7593fc7d86651f65c4e1f6a67802b7b72c532695" + integrity sha512-K2jlMXX4cB8+/6CYh/QywULgUcMSJcR6CycEGnONeCskrejtArCUmiV8dKu20ABv3CeHWgTVN261BQWRTTelRg== dependencies: "@discordjs/collection" "^0.1.6" "@discordjs/form-data" "^3.0.1"