diff --git a/src/Player.ts b/src/Player.ts index 235f9df..b3d44ee 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -80,7 +80,7 @@ export class Player extends EventEmitter { } } - static get AudioFilters() { + static get AudioFilters(): typeof AudioFilters { return AudioFilters; } @@ -89,7 +89,7 @@ export class Player extends EventEmitter { * @param extractorName The extractor name * @param extractor The extractor itself */ - use(extractorName: string, extractor: any) { + use(extractorName: string, extractor: any): Player { if (!extractorName) throw new PlayerError('Missing extractor name!', 'PlayerExtractorError'); const methods = ['validate', 'getInfo']; @@ -108,7 +108,7 @@ export class Player extends EventEmitter { * Remove existing extractor from this player * @param extractorName The extractor name */ - unuse(extractorName: string) { + unuse(extractorName: string): boolean { if (!extractorName) throw new PlayerError('Missing extractor name!', 'PlayerExtractorError'); return this.Extractors.delete(extractorName); @@ -446,7 +446,7 @@ export class Player extends EventEmitter { * Checks if this player is playing in a server * @param message The message object */ - isPlaying(message: Message) { + isPlaying(message: Message): boolean { return this.queues.some((g) => g.guildID === message.guild.id); } @@ -454,7 +454,7 @@ export class Player extends EventEmitter { * Returns guild queue object * @param message The message object */ - getQueue(message: Message) { + getQueue(message: Message): Queue { return this.queues.find((g) => g.guildID === message.guild.id); } @@ -523,7 +523,7 @@ export class Player extends EventEmitter { * @param time Time in ms to set * @alias setPosition */ - seek(message: Message, time: number) { + seek(message: Message, time: number): Promise { return this.setPosition(message, time); } @@ -553,7 +553,7 @@ export class Player extends EventEmitter { * @param message The message object * @param channel New voice channel to move to */ - moveTo(message: Message, channel?: VoiceChannel) { + moveTo(message: Message, channel?: VoiceChannel): boolean { if (!channel || channel.type !== 'voice') return; const queue = this.queues.find((g) => g.guildID === message.guild.id); if (!queue) { @@ -579,7 +579,7 @@ export class Player extends EventEmitter { * Pause the playback * @param message The message object */ - pause(message: Message) { + pause(message: Message): boolean { const queue = this.getQueue(message); if (!queue) { this.emit(PlayerEvents.ERROR, PlayerErrorEventCodes.NOT_PLAYING, message); @@ -599,7 +599,7 @@ export class Player extends EventEmitter { * Resume the playback * @param message The message object */ - resume(message: Message) { + resume(message: Message): boolean { const queue = this.getQueue(message); if (!queue) { this.emit(PlayerEvents.ERROR, PlayerErrorEventCodes.NOT_PLAYING, message); @@ -619,7 +619,7 @@ export class Player extends EventEmitter { * Stops the player * @param message The message object */ - stop(message: Message) { + stop(message: Message): boolean { const queue = this.getQueue(message); if (!queue) { this.emit(PlayerEvents.ERROR, PlayerErrorEventCodes.NOT_PLAYING, message); @@ -644,7 +644,7 @@ export class Player extends EventEmitter { * @param message The message object * @param percent The volume percentage/amount to set */ - setVolume(message: Message, percent: number) { + setVolume(message: Message, percent: number): boolean { const queue = this.getQueue(message); if (!queue) { this.emit(PlayerEvents.ERROR, PlayerErrorEventCodes.NOT_PLAYING, message); @@ -665,7 +665,7 @@ export class Player extends EventEmitter { * Clears the queue * @param message The message object */ - clearQueue(message: Message) { + clearQueue(message: Message): boolean { const queue = this.getQueue(message); if (!queue) { this.emit(PlayerEvents.ERROR, PlayerErrorEventCodes.NOT_PLAYING, message); @@ -681,7 +681,7 @@ export class Player extends EventEmitter { * Plays previous track * @param message The message object */ - back(message: Message) { + back(message: Message): boolean { const queue = this.getQueue(message); if (!queue) { this.emit(PlayerEvents.ERROR, PlayerErrorEventCodes.NOT_PLAYING, message); @@ -704,7 +704,7 @@ export class Player extends EventEmitter { * @param message The message object * @param enabled If it should enable the repeat mode */ - setRepeatMode(message: Message, enabled: boolean) { + setRepeatMode(message: Message, enabled: boolean): boolean { const queue = this.getQueue(message); if (!queue) { this.emit(PlayerEvents.ERROR, PlayerErrorEventCodes.NOT_PLAYING, message); @@ -721,7 +721,7 @@ export class Player extends EventEmitter { * @param message The message object * @param enabled If it should enable the loop mode */ - setLoopMode(message: Message, enabled: boolean) { + setLoopMode(message: Message, enabled: boolean): boolean { const queue = this.getQueue(message); if (!queue) { this.emit(PlayerEvents.ERROR, PlayerErrorEventCodes.NOT_PLAYING, message); @@ -737,7 +737,7 @@ export class Player extends EventEmitter { * Returns currently playing track * @param message The message object */ - nowPlaying(message: Message) { + nowPlaying(message: Message): Track { const queue = this.getQueue(message); if (!queue) { this.emit(PlayerEvents.ERROR, PlayerErrorEventCodes.NOT_PLAYING, message); @@ -751,7 +751,7 @@ export class Player extends EventEmitter { * Shuffles the queue * @param message The message object */ - shuffle(message: Message) { + shuffle(message: Message): Queue { const queue = this.getQueue(message); if (!queue) { this.emit(PlayerEvents.ERROR, PlayerErrorEventCodes.NOT_PLAYING, message); @@ -775,7 +775,7 @@ export class Player extends EventEmitter { * @param message The message object * @param track The track object/id to remove */ - remove(message: Message, track: Track | number) { + remove(message: Message, track: Track | number): Track { const queue = this.getQueue(message); if (!queue) { this.emit(PlayerEvents.ERROR, PlayerErrorEventCodes.NOT_PLAYING, message); @@ -803,7 +803,7 @@ export class Player extends EventEmitter { * @param message The message object * @param queueTime If it should make the time code of the whole queue */ - getTimeCode(message: Message, queueTime?: boolean) { + getTimeCode(message: Message, queueTime?: boolean): { current: string; end: string } { const queue = this.getQueue(message); if (!queue) return; @@ -827,7 +827,7 @@ export class Player extends EventEmitter { * @param message The message object * @param options Progressbar options */ - createProgressBar(message: Message, options?: PlayerProgressbarOptions) { + createProgressBar(message: Message, options?: PlayerProgressbarOptions): string { const queue = this.getQueue(message); if (!queue) return; @@ -876,14 +876,14 @@ export class Player extends EventEmitter { * @example const lyrics = await player.lyrics("alan walker faded") * message.channel.send(lyrics.lyrics); */ - async lyrics(query: string) { + async lyrics(query: string): Promise { const extractor = Util.require('@discord-player/extractor'); if (!extractor) throw new PlayerError("Cannot call 'Player.lyrics()' without '@discord-player/extractor'"); const data = await extractor.Lyrics(query); if (Array.isArray(data)) return null; - return data as LyricsData; + return data; } /** @@ -931,7 +931,7 @@ export class Player extends EventEmitter { }; } - private _handleVoiceStateUpdate(oldState: VoiceState, newState: VoiceState) { + private _handleVoiceStateUpdate(oldState: VoiceState, newState: VoiceState): void { const queue = this.queues.find((g) => g.guildID === oldState.guild.id); if (!queue) return; @@ -964,7 +964,7 @@ export class Player extends EventEmitter { } } - private _addTrackToQueue(message: Message, track: Track) { + private _addTrackToQueue(message: Message, track: Track): Queue { const queue = this.getQueue(message); if (!queue) this.emit( @@ -978,7 +978,7 @@ export class Player extends EventEmitter { return queue; } - private _addTracksToQueue(message: Message, tracks: Track[]) { + private _addTracksToQueue(message: Message, tracks: Track[]): Queue { const queue = this.getQueue(message); if (!queue) throw new PlayerError( @@ -1174,3 +1174,109 @@ export class Player extends EventEmitter { } export default Player; + +/** + * Emitted when a track starts + * @event Player#trackStart + * @param {Discord.Message} message + * @param {Track} track + * @param {Queue} queue + */ + +/** + * Emitted when a playlist is started + * @event Player#queueCreate + * @param {Discord.Message} message + * @param {Queue} queue + */ + +/** + * Emitted when the bot is awaiting search results + * @event Player#searchResults + * @param {Discord.Message} message + * @param {string} query + * @param {Track[]} tracks + * @param {Discord.Collector} collector + */ + +/** + * Emitted when the user has sent an invalid response for search results + * @event Player#searchInvalidResponse + * @param {Discord.Message} message + * @param {string} query + * @param {Track[]} tracks + * @param {string} invalidResponse + * @param {Discord.MessageCollector} collector + */ + +/** + * Emitted when the bot has stopped awaiting search results (timeout) + * @event Player#searchCancel + * @param {Discord.Message} message + * @param {string} query + * @param {Track[]} tracks + */ + +/** + * Emitted when the bot can't find related results to the query + * @event Player#noResults + * @param {Discord.Message} message + * @param {string} query + */ + +/** + * Emitted when the bot is disconnected from the channel + * @event Player#botDisconnect + * @param {Discord.Message} message + */ + +/** + * Emitted when the channel of the bot is empty + * @event Player#channelEmpty + * @param {Discord.Message} message + * @param {Queue} queue + */ + +/** + * Emitted when the queue of the server is ended + * @event Player#queueEnd + * @param {Discord.Message} message + * @param {Queue} queue + */ + +/** + * Emitted when a track is added to the queue + * @event Player#trackAdd + * @param {Discord.Message} message + * @param {Queue} queue + * @param {Track} track + */ + +/** + * Emitted when a playlist is added to the queue + * @event Player#playlistAdd + * @param {Discord.Message} message + * @param {Queue} queue + * @param {Object} playlist + */ + +/** + * Emitted when an error is triggered + * @event Player#error + * @param {string} error It can be `NotConnected`, `UnableToJoin`, `NotPlaying`, `ParseError`, `LiveVideo` or `VideoUnavailable`. + * @param {Discord.Message} message + */ + +/** + * Emitted when discord-player attempts to parse playlist contents (mostly soundcloud playlists) + * @event Player#playlistParseStart + * @param {Object} playlist Raw playlist (unparsed) + * @param {Discord.Message} message The message + */ + +/** + * Emitted when discord-player finishes parsing playlist contents (mostly soundcloud playlists) + * @event Player#playlistParseEnd + * @param {Object} playlist The playlist data (parsed) + * @param {Discord.Message} message The message + */ \ No newline at end of file diff --git a/src/Structures/ExtractorModel.ts b/src/Structures/ExtractorModel.ts index 9242a5f..7838b96 100644 --- a/src/Structures/ExtractorModel.ts +++ b/src/Structures/ExtractorModel.ts @@ -4,13 +4,26 @@ class ExtractorModel { name: string; private _raw: any; + /** + * Model for raw Discord Player extractors + * @param extractorName Name of the extractor + * @param data Extractor object + */ constructor(extractorName: string, data: any) { + + /** + * The extractor name + */ this.name = extractorName; Object.defineProperty(this, '_raw', { value: data, configurable: false, writable: false, enumerable: false }); } - async handle(query: string) { + /** + * Method to handle requests from `Player.play()` + * @param query Query to handle + */ + async handle(query: string): Promise { const data = await this._raw.getInfo(query); if (!data) return null; @@ -23,18 +36,28 @@ class ExtractorModel { author: data.author, description: data.description, url: data.url - } as ExtractorModelData; + }; } - validate(query: string) { + /** + * Method used by Discord Player to validate query with this extractor + * @param query The query to validate + */ + validate(query: string): boolean { return Boolean(this._raw.validate(query)); } - get version() { + /** + * The extractor version + */ + get version(): string { return this._raw.version ?? '0.0.0'; } - get important() { + /** + * If player should mark this extractor as important + */ + get important(): boolean { return Boolean(this._raw.important); } } diff --git a/src/Structures/Queue.ts b/src/Structures/Queue.ts index b25cb12..64c2555 100644 --- a/src/Structures/Queue.ts +++ b/src/Structures/Queue.ts @@ -26,6 +26,11 @@ export class Queue extends EventEmitter { public firstMessage: Message; public autoPlay = false; + /** + * Queue constructor + * @param player The player that instantiated this Queue + * @param message The message object + */ constructor(player: Player, message: Message) { super(); @@ -91,40 +96,41 @@ export class Queue extends EventEmitter { */ this.firstMessage = message; - // @ts-ignore + /** + * The audio filters in this queue + */ this.filters = {}; Object.keys(AudioFilters).forEach((fn) => { - // @ts-ignore - this.filters[fn] = false; + this.filters[fn as keyof QueueFilters] = false; }); } /** * Currently playing track */ - get playing() { + get playing(): Track { return this.tracks[0]; } /** * Calculated volume of this queue */ - get calculatedVolume() { + get calculatedVolume(): number { return this.filters.normalizer ? this.volume + 70 : this.volume; } /** * Total duration */ - get totalTime() { + get totalTime(): number { return this.tracks.length > 0 ? this.tracks.map((t) => t.durationMS).reduce((p, c) => p + c) : 0; } /** * Current stream time */ - get currentStreamTime() { + get currentStreamTime(): number { return this.voiceConnection?.dispatcher?.streamTime + this.additionalStreamTime || 0; } @@ -132,14 +138,14 @@ export class Queue extends EventEmitter { * Sets audio filters in this player * @param filters Audio filters to set */ - setFilters(filters: QueueFilters) { + setFilters(filters: QueueFilters): Promise { return this.player.setFilters(this.firstMessage, filters); } /** * Returns array of all enabled filters */ - getFiltersEnabled() { + getFiltersEnabled(): string[] { const filters: string[] = []; for (const filter in this.filters) { @@ -152,13 +158,16 @@ export class Queue extends EventEmitter { /** * Returns all disabled filters */ - getFiltersDisabled() { + getFiltersDisabled(): string[] { const enabled = this.getFiltersEnabled(); return Object.keys(this.filters).filter((f) => !enabled.includes(f)); } - toString() { + /** + * String representation of this Queue + */ + toString(): string { return ``; } } diff --git a/src/Structures/Track.ts b/src/Structures/Track.ts index 70bea6b..31c4130 100644 --- a/src/Structures/Track.ts +++ b/src/Structures/Track.ts @@ -1,23 +1,69 @@ import { Player } from '../Player'; import { User } from 'discord.js'; import { TrackData } from '../types/types'; +import Queue from './Queue'; export class Track { /** * The player that instantiated this Track */ public player!: Player; + + /** + * Title of this track + */ public title!: string; + + /** + * Description of this track + */ public description!: string; + + /** + * Author of this track + */ public author!: string; + + /** + * Link of this track + */ public url!: string; + + /** + * Thumbnail of this track + */ public thumbnail!: string; + + /** + * Duration of this track + */ public duration!: string; + + /** + * View count of this track + */ public views!: number; + + /** + * Person who requested this track + */ public requestedBy!: User; + + /** + * If this track belongs to a playlist + */ public fromPlaylist!: boolean; + + /** + * Raw data of this track + */ public raw!: TrackData; + /** + * Track constructor + * @param player The player that instantiated this Track + * @param data Track data + */ constructor(player: Player, data: TrackData) { Object.defineProperty(this, 'player', { value: player, enumerable: false }); @@ -42,14 +88,14 @@ export class Track { /** * The queue in which this track is located */ - get queue() { + get queue(): Queue { return this.player.queues.find((q) => q.tracks.includes(this)); } /** * The track duration in millisecond */ - get durationMS() { + get durationMS(): number { const times = (n: number, t: number) => { let tn = 1; for (let i = 0; i < t; i++) tn *= n; @@ -63,7 +109,10 @@ export class Track { .reduce((a, c) => a + c, 0); } - toString() { + /** + * String representation of this track + */ + toString(): string { return `${this.title} by ${this.author}`; } } diff --git a/src/types/types.ts b/src/types/types.ts index 11d950b..f687a9a 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -145,3 +145,10 @@ export interface PlayerStats { uptime: number; }; } + +export interface TimeData { + days: number; + hours: number; + minutes: number; + seconds: number; +} \ No newline at end of file diff --git a/src/utils/Util.ts b/src/utils/Util.ts index a8af3b4..95af9aa 100644 --- a/src/utils/Util.ts +++ b/src/utils/Util.ts @@ -1,4 +1,4 @@ -import { QueryType } from '../types/types'; +import { QueryType, TimeData } from '../types/types'; import { FFmpeg } from 'prism-media'; import YouTube from 'youtube-sr'; import { Track } from '../Structures/Track'; @@ -19,7 +19,7 @@ export class Util { throw new Error(`The ${this.constructor.name} class is static and cannot be instantiated!`); } - static getFFmpegVersion(force?: boolean) { + static getFFmpegVersion(force?: boolean): string { try { const info = FFmpeg.getInfo(Boolean(force)); @@ -29,12 +29,12 @@ export class Util { } } - static checkFFmpeg(force?: boolean) { + static checkFFmpeg(force?: boolean): boolean { const version = Util.getFFmpegVersion(force); return version === null ? false : true; } - static alertFFmpeg() { + static alertFFmpeg(): void { const hasFFmpeg = Util.checkFFmpeg(); if (!hasFFmpeg) @@ -59,11 +59,11 @@ export class Util { return 'youtube_search'; } - static isURL(str: string) { + static isURL(str: string): boolean { return str.length < 2083 && attachmentRegex.test(str); } - static getVimeoID(query: string) { + static getVimeoID(query: string): string { return Util.getQueryType(query) === 'vimeo' ? query .split('/') @@ -72,7 +72,7 @@ export class Util { : null; } - static parseMS(milliseconds: number) { + static parseMS(milliseconds: number): TimeData { const roundTowardsZero = milliseconds > 0 ? Math.floor : Math.ceil; return { @@ -83,7 +83,7 @@ export class Util { }; } - static durationString(durObj: object) { + static durationString(durObj: object): string { return Object.values(durObj) .map((m) => (isNaN(m) ? 0 : m)) .join(':'); @@ -119,7 +119,7 @@ export class Util { }); } - static isRepl() { + static isRepl(): boolean { if ('DP_REPL_NOCHECK' in process.env) return false; const REPL_IT_PROPS = [ @@ -137,11 +137,11 @@ export class Util { return false; } - static isVoiceEmpty(channel: VoiceChannel) { + static isVoiceEmpty(channel: VoiceChannel): boolean { return channel.members.filter((member) => !member.user.bot).size === 0; } - static buildTimeCode(data: any) { + static buildTimeCode(data: any): string { const items = Object.keys(data); const required = ['days', 'hours', 'minutes', 'seconds'];