From fcd8a8e186f46eb62f76ac9944195fa66f85cafc Mon Sep 17 00:00:00 2001 From: Snowflake107 Date: Sun, 20 Jun 2021 20:07:09 +0545 Subject: [PATCH] feat: documentation --- docs/extractors/extractor.md | 3 - docs/faq/custom_filters.md | 2 +- docs/faq/live_video.md | 11 --- docs/faq/pause_resume.md | 15 ---- docs/general/welcome.md | 47 +++++++++--- docs/index.yml | 4 - docs/youtube/cookies.md | 2 +- docs/youtube/proxy.md | 2 +- src/Player.ts | 24 +++++- src/Structures/ExtractorModel.ts | 6 ++ src/Structures/Playlist.ts | 83 ++++++++++++++++++++- src/Structures/Queue.ts | 43 ++++++++--- src/Structures/Track.ts | 9 ++- src/VoiceInterface/BasicStreamDispatcher.ts | 57 +++++++++++++- src/VoiceInterface/VoiceUtils.ts | 31 +++++++- src/types/types.ts | 33 ++++++++ src/utils/QueryResolver.ts | 17 +++++ src/utils/Util.ts | 42 +++++++++++ tsconfig.json | 3 +- 19 files changed, 364 insertions(+), 70 deletions(-) delete mode 100644 docs/faq/live_video.md delete mode 100644 docs/faq/pause_resume.md diff --git a/docs/extractors/extractor.md b/docs/extractors/extractor.md index 9510969..dff289b 100644 --- a/docs/extractors/extractor.md +++ b/docs/extractors/extractor.md @@ -32,9 +32,6 @@ Your extractor should have 2 methods (required): url: "Some Link" } ``` - - `important: boolean` - - You can mark your Extractor as `important` by adding `important: true` to your extractor object. Doing this will disable rest of the extractors that comes after your extractor and use your extractor to get data. By default, it is set to `false`. - `version: string` diff --git a/docs/faq/custom_filters.md b/docs/faq/custom_filters.md index fd3cfa9..30e1f1c 100644 --- a/docs/faq/custom_filters.md +++ b/docs/faq/custom_filters.md @@ -8,5 +8,5 @@ const { AudioFilters } = require("discord-player"); AudioFilters.define("3D", "apulsator=hz=0.128"); // later, it can be used like this -player.setFilters(message, { "3D": true }); +queue.setFilters(message, { "3D": true }); ``` \ No newline at end of file diff --git a/docs/faq/live_video.md b/docs/faq/live_video.md deleted file mode 100644 index 40eb4be..0000000 --- a/docs/faq/live_video.md +++ /dev/null @@ -1,11 +0,0 @@ -# How to play live videos? - -You cannot play live videos by default. If you need to play the live video, just add this option: - -```js -const player = new Player(client, { - enableLive: true // enables livestream -}); -``` - -However, you cannot use audio filters with livestreams using this library! \ No newline at end of file diff --git a/docs/faq/pause_resume.md b/docs/faq/pause_resume.md deleted file mode 100644 index d0cbc54..0000000 --- a/docs/faq/pause_resume.md +++ /dev/null @@ -1,15 +0,0 @@ -# Pause and Resume is not working properly - -This is a bug in **[discord.js#5300](https://github.com/discordjs/discord.js/issues/5300)**. - -# Fix - -You have to update your command something like this: - -```diff -- client.player.resume(message); - -+ client.player.resume(message); -+ client.player.pause(message); -+ client.player.resume(message); -``` \ No newline at end of file diff --git a/docs/general/welcome.md b/docs/general/welcome.md index 2075526..5e3c205 100644 --- a/docs/general/welcome.md +++ b/docs/general/welcome.md @@ -3,6 +3,10 @@ Complete framework to facilitate music commands using **[discord.js](https://dis [![downloadsBadge](https://img.shields.io/npm/dt/discord-player?style=for-the-badge)](https://npmjs.com/discord-player) [![versionBadge](https://img.shields.io/npm/v/discord-player?style=for-the-badge)](https://npmjs.com/discord-player) +[![discordBadge](https://img.shields.io/discord/558328638911545423?style=for-the-badge&color=7289da)](https://androz2091.fr/discord) +[![wakatime](https://wakatime.com/badge/github/Androz2091/discord-player.svg)](https://wakatime.com/badge/github/Androz2091/discord-player) + +> V5 WIP ## Installation @@ -43,13 +47,13 @@ Here is the code you will need to get started with discord-player. Then, you wil ```js const Discord = require("discord.js"), -client = new Discord.Client, +client = new Discord.Client({ intents: ["GUILD_VOICE_STATES", "GUILD_MESSAGES", "GUILDS"] }), settings = { prefix: "!", token: "Your Discord Token" }; -const { Player } = require("discord-player"); +const { Player, QueryType } = require("discord-player"); // Create a new Player (you don't need any API Key) const player = new Player(client); @@ -58,7 +62,7 @@ const player = new Player(client); client.player = player; // add the trackStart event so when a song will be played this message will be sent -client.player.on("trackStart", (message, track) => message.channel.send(`Now playing ${track.title}...`)) +client.player.on("trackStart", (queue, track) => queue.metadata.channel.send(`Now playing ${track.title}...`)) client.once("ready", () => { console.log("I'm ready !"); @@ -70,10 +74,29 @@ client.on("message", async (message) => { const command = args.shift().toLowerCase(); // !play Despacito - // will play the song "Despacito" in the voice channel - if(command === "play"){ - client.player.play(message, args[0]); - // as we registered the event above, no need to send a success message here + // will play "Despacito" in the voice channel + if (command === "play") { + if (!message.member.voice.channel) return void message.reply("You are not in a voice channel!"); + if (message.guild.me.voice.channel && message.member.voice.channelID !== message.guild.me.voice.channelID) return void message.reply("You are not in my voice channel!"); + + const queue = client.player.createQueue(message.guild, { + metadata: message + }); + + // verify vc connection + try { + if (!queue.connection) await queue.connect(message.member.voice.channel); + } catch { + queue.destroy(); + return void message.reply("Could not join your voice channel!"); + } + + const track = await client.player.search(args[0], { + searchEngine: QueryType.YOUTUBE_SEARCH + }).then(x => x.tracks[1]); + if (!track) return void message.reply("Track not found!"); + + queue.play(track); } }); @@ -106,13 +129,13 @@ These bots are made by the community, they can help you build your own! * [Discord-Music](https://github.com/inhydrox/discord-music) by [inhydrox](https://github.com/inhydrox) * [Music-bot](https://github.com/ZerioDev/Music-bot) by [ZerioDev](https://github.com/ZerioDev) -## FAQ +## Advanced -### How to use cookies +### Use cookies ```js const player = new Player(client, { - ytdlDownloadOptions: { + ytdlOptions: { requestOptions: { headers: { cookie: "YOUR_YOUTUBE_COOKIE" @@ -122,7 +145,7 @@ const player = new Player(client, { }); ``` -### How to use custom proxies +### Use custom proxies ```js const HttpsProxyAgent = require("https-proxy-agent"); @@ -132,7 +155,7 @@ const proxy = "http://user:pass@111.111.111.111:8080"; const agent = HttpsProxyAgent(proxy); const player = new Player(client, { - ytdlDownloadOptions: { + ytdlOptions: { requestOptions: { agent } } }); diff --git a/docs/index.yml b/docs/index.yml index 3ab6cd4..d2ebc01 100644 --- a/docs/index.yml +++ b/docs/index.yml @@ -10,10 +10,6 @@ files: - name: Custom Filters path: custom_filters.md - - name: Livestreams - path: live_video.md - - name: Pause & Resume - path: pause_resume.md - name: YouTube files: - name: Using Cookies diff --git a/docs/youtube/cookies.md b/docs/youtube/cookies.md index 2405dcf..e448857 100644 --- a/docs/youtube/cookies.md +++ b/docs/youtube/cookies.md @@ -4,7 +4,7 @@ const { Player } = require("discord-player"); const player = new Player(client, { - ytdlDownloadOptions: { + ytdlOptions: { requestOptions: { headers: { cookie: "YOUR_YOUTUBE_COOKIE" diff --git a/docs/youtube/proxy.md b/docs/youtube/proxy.md index 3dc8ba5..35fe5a8 100644 --- a/docs/youtube/proxy.md +++ b/docs/youtube/proxy.md @@ -9,7 +9,7 @@ const proxy = "http://user:pass@111.111.111.111:8080"; const agent = HttpsProxyAgent(proxy); const player = new Player(client, { - ytdlDownloadOptions: { + ytdlOptions: { requestOptions: { agent } } }); diff --git a/src/Player.ts b/src/Player.ts index 44e0510..0e4dbcc 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -56,6 +56,13 @@ class DiscordPlayer extends EventEmitter { } } + /** + * Handles voice state update + * @param {VoiceState} oldState The old voice state + * @param {VoiceState} newState The new voice state + * @returns {void} + * @private + */ private _handleVoiceState(oldState: VoiceState, newState: VoiceState): void { const queue = this.getQueue(oldState.guild.id); if (!queue) return; @@ -141,7 +148,7 @@ class DiscordPlayer extends EventEmitter { * Search tracks * @param {string|Track} query The search query * @param {UserResolvable} requestedBy The person who requested track search - * @returns {Promise<{playlist?: Playlist; tracks: Track[]}>} + * @returns {Promise} */ async search(query: string | Track, options: SearchOptions) { if (query instanceof Track) return { playlist: null, tracks: [query] }; @@ -400,6 +407,12 @@ class DiscordPlayer extends EventEmitter { } } + /** + * @param {string} extractorName The extractor name + * @param {ExtractorModel|any} extractor The extractor object + * @param {boolean} [force=false] + * @returns {ExtractorModel} + */ use(extractorName: string, extractor: ExtractorModel | any, force = false): ExtractorModel { if (!extractorName) throw new Error("Cannot use unknown extractor!"); if (this.extractors.has(extractorName) && !force) return this.extractors.get(extractorName); @@ -418,6 +431,11 @@ class DiscordPlayer extends EventEmitter { return model; } + /** + * Removes registered extractor + * @param {string} extractorName The extractor name + * @returns {ExtractorModel} + */ unuse(extractorName: string) { if (!this.extractors.has(extractorName)) throw new Error(`Cannot find extractor "${extractorName}"`); const prev = this.extractors.get(extractorName); @@ -425,6 +443,10 @@ class DiscordPlayer extends EventEmitter { return prev; } + /** + * Generates a report of the dependencies used by the `@discordjs/voice` module. Useful for debugging. + * @returns {string} + */ scanDeps() { return generateDependencyReport(); } diff --git a/src/Structures/ExtractorModel.ts b/src/Structures/ExtractorModel.ts index 9b0ad5e..7fec971 100644 --- a/src/Structures/ExtractorModel.ts +++ b/src/Structures/ExtractorModel.ts @@ -16,6 +16,12 @@ class ExtractorModel { */ this.name = extractorName; + /** + * The raw model + * @name ExtractorModel#_raw + * @type {any} + * @private + */ Object.defineProperty(this, "_raw", { value: data, configurable: false, writable: false, enumerable: false }); } diff --git a/src/Structures/Playlist.ts b/src/Structures/Playlist.ts index bcdb7d0..5cbfa98 100644 --- a/src/Structures/Playlist.ts +++ b/src/Structures/Playlist.ts @@ -16,25 +16,106 @@ class Playlist { }; public id: string; public url: string; - public rawPlaylist?: any; + public readonly rawPlaylist?: any; + /** + * Playlist constructor + * @param {Player} player The player + * @param {PlaylistInitData} data The data + */ constructor(player: Player, data: PlaylistInitData) { + /** + * The player + * @name Playlist#player + * @type {Player} + * @readonly + */ this.player = player; + + /** + * The tracks in this playlist + * @name Playlist#tracks + * @type {Track[]} + */ this.tracks = data.tracks ?? []; + + /** + * The author of this playlist + * @name Playlist#author + * @type {object} + */ this.author = data.author; + + /** + * The description + * @name Playlist#description + * @type {string} + */ this.description = data.description; + + /** + * The thumbnail of this playlist + * @name Playlist#thumbnail + * @type {string} + */ this.thumbnail = data.thumbnail; + + /** + * The playlist type: + * - `album` + * - `playlist` + * @name Playlist#type + * @type {string} + */ this.type = data.type; + + /** + * The source of this playlist: + * - `youtube` + * - `soundcloud` + * - `spotify` + * - `arbitrary` + * @name Playlist#source + * @type {string} + */ this.source = data.source; + + /** + * The playlist id + * @name Playlist#id + * @type {string} + */ this.id = data.id; + + /** + * The playlist url + * @name Playlist#url + * @type {string} + */ this.url = data.url; + + /** + * The playlist title + * @type {string} + */ this.title = data.title; + + /** + * @name Playlist#rawPlaylist + * @type {any} + * @readonly + */ } *[Symbol.iterator]() { yield* this.tracks; } + /** + * JSON representation of this playlist + * @param {boolean} [withTracks=true] If it should build json with tracks + * @returns {PlaylistJSON} + */ toJSON(withTracks = true) { const payload = { id: this.id, diff --git a/src/Structures/Queue.ts b/src/Structures/Queue.ts index 870e158..a40cbdd 100644 --- a/src/Structures/Queue.ts +++ b/src/Structures/Queue.ts @@ -27,18 +27,20 @@ class Queue { * Queue constructor * @param {Player} player The player that instantiated this queue * @param {Guild} guild The guild that instantiated this queue - * @param {PlayerOptions={}} options Player options for the queue + * @param {PlayerOptions} [options={}] Player options for the queue */ constructor(player: Player, guild: Guild, options: PlayerOptions = {}) { /** * The player that instantiated this queue * @type {Player} + * @readonly */ this.player = player; /** * The guild that instantiated this queue * @type {Guild} + * @readonly */ this.guild = guild; @@ -69,7 +71,7 @@ class Queue { /** * Returns current track - * @returns {Track} + * @type {Track} */ get current() { return this.connection.audioResource?.metadata ?? this.tracks[0]; @@ -85,7 +87,7 @@ class Queue { /** * Connects to a voice channel - * @param {StageChannel|VoiceChannel} channel + * @param {StageChannel|VoiceChannel} channel The voice/stage channel * @returns {Promise} */ async connect(channel: StageChannel | VoiceChannel) { @@ -110,6 +112,8 @@ class Queue { /** * Destroys this queue + * @param {boolean} [disconnect=this.options.leaveOnStop] If it should leave on destroy + * @returns {void} */ destroy(disconnect = this.options.leaveOnStop) { this.connection.end(); @@ -159,6 +163,7 @@ class Queue { /** * Sets bitrate * @param {number|"auto"} bitrate bitrate to set + * @returns {void} */ setBitrate(bitrate: number | "auto") { if (!this.connection?.audioResource?.encoder) return; @@ -189,20 +194,22 @@ class Queue { } /** - * Returns current volume amount + * The current volume amount + * @type {number} */ get volume() { if (!this.connection) return 100; return this.connection.volume; } - /** - * Alternative volume setter - */ set volume(amount: number) { this.setVolume(amount); } + /** + * The stream time of this queue + * @type {number} + */ get streamTime() { if (!this.connection) return 0; const playbackTime = this._streamTime + this.connection.streamTime; @@ -213,14 +220,27 @@ class Queue { return NC ? playbackTime * NC : VW ? playbackTime * VW : playbackTime; } + /** + * Returns enabled filters + * @returns {AudioFilters} + */ getFiltersEnabled() { return AudioFilters.names.filter((x) => this._activeFilters.includes(x)); } + /** + * Returns disabled filters + * @returns {AudioFilters} + */ getFiltersDisabled() { return AudioFilters.names.filter((x) => !this._activeFilters.includes(x)); } + /** + * Sets filters + * @param {QueueFilters} filters Queue filters + * @returns {Promise} + */ async setFilters(filters?: QueueFilters) { if (!filters || !Object.keys(filters).length) { // reset filters @@ -254,6 +274,11 @@ class Queue { }); } + /** + * Seeks to the given time + * @param {number} position The position + * @returns {boolean} + */ async seek(position: number) { if (!this.playing || !this.current) return false; if (position < 1) position = 0; @@ -277,8 +302,8 @@ class Queue { } /** - * @param {Track} [src] The track to play (if empty, uses first track from the queue) - * @param {PlayOptions={}} options The options + * @param {Track} [src] The track to play (if empty, uses first track from the queue) + * @param {PlayOptions} [options={}] The options * @returns {Promise} */ async play(src?: Track, options: PlayOptions = {}): Promise { diff --git a/src/Structures/Track.ts b/src/Structures/Track.ts index 9d3a2b6..f03a6a6 100644 --- a/src/Structures/Track.ts +++ b/src/Structures/Track.ts @@ -92,6 +92,13 @@ class Track { * @type {RawTrackData} */ + /** + * The track id + * @name Track#_trackID + * @type {number} + * @readonly + */ + void this._patch(data); } @@ -153,7 +160,7 @@ class Track { /** * Raw JSON representation of this track - * @returns {object} + * @returns {TrackJSON} */ toJSON(hidePlaylist?: boolean) { return { diff --git a/src/VoiceInterface/BasicStreamDispatcher.ts b/src/VoiceInterface/BasicStreamDispatcher.ts index 57fc0b9..3201cc2 100644 --- a/src/VoiceInterface/BasicStreamDispatcher.ts +++ b/src/VoiceInterface/BasicStreamDispatcher.ts @@ -24,18 +24,37 @@ export interface VoiceEvents { finish: () => any; } -class BasicStreamDispatcher extends EventEmitter { +class StreamDispatcher extends EventEmitter { public readonly voiceConnection: VoiceConnection; public readonly audioPlayer: AudioPlayer; public readonly channel: VoiceChannel | StageChannel; public audioResource?: AudioResource; private readyLock: boolean = false; + /** + * Creates new connection object + * @param {VoiceConnection} connection The connection + * @param {VoiceChannel|StageChannel} channel The connected channel + */ constructor(connection: VoiceConnection, channel: VoiceChannel | StageChannel) { super(); + /** + * The voice connection + * @type {VoiceConnection} + */ this.voiceConnection = connection; + + /** + * The audio player + * @type {AudioPlayer} + */ this.audioPlayer = createAudioPlayer(); + + /** + * The voice channel + * @type {VoiceChannel|StageChannel} + */ this.channel = channel; this.voiceConnection.on("stateChange", async (_, newState) => { @@ -85,7 +104,7 @@ class BasicStreamDispatcher extends EventEmitter { /** * Creates stream * @param {Readable|Duplex|string} src The stream source - * @param {({type?:StreamType;data?:any;})} [ops] Options + * @param {object} [ops={}] Options * @returns {AudioResource} */ createStream(src: Readable | Duplex | string, ops?: { type?: StreamType; data?: any }) { @@ -100,6 +119,7 @@ class BasicStreamDispatcher extends EventEmitter { /** * The player status + * @type {AudioPlayerStatus} */ get status() { return this.audioPlayer.state.status; @@ -107,6 +127,7 @@ class BasicStreamDispatcher extends EventEmitter { /** * Disconnects from voice + * @returns {void} */ disconnect() { try { @@ -116,16 +137,26 @@ class BasicStreamDispatcher extends EventEmitter { /** * Stops the player + * @returns {void} */ end() { this.audioPlayer.stop(); } + /** + * Pauses the stream playback + * @param {boolean} [interpolateSilence=false] If true, the player will play 5 packets of silence after pausing to prevent audio glitches. + * @returns {boolean} + */ pause(interpolateSilence?: boolean) { const success = this.audioPlayer.pause(interpolateSilence); return success; } + /** + * Resumes the stream playback + * @returns {boolean} + */ resume() { const success = this.audioPlayer.unpause(); return success; @@ -133,7 +164,8 @@ class BasicStreamDispatcher extends EventEmitter { /** * Play stream - * @param {AudioResource} resource The audio resource to play + * @param {AudioResource} [resource=this.audioResource] The audio resource to play + * @returns {Promise} */ async playStream(resource: AudioResource = this.audioResource) { if (!resource) throw new Error("Audio resource is not available!"); @@ -144,6 +176,11 @@ class BasicStreamDispatcher extends EventEmitter { return this; } + /** + * Sets playback volume + * @param {number} value The volume amount + * @returns {boolean} + */ setVolume(value: number) { if (!this.audioResource || isNaN(value) || value < 0 || value > Infinity) return false; @@ -152,20 +189,32 @@ class BasicStreamDispatcher extends EventEmitter { return true; } + /** + * The current volume + * @type {number} + */ get volume() { if (!this.audioResource || !this.audioResource.volume) return 100; const currentVol = this.audioResource.volume.volume; return Math.round(Math.pow(currentVol, 1 / 1.660964) * 100); } + /** + * The playback time + * @type {number} + */ get streamTime() { if (!this.audioResource) return 0; return this.audioResource.playbackDuration; } + /** + * The paused state + * @type {boolean} + */ get paused() { return [AudioPlayerStatus.AutoPaused, AudioPlayerStatus.Paused].includes(this.audioPlayer.state.status); } } -export { BasicStreamDispatcher as StreamDispatcher }; +export { StreamDispatcher as StreamDispatcher }; diff --git a/src/VoiceInterface/VoiceUtils.ts b/src/VoiceInterface/VoiceUtils.ts index 00ced00..d2632af 100644 --- a/src/VoiceInterface/VoiceUtils.ts +++ b/src/VoiceInterface/VoiceUtils.ts @@ -3,13 +3,24 @@ import { entersState, joinVoiceChannel, VoiceConnection, VoiceConnectionStatus } import { StreamDispatcher } from "./BasicStreamDispatcher"; class VoiceUtils { - public cache = new Collection(); + public cache: Collection; /** - * Joins a voice channel + * The voice utils + */ + constructor() { + /** + * The cache where voice utils stores stream managers + * @type {Collection} + */ + this.cache = new Collection(); + } + + /** + * Joins a voice channel, creating basic stream dispatch manager * @param {StageChannel|VoiceChannel} channel The voice channel - * @param {({deaf?: boolean;maxTime?: number;})} [options] Join options - * @returns {Promise} + * @param {object} [options={}] Join options + * @returns {Promise} */ public async connect( channel: VoiceChannel | StageChannel, @@ -24,6 +35,12 @@ class VoiceUtils { return sub; } + /** + * Joins a voice channel + * @param {StageChannel|VoiceChannel} [channel] The voice/stage channel to join + * @param {object} [options={}] Join options + * @returns {VoiceConnection} + */ public async join( channel: VoiceChannel | StageChannel, options?: { @@ -50,12 +67,18 @@ class VoiceUtils { /** * Disconnects voice connection * @param {VoiceConnection} connection The voice connection + * @returns {void} */ public disconnect(connection: VoiceConnection | StreamDispatcher) { if (connection instanceof StreamDispatcher) return connection.voiceConnection.destroy(); return connection.destroy(); } + /** + * Returns Discord Player voice connection + * @param {Snowflake} guild The guild id + * @returns {StreamDispatcher} + */ public getConnection(guild: Snowflake) { return this.cache.get(guild); } diff --git a/src/types/types.ts b/src/types/types.ts index d6a3d14..4f23802 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -140,38 +140,71 @@ export enum QueryType { export interface PlayerEvents { /** * Emitted when bot gets disconnected from a voice channel + * @event Player#botDisconnect + * @param {Queue} queue The queue */ botDisconnect: (queue: Queue) => any; + /** * Emitted when the voice channel is empty + * @event Player#channelEmpty + * @param {Queue} queue The queue */ channelEmpty: (queue: Queue) => any; + /** * Emitted when bot connects to a voice channel + * @event Player#connectionCreate + * @param {Queue} queue The queue + * @param {StreamDispatcher} connection The discord player connection object */ connectionCreate: (queue: Queue, connection: StreamDispatcher) => any; + /** * Debug information + * @event Player#debug + * @param {Queue} queue The queue + * @param {string} message The message */ debug: (queue: Queue, message: string) => any; + /** * Emitted on error + * This event should handled properly otherwise it may crash your process! + * @event Player#error + * @param {Queue} queue The queue + * @param {Error} error The error */ error: (queue: Queue, error: Error) => any; + /** * Emitted when queue ends + * @event Player#queueEnd + * @param {Queue} queue The queue */ queueEnd: (queue: Queue) => any; + /** * Emitted when a single track is added + * @event Player#trackAdd + * @param {Queue} queue The queue + * @param {Track} track The track */ trackAdd: (queue: Queue, track: Track) => any; + /** * Emitted when multiple tracks are added + * @event Player#tracksAdd + * @param {Queue} queue The queue + * @param {Track[]} tracks The tracks */ tracksAdd: (queue: Queue, track: Track[]) => any; + /** * Emitted when a track starts playing + * @event Player#trackStart + * @param {Queue} queue The queue + * @param {Track} track The track */ trackStart: (queue: Queue, track: Track) => any; } diff --git a/src/utils/QueryResolver.ts b/src/utils/QueryResolver.ts index 5713202..19554a8 100644 --- a/src/utils/QueryResolver.ts +++ b/src/utils/QueryResolver.ts @@ -16,6 +16,18 @@ const attachmentRegex = // scary things above *sigh* class QueryResolver { + /** + * Query resolver + */ + constructor() { + throw new Error("Cannot instantiate static class!"); + } + + /** + * Resolves the given search query + * @param {string} query The query + * @returns {QueryType} + */ static resolve(query: string): QueryType { if (SoundcloudValidateURL(query, "track")) return QueryType.SOUNDCLOUD_TRACK; if (SoundcloudValidateURL(query, "playlist") || query.includes("/sets/")) return QueryType.SOUNDCLOUD_PLAYLIST; @@ -32,6 +44,11 @@ class QueryResolver { return QueryType.YOUTUBE_SEARCH; } + /** + * Parses vimeo id from url + * @param {string} query The query + * @returns {string} + */ static getVimeoID(query: string): string { return QueryResolver.resolve(query) === QueryType.VIMEO ? query diff --git a/src/utils/Util.ts b/src/utils/Util.ts index 7d7488a..187813b 100644 --- a/src/utils/Util.ts +++ b/src/utils/Util.ts @@ -2,12 +2,29 @@ import { StageChannel, VoiceChannel } from "discord.js"; import { TimeData } from "../types/types"; class Util { + /** + * Utils + */ + constructor() { + throw new Error("Cannot instantiate static class"); + } + + /** + * Creates duration string + * @param {object} durObj The duration object + * @returns {string} + */ static durationString(durObj: object) { return Object.values(durObj) .map((m) => (isNaN(m) ? 0 : m)) .join(":"); } + /** + * Parses milliseconds to consumable time object + * @param {number} milliseconds The time in ms + * @returns {TimeData} + */ static parseMS(milliseconds: number) { const round = milliseconds > 0 ? Math.floor : Math.ceil; @@ -19,6 +36,11 @@ class Util { } as TimeData; } + /** + * Builds time code + * @param {TimeData} duration The duration object + * @returns {string} + */ static buildTimeCode(duration: TimeData) { const items = Object.keys(duration); const required = ["days", "hours", "minutes", "seconds"]; @@ -31,15 +53,30 @@ class Util { return final.length <= 3 ? `0:${final.padStart(2, "0") || 0}` : final; } + /** + * Picks last item of the given array + * @param {any[]} arr The array + * @returns {any} + */ static last(arr: T[]): T { if (!Array.isArray(arr)) return; return arr[arr.length - 1]; } + /** + * Checks if the voice channel is empty + * @param {VoiceChannel|StageChannel} channel The voice channel + * @returns {boolean} + */ static isVoiceEmpty(channel: VoiceChannel | StageChannel) { return channel.members.filter((member) => !member.user.bot).size === 0; } + /** + * Safer require + * @param {string} id Node require id + * @returns {any} + */ static require(id: string) { try { return require(id); @@ -48,6 +85,11 @@ class Util { } } + /** + * Asynchronous timeout + * @param {number} time The time in ms to wait + * @returns {Promise} + */ static wait(time: number) { return new Promise((r) => setTimeout(r, time).unref()); } diff --git a/tsconfig.json b/tsconfig.json index 5fc7c84..629bcf8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,8 +6,7 @@ "outDir": "./lib", "strict": true, "strictNullChecks": false, - "esModuleInterop": true, - "removeComments": true + "esModuleInterop": true }, "include": [ "src/**/*"