UNPKG

@hunghg255/distube

Version:

A Discord.js module to simplify your music commands and play songs with audio filters on Discord without any API key.

1,697 lines (1,673 loc) 61.5 kB
import * as ytdl from '@distube/ytdl-core'; import ytdl__default, { Cookie } from '@distube/ytdl-core'; import * as discord_js from 'discord.js'; import { GuildMember, Snowflake, Message, GuildTextBasedChannel, VoiceBasedChannel, VoiceState, Guild, Interaction, Client, Collection, ClientOptions } from 'discord.js'; import ytpl from '@distube/ytpl'; import { Video, Playlist as Playlist$1 } from '@distube/ytsr'; import { TypedEmitter } from 'tiny-typed-emitter'; import { AudioPlayer, VoiceConnection, AudioResource, StreamType as StreamType$1 } from '@discordjs/voice'; import { PassThrough } from 'node:stream'; import { ChildProcess } from 'child_process'; type Awaitable<T = any> = T | PromiseLike<T>; type DisTubeEvents = { addList: [queue: Queue, playlist: Playlist]; addSong: [queue: Queue, song: Song]; deleteQueue: [queue: Queue]; disconnect: [queue: Queue]; empty: [queue: Queue]; error: [channel: GuildTextBasedChannel | undefined, error: Error]; ffmpegDebug: [debug: string]; finish: [queue: Queue]; finishSong: [queue: Queue, song: Song]; initQueue: [queue: Queue]; noRelated: [queue: Queue]; playSong: [queue: Queue, song: Song]; searchCancel: [message: Message<true>, query: string]; searchDone: [message: Message<true>, answer: Message<true>, query: string]; searchInvalidAnswer: [message: Message<true>, answer: Message<true>, query: string]; searchNoResult: [message: Message<true>, query: string]; searchResult: [message: Message<true>, results: SearchResult[], query: string]; }; type TypedDisTubeEvents = { [K in keyof DisTubeEvents]: (...args: DisTubeEvents[K]) => Awaitable; }; type DisTubeVoiceEvents = { disconnect: (error?: Error) => Awaitable; error: (error: Error) => Awaitable; finish: () => Awaitable; }; /** * An FFmpeg audio filter object * * ```ts * { * name: "bassboost", * value: "bass=g=10" * } * ```ts */ interface Filter { /** * Name of the filter */ name: string; /** * FFmpeg audio filter argument */ value: string; } /** * Data that resolves to give an FFmpeg audio filter. This can be: * * - A name of a default filters or custom filters (`string`) * - A {@link Filter} object * * @see {@link defaultFilters} * @see {@link DisTubeOptions|DisTubeOptions.customFilters} */ type FilterResolvable = string | Filter; /** * FFmpeg Filters * * ```ts * { * "Filter Name": "Filter Value", * "bassboost": "bass=g=10" * } * ```ts * * @see {@link defaultFilters} */ type Filters = Record<string, string>; /** * DisTube options */ type DisTubeOptions = { /** * DisTube plugins */ plugins?: (CustomPlugin | ExtractorPlugin)[]; /** * Whether or not emitting {@link DisTube#playSong} event when looping a song * or next song is the same as the previous one */ emitNewSongOnly?: boolean; /** * Whether or not leaving voice channel if the voice channel is empty after {@link * DisTubeOptions}.emptyCooldown seconds */ leaveOnEmpty?: boolean; /** * Whether or not leaving voice channel when the queue ends */ leaveOnFinish?: boolean; /** * Whether or not leaving voice channel after using {@link DisTube#stop} function */ leaveOnStop?: boolean; /** * Built-in leave on empty cooldown in seconds (When leaveOnEmpty is true) */ emptyCooldown?: number; /** * Whether or not saving the previous songs of the queue and enable {@link * DisTube#previous} method */ savePreviousSongs?: boolean; /** * Limit of search results emits in {@link DisTube#searchResult} event when * {@link DisTube#play} method executed. If `searchSongs <= 1`, play the first * result */ searchSongs?: number; /** * Built-in search cooldown in seconds (When searchSongs is bigger than 0) */ searchCooldown?: number; /** * YouTube cookies. Guide: {@link * https://github.com/skick1234/DisTube/wiki/YouTube-Cookies | YouTube Cookies} */ youtubeCookie?: Cookie[] | string; /** * Override {@link defaultFilters} or add more ffmpeg filters */ customFilters?: Filters; /** * `ytdl-core` get info options */ ytdlOptions?: ytdl__default.downloadOptions; /** * Whether or not playing age-restricted content and disabling safe search in * non-NSFW channel */ nsfw?: boolean; /** * Whether or not emitting `addSong` event when creating a new Queue */ emitAddSongWhenCreatingQueue?: boolean; /** * Whether or not emitting `addList` event when creating a new Queue */ emitAddListWhenCreatingQueue?: boolean; /** * Whether or not joining the new voice channel when using {@link DisTube#play} * method */ joinNewVoiceChannel?: boolean; /** * Decide the {@link DisTubeStream#type} will be used (Not the same as {@link * DisTubeStream#type}) */ streamType?: StreamType; /** * Whether or not playing a song with direct link */ directLink?: boolean; /** * FFmpeg path * @deprecated */ ffmpegPath?: string; /** * FFmpeg default arguments * @deprecated */ ffmpegDefaultArgs?: FFmpegArgs; /** * FFmpeg options */ ffmpeg?: { /** * FFmpeg path */ path?: string; /** * FFmpeg default arguments */ args?: { global?: FFmpegArgs; input?: FFmpegArgs; output?: FFmpegArgs; }; }; }; /** * Data that can be resolved to give a guild id string. This can be: * * - A guild id string | a guild {@link https://discord.js.org/#/docs/main/stable/class/Snowflake|Snowflake} * - A {@link https://discord.js.org/#/docs/main/stable/class/Guild | Guild} * - A {@link https://discord.js.org/#/docs/main/stable/class/Message | Message} * - A {@link https://discord.js.org/#/docs/main/stable/class/BaseGuildVoiceChannel * | BaseGuildVoiceChannel} * - A {@link https://discord.js.org/#/docs/main/stable/class/BaseGuildTextChannel * | BaseGuildTextChannel} * - A {@link https://discord.js.org/#/docs/main/stable/class/VoiceState | * VoiceState} * - A {@link https://discord.js.org/#/docs/main/stable/class/GuildMember | * GuildMember} * - A {@link https://discord.js.org/#/docs/main/stable/class/Interaction | * Interaction} * - A {@link DisTubeVoice} * - A {@link Queue} */ type GuildIdResolvable = Queue | DisTubeVoice | Snowflake | Message | GuildTextBasedChannel | VoiceBasedChannel | VoiceState | Guild | GuildMember | Interaction | string; interface OtherSongInfo { src: string; id?: string; title?: string; name?: string; is_live?: boolean; isLive?: boolean; _duration_raw?: string | number; duration?: string | number; webpage_url?: string; url: string; thumbnail?: string; related?: RelatedSong[]; view_count?: string | number; views?: string | number; like_count?: string | number; likes?: string | number; dislike_count?: string | number; dislikes?: string | number; repost_count?: string | number; reposts?: string | number; uploader?: string | { name: string; url: string; }; uploader_url?: string; age_limit?: string | number; chapters?: Chapter[]; age_restricted?: boolean; } interface Chapter { title: string; start_time: number; } interface PlaylistInfo { source: string; member?: GuildMember; songs: Song[]; name?: string; url?: string; thumbnail?: string; /** * @deprecated Use {@link PlaylistInfo#name} */ title?: string; /** * @deprecated Use {@link PlaylistInfo#url} */ webpage_url?: string; } type RelatedSong = Omit<Song, "related">; type PlayHandlerOptions = { /** * [Default: false] Skip the playing song (if exists) and play the added playlist * instantly */ skip?: boolean; /** * [Default: 0] Position of the song/playlist to add to the queue, \<= 0 to add to * the end of the queue */ position?: number; /** * The default text channel of the queue */ textChannel?: GuildTextBasedChannel; }; interface PlayOptions extends PlayHandlerOptions, ResolveOptions<any> { /** * Called message (For built-in search events. If this is a {@link * https://developer.mozilla.org/en-US/docs/Glossary/Falsy | falsy value}, it will * play the first result instead) */ message?: Message; } interface ResolveOptions<T = unknown> { /** * Requested user */ member?: GuildMember; /** * Metadata */ metadata?: T; } interface ResolvePlaylistOptions<T = unknown> extends ResolveOptions<T> { /** * Source of the playlist */ source?: string; } interface CustomPlaylistOptions { /** * A guild member creating the playlist */ member?: GuildMember; /** * Additional properties such as `name` */ properties?: Record<string, any>; /** * Whether or not fetch the songs in parallel */ parallel?: boolean; /** * Metadata */ metadata?: any; } /** * The repeat mode of a {@link Queue} * * - `DISABLED` = 0 * - `SONG` = 1 * - `QUEUE` = 2 */ declare enum RepeatMode { DISABLED = 0, SONG = 1, QUEUE = 2 } /** * All available plugin types: * * - `CUSTOM` = `"custom"`: {@link CustomPlugin} * - `EXTRACTOR` = `"extractor"`: {@link ExtractorPlugin} */ declare enum PluginType { CUSTOM = "custom", EXTRACTOR = "extractor" } /** * Search result types: * * - `VIDEO` = `"video"` * - `PLAYLIST` = `"playlist"` */ declare enum SearchResultType { VIDEO = "video", PLAYLIST = "playlist" } /** * Stream types: * * - `OPUS` = `0` (Better quality, use more resources - **Recommended**) * - `RAW` = `1` (Better performance, use less resources) */ declare enum StreamType { OPUS = 0, RAW = 1 } declare enum Events { ERROR = "error", ADD_LIST = "addList", ADD_SONG = "addSong", PLAY_SONG = "playSong", FINISH_SONG = "finishSong", EMPTY = "empty", FINISH = "finish", INIT_QUEUE = "initQueue", NO_RELATED = "noRelated", DISCONNECT = "disconnect", DELETE_QUEUE = "deleteQueue", SEARCH_CANCEL = "searchCancel", SEARCH_NO_RESULT = "searchNoResult", SEARCH_DONE = "searchDone", SEARCH_INVALID_ANSWER = "searchInvalidAnswer", SEARCH_RESULT = "searchResult", FFMPEG_DEBUG = "ffmpegDebug" } type FFmpegArgs = Record<string, string | number | boolean | Array<string | null | undefined> | null | undefined>; type FFmpegOptions = { path: string; args: { global: FFmpegArgs; input: FFmpegArgs; output: FFmpegArgs; }; }; /** * Default DisTube audio filters. */ declare const defaultFilters: Filters; declare const defaultOptions: { plugins: never[]; emitNewSongOnly: false; leaveOnEmpty: true; leaveOnFinish: false; leaveOnStop: true; savePreviousSongs: true; searchSongs: number; ytdlOptions: {}; searchCooldown: number; emptyCooldown: number; nsfw: false; emitAddSongWhenCreatingQueue: true; emitAddListWhenCreatingQueue: true; joinNewVoiceChannel: true; streamType: StreamType.OPUS; directLink: true; }; declare const ERROR_MESSAGES: { INVALID_TYPE: (expected: (number | string) | readonly (number | string)[], got: any, name?: string) => string; NUMBER_COMPARE: (name: string, expected: string, value: number) => string; EMPTY_ARRAY: (name: string) => string; EMPTY_FILTERED_ARRAY: (name: string, type: string) => string; EMPTY_STRING: (name: string) => string; INVALID_KEY: (obj: string, key: string) => string; MISSING_KEY: (obj: string, key: string) => string; MISSING_KEYS: (obj: string, key: string[], all: boolean) => string; MISSING_INTENTS: (i: string) => string; DISABLED_OPTION: (o: string) => string; ENABLED_OPTION: (o: string) => string; NOT_IN_VOICE: string; VOICE_FULL: string; VOICE_CONNECT_FAILED: (s: number) => string; VOICE_MISSING_PERMS: string; VOICE_RECONNECT_FAILED: string; VOICE_DIFFERENT_GUILD: string; VOICE_DIFFERENT_CLIENT: string; FFMPEG_EXITED: (code: number) => string; FFMPEG_NOT_INSTALLED: (path: string) => string; NO_QUEUE: string; QUEUE_EXIST: string; PAUSED: string; RESUMED: string; NO_PREVIOUS: string; NO_UP_NEXT: string; NO_SONG_POSITION: string; NO_PLAYING: string; NO_RESULT: string; NO_RELATED: string; CANNOT_PLAY_RELATED: string; UNAVAILABLE_VIDEO: string; UNPLAYABLE_FORMATS: string; NON_NSFW: string; NOT_SUPPORTED_URL: string; CANNOT_RESOLVE_SONG: (t: any) => string; NO_VALID_SONG: string; EMPTY_FILTERED_PLAYLIST: string; EMPTY_PLAYLIST: string; }; type ErrorMessage = typeof ERROR_MESSAGES; type ErrorCode = keyof ErrorMessage; type StaticErrorCode = { [K in ErrorCode]-?: ErrorMessage[K] extends string ? K : never; }[ErrorCode]; type TemplateErrorCode = Exclude<keyof typeof ERROR_MESSAGES, StaticErrorCode>; declare class DisTubeError<T extends string> extends Error { errorCode: string; constructor(code: StaticErrorCode); constructor(code: T extends TemplateErrorCode ? T : never, ...args: Parameters<ErrorMessage[typeof code]>); constructor(code: TemplateErrorCode, _: never); constructor(code: T extends ErrorCode ? "This is built-in error code" : T, message: string); get name(): string; get code(): string; } /** * Task queuing system */ declare class TaskQueue { #private; /** * Waits for last task finished and queues a new task * * @param resolveInfo - Whether the task is a resolving info task */ queuing(resolveInfo?: boolean): Promise<void>; /** * Removes the finished task and processes the next task */ resolve(): void; /** * The remaining number of tasks */ get remaining(): number; /** * Whether or not having a resolving info task */ get hasResolveTask(): boolean; } /** * Class representing a playlist. */ declare class Playlist<T = unknown> implements PlaylistInfo { #private; source: string; songs: Song[]; name: string; url?: string; thumbnail?: string; [x: string]: any; /** * Create a playlist * * @param playlist - Playlist * @param options - Optional options */ constructor(playlist: Song[] | PlaylistInfo, options?: { member?: GuildMember; properties?: Record<string, any>; metadata?: T; }); /** * Playlist duration in second. */ get duration(): number; /** * Formatted duration string `hh:mm:ss`. */ get formattedDuration(): string; /** * User requested. */ get member(): GuildMember | undefined; set member(member: GuildMember | undefined); /** * User requested. */ get user(): discord_js.User | undefined; get metadata(): T; set metadata(metadata: T); } /** * A abstract class representing a search result. * * @virtual */ declare abstract class ISearchResult { source: "youtube"; abstract type: SearchResultType; id: string; name: string; url: string; uploader: { name?: string; url?: string; }; /** * Create a search result * * @param info - ytsr result */ constructor(info: Video | Playlist$1); } /** * A class representing a video search result. */ declare class SearchResultVideo extends ISearchResult { type: SearchResultType.VIDEO; views: number; isLive: boolean; duration: number; formattedDuration: string; thumbnail: string; constructor(info: Video); } /** * A video or playlist search result */ type SearchResult = SearchResultVideo | SearchResultPlaylist; /** * A class representing a playlist search result. */ declare class SearchResultPlaylist extends ISearchResult { type: SearchResultType.PLAYLIST; length: number; constructor(info: Playlist$1); } /** * Class representing a song. * * <info>If {@link Song} is added from a YouTube {@link SearchResult} or {@link * Playlist}, some info will be missing to save your resources. It will be filled * when emitting {@link DisTube#playSong} event. * * Missing info: {@link Song#likes}, {@link Song#dislikes}, {@link Song#streamURL}, * {@link Song#related}, {@link Song#chapters}, {@link Song#age_restricted}</info> */ declare class Song<T = unknown> { #private; source: string; formats?: ytdl__default.videoFormat[]; id?: string; name?: string; isLive: boolean; duration: number; formattedDuration?: string; url: string; streamURL?: string; thumbnail?: string; related: RelatedSong[]; views: number; likes: number; dislikes: number; uploader: { name?: string; url?: string; }; age_restricted: boolean; chapters: Chapter[]; reposts: number; /** * Create a Song * * @param info - Raw info * @param options - Optional options */ constructor(info: ytdl__default.videoInfo | SearchResult | OtherSongInfo | ytdl__default.relatedVideo | RelatedSong | ytpl.result["items"][number], options?: { member?: GuildMember; source?: string; metadata?: T; }); _patchYouTube(i: ytdl__default.videoInfo | SearchResult): void; /** * Patch data from other source * * @param info - Video info */ _patchOther(info: OtherSongInfo): void; /** * The playlist added this song */ get playlist(): Playlist | undefined; set playlist(playlist: Playlist | undefined); /** * User requested. */ get member(): GuildMember | undefined; set member(member: GuildMember | undefined); /** * User requested. */ get user(): discord_js.User | undefined; get metadata(): T; set metadata(metadata: T); } /** * @virtual */ declare abstract class DisTubeBase { distube: DisTube; constructor(distube: DisTube); /** * Emit the {@link DisTube} of this base * * @param eventName - Event name * @param args - arguments */ emit(eventName: keyof DisTubeEvents, ...args: any): boolean; /** * Emit error event * * @param error - error * @param channel - Text channel where the error is encountered. */ emitError(error: Error, channel?: GuildTextBasedChannel): void; /** * The queue manager */ get queues(): QueueManager; /** * The voice manager */ get voices(): DisTubeVoiceManager; /** * Discord.js client */ get client(): Client; /** * DisTube options */ get options(): Options; /** * DisTube handler */ get handler(): DisTubeHandler; } /** * Create a voice connection to the voice channel */ declare class DisTubeVoice extends TypedEmitter<DisTubeVoiceEvents> { #private; readonly id: Snowflake; readonly voices: DisTubeVoiceManager; readonly audioPlayer: AudioPlayer; connection: VoiceConnection; audioResource?: AudioResource; emittedError: boolean; isDisconnected: boolean; stream?: DisTubeStream; constructor(voiceManager: DisTubeVoiceManager, channel: VoiceBasedChannel); /** * The voice channel id the bot is in */ get channelId(): string | undefined; get channel(): VoiceBasedChannel; set channel(channel: VoiceBasedChannel); /** * Join a voice channel with this connection * * @param channel - A voice channel */ join(channel?: VoiceBasedChannel): Promise<DisTubeVoice>; /** * Leave the voice channel of this connection * * @param error - Optional, an error to emit with 'error' event. */ leave(error?: Error): void; /** * Stop the playing stream * * @param force - If true, will force the {@link DisTubeVoice#audioPlayer} to enter the Idle state even * if the {@link DisTubeVoice#audioResource} has silence padding frames. */ stop(force?: boolean): void; /** * Play a {@link DisTubeStream} * * @param dtStream - DisTubeStream */ play(dtStream: DisTubeStream): void; set volume(volume: number); get volume(): number; /** * Playback duration of the audio resource in seconds */ get playbackDuration(): number; pause(): void; unpause(): void; /** * Whether the bot is self-deafened */ get selfDeaf(): boolean; /** * Whether the bot is self-muted */ get selfMute(): boolean; /** * Self-deafens/undeafens the bot. * * @param selfDeaf - Whether or not the bot should be self-deafened * * @returns true if the voice state was successfully updated, otherwise false */ setSelfDeaf(selfDeaf: boolean): boolean; /** * Self-mutes/unmutes the bot. * * @param selfMute - Whether or not the bot should be self-muted * * @returns true if the voice state was successfully updated, otherwise false */ setSelfMute(selfMute: boolean): boolean; /** * The voice state of this connection */ get voiceState(): VoiceState | undefined; } interface StreamOptions { ffmpeg: FFmpegOptions; seek?: number; type?: StreamType; } declare const chooseBestVideoFormat: ({ duration, formats, isLive }: Song) => ytdl.videoFormat | undefined; declare const checkFFmpeg: (distube: DisTube) => void; /** * Create a stream to play with {@link DisTubeVoice} */ declare class DisTubeStream extends TypedEmitter<{ debug: (debug: string) => Awaitable; error: (error: Error) => Awaitable; }> { private killed; process: ChildProcess; stream: PassThrough; type: StreamType$1; url: string; /** * Create a DisTubeStream to play with {@link DisTubeVoice} * * @param url - Stream URL * @param options - Stream options */ constructor(url: string, { ffmpeg, seek, type }: StreamOptions); private debug; kill(): void; /** * Create a stream from a YouTube {@link Song} * * @param song - A YouTube Song * @param options - options */ static YouTube(song: Song, options: StreamOptions): DisTubeStream; /** * Create a stream from a stream url * * @param url - stream url * @param options - options */ static DirectLink(url: string, options: StreamOptions): DisTubeStream; } /** * DisTube's Handler */ declare class DisTubeHandler extends DisTubeBase { #private; constructor(distube: DisTube); get ytdlOptions(): ytdl__default.getInfoOptions; get ytCookie(): string; /** * @param url - url * @param basic - getBasicInfo? */ getYouTubeInfo(url: string, basic?: boolean): Promise<ytdl__default.videoInfo>; resolve<T = unknown>(song: Song<T>, options?: Omit<ResolveOptions, "metadata">): Promise<Song<T>>; resolve<T = unknown>(song: Playlist<T>, options?: Omit<ResolveOptions, "metadata">): Promise<Playlist<T>>; resolve<T = unknown>(song: string | SearchResult, options?: ResolveOptions<T>): Promise<Song<T> | Playlist<T>>; resolve<T = unknown>(song: ytdl__default.videoInfo | OtherSongInfo | ytdl__default.relatedVideo, options?: ResolveOptions<T>): Promise<Song<T>>; resolve<T = unknown>(song: Playlist, options: ResolveOptions<T>): Promise<Playlist<T>>; resolve(song: string | ytdl__default.videoInfo | Song | Playlist | SearchResult | OtherSongInfo | ytdl__default.relatedVideo, options?: ResolveOptions): Promise<Song | Playlist>; resolvePlaylist<T = unknown>(playlist: Playlist<T> | Song<T>[] | string, options?: Omit<ResolvePlaylistOptions, "metadata">): Promise<Playlist<T>>; resolvePlaylist<T = undefined>(playlist: Playlist | Song[] | string, options: ResolvePlaylistOptions<T>): Promise<Playlist<T>>; resolvePlaylist(playlist: Playlist | Song[] | string, options?: ResolvePlaylistOptions): Promise<Playlist>; /** * Search for a song, fire {@link DisTube#error} if not found. * * @throws {@link DisTubeError} * * @param message - The original message from an user * @param query - The query string * * @returns Song info */ searchSong(message: Message<true>, query: string): Promise<SearchResult | null>; /** * Create a message collector for selecting search results. * * Needed events: {@link DisTube#searchResult}, {@link DisTube#searchCancel}, * {@link DisTube#searchInvalidAnswer}, {@link DisTube#searchDone}. * * @throws {@link DisTubeError} * * @param message - The original message from an user * @param results - The search results * @param query - The query string * * @returns Selected result */ createSearchMessageCollector<R extends SearchResult | Song | Playlist>(message: Message<true>, results: Array<R>, query?: string): Promise<R | null>; /** * Play or add a {@link Playlist} to the queue. * * @throws {@link DisTubeError} * * @param voiceChannel - A voice channel * @param playlist - A YouTube playlist url | a Playlist * @param options - Optional options */ playPlaylist(voiceChannel: VoiceBasedChannel, playlist: Playlist, options?: PlayHandlerOptions): Promise<void>; /** * Play or add a {@link Song} to the queue. * * @throws {@link DisTubeError} * * @param voiceChannel - A voice channel * @param song - A YouTube playlist url | a Playlist * @param options - Optional options */ playSong(voiceChannel: VoiceBasedChannel, song: Song, options?: PlayHandlerOptions): Promise<void>; /** * Get {@link Song}'s stream info and attach it to the song. * * @param song - A Song */ attachStreamInfo(song: Song): Promise<void>; } declare class Options { #private; plugins: (CustomPlugin | ExtractorPlugin)[]; emitNewSongOnly: boolean; leaveOnFinish: boolean; leaveOnStop: boolean; leaveOnEmpty: boolean; emptyCooldown: number; savePreviousSongs: boolean; searchSongs: number; searchCooldown: number; youtubeCookie?: Cookie[] | string; customFilters?: Filters; ytdlOptions: ytdl__default.getInfoOptions; nsfw: boolean; emitAddSongWhenCreatingQueue: boolean; emitAddListWhenCreatingQueue: boolean; joinNewVoiceChannel: boolean; streamType: StreamType; directLink: boolean; /** @deprecated */ ffmpegPath: undefined; /** @deprecated */ ffmpegDefaultArgs: undefined; ffmpeg: FFmpegOptions; constructor(options: DisTubeOptions); } /** * Manages the collection of a data model. * * @virtual */ declare abstract class BaseManager<V> extends DisTubeBase { /** * The collection of items for this manager. */ collection: Collection<string, V>; /** * The size of the collection. */ get size(): number; } /** * Manages the collection of a data model paired with a guild id. * * @virtual */ declare abstract class GuildIdManager<V> extends BaseManager<V> { add(idOrInstance: GuildIdResolvable, data: V): this; get(idOrInstance: GuildIdResolvable): V | undefined; remove(idOrInstance: GuildIdResolvable): boolean; has(idOrInstance: GuildIdResolvable): boolean; } /** * Manages voice connections for {@link DisTube} */ declare class DisTubeVoiceManager extends GuildIdManager<DisTubeVoice> { /** * Get a {@link DisTubeVoice}. * * @param guild - The queue resolvable to resolve */ /** * Collection of {@link DisTubeVoice}. */ /** * Create a {@link DisTubeVoice} * * @param channel - A voice channel to join */ create(channel: VoiceBasedChannel): DisTubeVoice; /** * Join a voice channel * * @param channel - A voice channel to join */ join(channel: VoiceBasedChannel): Promise<DisTubeVoice>; /** * Leave the connected voice channel in a guild * * @param guild - Queue Resolvable */ leave(guild: GuildIdResolvable): void; } /** * Manage filters of a playing {@link Queue} */ declare class FilterManager extends BaseManager<Filter> { #private; /** * Collection of {@link Filter}. */ queue: Queue; constructor(queue: Queue); /** * Enable a filter or multiple filters to the manager * * @param filterOrFilters - The filter or filters to enable * @param override - Wether or not override the applied filter with new filter value */ add(filterOrFilters: FilterResolvable | FilterResolvable[], override?: boolean): this; /** * Clear enabled filters of the manager */ clear(): this; /** * Set the filters applied to the manager * * @param filters - The filters to apply */ set(filters: FilterResolvable[]): this; /** * Disable a filter or multiple filters * * @param filterOrFilters - The filter or filters to disable */ remove(filterOrFilters: FilterResolvable | FilterResolvable[]): this; /** * Check whether a filter enabled or not * * @param filter - The filter to check */ has(filter: FilterResolvable): boolean; /** * Array of enabled filter names */ get names(): string[]; /** * Array of enabled filters */ get values(): Filter[]; get ffmpegArgs(): FFmpegArgs; toString(): string; } /** * Queue manager */ declare class QueueManager extends GuildIdManager<Queue> { #private; /** * Collection of {@link Queue}. */ /** * Create a {@link Queue} * * @param channel - A voice channel * @param song - First song * @param textChannel - Default text channel * * @returns Returns `true` if encounter an error */ create(channel: VoiceBasedChannel, song: Song[] | Song, textChannel?: GuildTextBasedChannel): Promise<Queue | true>; /** * Create a ytdl stream * * @param queue - Queue */ createStream(queue: Queue): DisTubeStream; /** * Play a song on voice connection * * @param queue - The guild queue * * @returns error? */ playSong(queue: Queue): Promise<boolean>; } /** * Represents a queue. */ declare class Queue extends DisTubeBase { #private; readonly id: Snowflake; voice: DisTubeVoice; songs: Song[]; previousSongs: Song[]; stopped: boolean; _next: boolean; _prev: boolean; playing: boolean; paused: boolean; repeatMode: RepeatMode; autoplay: boolean; beginTime: number; textChannel?: GuildTextBasedChannel; _emptyTimeout?: NodeJS.Timeout; _taskQueue: TaskQueue; _listeners?: DisTubeVoiceEvents; /** * Create a queue for the guild * * @param distube - DisTube * @param voice - Voice connection * @param song - First song(s) * @param textChannel - Default text channel */ constructor(distube: DisTube, voice: DisTubeVoice, song: Song | Song[], textChannel?: GuildTextBasedChannel); /** * The client user as a `GuildMember` of this queue's guild */ get clientMember(): discord_js.GuildMember | undefined; /** * The filter manager of the queue */ get filters(): FilterManager; /** * Formatted duration string. */ get formattedDuration(): string; /** * Queue's duration. */ get duration(): number; /** * What time in the song is playing (in seconds). */ get currentTime(): number; /** * Formatted {@link Queue#currentTime} string. */ get formattedCurrentTime(): string; /** * The voice channel playing in. */ get voiceChannel(): discord_js.VoiceBasedChannel | null; get volume(): number; set volume(value: number); /** * @throws {DisTubeError} * * @param song - Song to add * @param position - Position to add, \<= 0 to add to the end of the queue * * @returns The guild queue */ addToQueue(song: Song | Song[], position?: number): Queue; /** * Pause the guild stream * * @returns The guild queue */ pause(): Queue; /** * Resume the guild stream * * @returns The guild queue */ resume(): Queue; /** * Set the guild stream's volume * * @param percent - The percentage of volume you want to set * * @returns The guild queue */ setVolume(percent: number): Queue; /** * Skip the playing song if there is a next song in the queue. <info>If {@link * Queue#autoplay} is `true` and there is no up next song, DisTube will add and * play a related song.</info> * * @returns The song will skip to */ skip(): Promise<Song>; /** * Play the previous song if exists * * @returns The guild queue */ previous(): Promise<Song>; /** * Shuffle the queue's songs * * @returns The guild queue */ shuffle(): Promise<Queue>; /** * Jump to the song position in the queue. The next one is 1, 2,... The previous * one is -1, -2,... * if `num` is invalid number * * @param position - The song position to play * * @returns The new Song will be played */ jump(position: number): Promise<Song>; /** * Set the repeat mode of the guild queue. * Toggle mode `(Disabled -> Song -> Queue -> Disabled ->...)` if `mode` is `undefined` * * @param mode - The repeat modes (toggle if `undefined`) * * @returns The new repeat mode */ setRepeatMode(mode?: RepeatMode): RepeatMode; /** * Set the playing time to another position * * @param time - Time in seconds * * @returns The guild queue */ seek(time: number): Queue; /** * Add a related song of the playing song to the queue * * @returns The added song */ addRelatedSong(): Promise<Song>; /** * Stop the guild stream and delete the queue */ stop(): Promise<void>; /** * Remove the queue from the manager (This does not leave the voice channel even if * {@link DisTubeOptions | DisTubeOptions.leaveOnStop} is enabled) */ remove(): void; /** * Toggle autoplay mode * * @returns Autoplay mode state */ toggleAutoplay(): boolean; } /** * DisTube Plugin * * @virtual */ declare abstract class Plugin { abstract type: PluginType; distube: DisTube; init(distube: DisTube): void; /** * Type of the plugin */ /** * Emit an event to the {@link DisTube} class * * @param eventName - Event name * @param args - arguments */ emit(eventName: keyof DisTubeEvents, ...args: any): boolean; /** * Emit error event to the {@link DisTube} class * * @param error - error * @param channel - Text channel where the error is encountered. */ emitError(error: Error, channel?: GuildTextBasedChannel): void; /** * The queue manager */ get queues(): QueueManager; /** * The voice manager */ get voices(): DisTubeVoiceManager; /** * Discord.js client */ get client(): Client; /** * DisTube options */ get options(): Options; /** * DisTube handler */ get handler(): DisTubeHandler; /** * Check if the string is working with this plugin * * @param _string - Input string */ validate(_string: string): Awaitable<boolean>; /** * Get the stream url from {@link Song#url}. Returns {@link Song#url} by default. * Not needed if the plugin plays song from YouTube. * * @param url - Input url */ getStreamURL(url: string): Awaitable<string>; /** * Get related songs from a supported url. {@link Song#member} should be * `undefined`. Not needed to add {@link Song#related} because it will be added * with this function later. * * @param _url - Input url */ getRelatedSongs(_url: string): Awaitable<RelatedSong[]>; } /** * Custom Plugin * * @virtual */ declare abstract class CustomPlugin extends Plugin { readonly type = PluginType.CUSTOM; abstract play(voiceChannel: VoiceBasedChannel, song: string, options: PlayOptions): Awaitable<void>; } /** * Extractor Plugin * * @virtual */ declare abstract class ExtractorPlugin extends Plugin { readonly type = PluginType.EXTRACTOR; abstract resolve<T = unknown>(url: string, options: { member?: GuildMember; metadata?: T; }): Awaitable<Song<T> | Playlist<T>>; } /** * Format duration to string * * @param sec - Duration in seconds */ declare function formatDuration(sec: number): string; /** * Convert formatted duration to seconds * * @param input - Formatted duration string */ declare function toSecond(input: any): number; /** * Parse number from input * * @param input - Any */ declare function parseNumber(input: any): number; declare const SUPPORTED_PROTOCOL: readonly ["https:", "http:", "file:"]; /** * Check if the string is an URL * * @param input - input */ declare function isURL(input: any): input is `${(typeof SUPPORTED_PROTOCOL)[number]}//${string}`; /** * Check if the Client has enough intents to using DisTube * * @param options - options */ declare function checkIntents(options: ClientOptions): void; /** * Check if the voice channel is empty * * @param voiceState - voiceState */ declare function isVoiceChannelEmpty(voiceState: VoiceState): boolean; declare function isSnowflake(id: any): id is Snowflake; declare function isMemberInstance(member: any): member is GuildMember; declare function isTextChannelInstance(channel: any): channel is GuildTextBasedChannel; declare function isMessageInstance(message: any): message is Message<true>; declare function isSupportedVoiceChannel(channel: any): channel is VoiceBasedChannel; declare function isGuildInstance(guild: any): guild is Guild; declare function resolveGuildId(resolvable: GuildIdResolvable): Snowflake; declare function isClientInstance(client: any): client is Client; declare function checkInvalidKey(target: Record<string, any>, source: Record<string, any> | string[], sourceName: string): void; declare function isObject(obj: any): obj is object; declare function isRecord<T = unknown>(obj: any): obj is Record<string, T>; type KeyOf<T> = T extends object ? (keyof T)[] : []; declare function objectKeys<T>(obj: T): KeyOf<T>; declare function isNsfwChannel(channel?: GuildTextBasedChannel): boolean; type Falsy = undefined | null | false | 0 | ""; declare const isTruthy: <T>(x: T | Falsy) => x is T; declare class DirectLinkPlugin extends ExtractorPlugin { validate(url: string): Promise<boolean>; resolve(url: string, options?: { member?: GuildMember; metadata?: any; }): Song<any>; } declare const version: string; interface DisTube extends TypedEmitter<TypedDisTubeEvents> { /** * @event * Emitted after DisTube add a new playlist to the playing {@link Queue}. * * @example * ```ts * distube.on("addList", (queue, playlist) => queue.textChannel.send( * `Added \`${playlist.name}\` playlist (${playlist.songs.length} songs) to the queue!` * )); * ``` * * @param queue - The guild queue * @param playlist - Playlist info */ [Events.ADD_LIST]: (queue: Queue, playlist: Playlist) => Awaitable; /** * @event * Emitted after DisTube add a new song to the playing {@link Queue}. * * @example * ```ts * distube.on("addSong", (queue, song) => queue.textChannel.send( * `Added ${song.name} - \`${song.formattedDuration}\` to the queue by ${song.user}.` * )); * ``` * * @param queue - The guild queue * @param song - Added song */ [Events.ADD_SONG]: (queue: Queue, song: Song) => Awaitable; /** * @event * Emitted when a {@link Queue} is deleted with any reasons. * * @param queue - The guild queue */ [Events.DELETE_QUEUE]: (queue: Queue) => Awaitable; /** * @event * Emitted when the bot is disconnected to a voice channel. * * @param queue - The guild queue */ [Events.DISCONNECT]: (queue: Queue) => Awaitable; /** * @event * Emitted when there is no user in the voice channel, {@link DisTubeOptions}.leaveOnEmpty * is `true` and there is a playing queue. * * If there is no playing queue (stopped and {@link DisTubeOptions}.leaveOnStop is * `false`), it will leave the channel without emitting this event. * * @example * ```ts * distube.on("empty", queue => queue.textChannel.send("Channel is empty. Leaving the channel")) * ``` * * @param queue - The guild queue */ [Events.EMPTY]: (queue: Queue) => Awaitable; /** * @event * Emitted when DisTube encounters an error while playing songs. * * @example * ```ts * distube.on('error', (channel, e) => { * if (channel) channel.send(`An error encountered: ${e}`) * else console.error(e) * }) * ``` * * @param channel - Text channel where the error is encountered. * @param error - The error encountered */ [Events.ERROR]: (channel: GuildTextBasedChannel | undefined, error: Error) => Awaitable; /** * @event * Emitted for FFmpeg debugging information. * * @param debug - The debug information */ [Events.FFMPEG_DEBUG]: (debug: string) => Awaitable; /** * @event * Emitted when there is no more song in the queue and {@link Queue#autoplay} is * `false`. DisTube will leave voice channel if {@link * DisTubeOptions}.leaveOnFinish is `true`. * * @example * ```ts * distube.on("finish", queue => queue.textChannel.send("No more song in queue")); * ``` * * @param queue - The guild queue */ [Events.FINISH]: (queue: Queue) => Awaitable; /** * @event * Emitted when DisTube finished a song. * * @example * ```ts * distube.on("finishSong", (queue, song) => queue.textChannel.send(`${song.name} has finished!`)); * ``` * * @param queue - The guild queue * @param song - Finished song */ [Events.FINISH_SONG]: (queue: Queue, song: Song) => Awaitable; /** * @event * Emitted when DisTube initialize a queue to change queue default properties. * * @example * ```ts * distube.on("initQueue", queue => { * queue.autoplay = false; * queue.volume = 100; * }); * ```ts * * @param queue - The guild queue */ [Events.INIT_QUEUE]: (queue: Queue) => Awaitable; /** * @event * Emitted when {@link Queue#autoplay} is `true`, {@link Queue#songs} is empty, and * DisTube cannot find related songs to play. * * @example * ```ts * distube.on("noRelated", queue => queue.textChannel.send("Can't find related video to play.")); * ```ts * * @param queue - The guild queue */ [Events.NO_RELATED]: (queue: Queue) => Awaitable; /** * @event * Emitted when DisTube play a song. * * If {@link DisTubeOptions}.emitNewSongOnly is `true`, this event is not emitted * when looping a song or next song is the previous one. * * @example * ```ts * distube.on("playSong", (queue, song) => queue.textChannel.send( * `Playing \`${song.name}\` - \`${song.formattedDuration}\`\nRequested by: ${song.user}` * )); * ```ts * * @param queue - The guild queue * @param song - Playing song */ [Events.PLAY_SONG]: (queue: Queue, song: Song) => Awaitable; /** * @event * Emitted when {@link DisTubeOptions | DisTubeOptions.searchSongs} bigger than 0, * and the search canceled due to {@link DisTubeOptions | * DisTubeOptions.searchTimeout}. * * @example * ```ts * // DisTubeOptions.searchSongs > 0 * distube.on("searchCancel", (message) => message.channel.send(`Searching canceled`)); * ```ts * * @param message - The user message called play method * @param query - The search query */ [Events.SEARCH_CANCEL]: (message: Message, query: string) => Awaitable; /** * @event * Emitted when {@link DisTubeOptions | DisTubeOptions.searchSongs} bigger than 0, * and after the user chose a search result to play. * * @param message - The user message called play method * @param answer - The answered message of user * @param query - The search query */ [Events.SEARCH_DONE]: (message: Message, answer: string, query: string) => Awaitable; /** * @event * Emitted when {@link DisTubeOptions | DisTubeOptions.searchSongs} bigger than 0, * and the search canceled due to user's next message is not a number or out of * results range. * * @example * ```ts * // DisTubeOptions.searchSongs > 0 * distube.on("searchInvalidAnswer", (message) => message.channel.send(`You answered an invalid number!`)); * ```ts * * @param message - The user message called play method * @param answer - The answered message of user * @param query - The search query */ [Events.SEARCH_INVALID_ANSWER]: (message: Message, answer: string, query: string) => Awaitable; /** * @event * Emitted when DisTube cannot find any results for the query. * * @example * ```ts * distube.on("searchNoResult", (message, query) => message.channel.send(`No result found for ${query}!`)); * ```ts * * @param message - The user message called play method * @param query - The search query */ [Events.SEARCH_NO_RESULT]: (message: Message, query: string) => Awaitable; /** * @event * Emitted when {@link DisTubeOptions | DisTubeOptions.searchSongs} bigger than 0, * and song param of {@link DisTube#play} is invalid url. DisTube will wait for * user's next message to choose a song manually. <info>{@link * https://support.google.com/youtube/answer/7354993 | Safe search} is enabled if * {@link DisTubeOptions}.nsfw is disabled and the message's channel is not a nsfw * channel.</info> * * @example * ```ts * // DisTubeOptions.searchSongs > 0 * distube.on("searchResult", (message, results) => { * message.channel.send(`**Choose an option from below**\n${ * results.map((song, i) => `**${i + 1}**. ${song.name} - \`${song.formattedDuration}\``).join("\n") * }\n*Enter anything else or wait 60 seconds to cancel*`); * }); * ```ts * * @param message - The user message called play method * @param results - Searched results * @param query - The search query */ [Events.SEARCH_RESULT]: (message: Message, results: SearchResult[], query: string) => Awaitable; } /** * DisTube class */ declare class DisTube extends TypedEmitter<TypedDisTubeEvents> { #private; readonly handler: DisTubeHandler; readonly options: Options; readonly client: Client; readonly queues: QueueManager; readonly voices: DisTubeVoiceManager; readonly extractorPlugins: ExtractorPlugin[]; readonly customPlugins: CustomPlugin[]; readonly filters: Filters; /** * @deprecated Use `youtubeCookie: Cookie[]` instead. Guide: {@link * https://github.com/skick1234/DisTube/wiki/YouTube-Cookies | YouTube Cookies} */ constructor(client: Client, opts: DisTubeOptions & { youtubeCookie: string; }); /** * Create a new DisTube class. * * @example * ```ts * const Discord = require('discord.js'), * DisTube = require('distube'), * client = new Discord.Client(); * // Create a new DisTube * const distube = new DisTube.default(client, { searchSongs: 10 }); * // client.DisTube = distube // make it access easily * client.login("Your Discord Bot Token") * ```ts * * @throws {@link DisTubeError} * * @param client - Discord.JS client * @param opts - Custom DisTube options */ constructor(client: Client, opts?: DisTubeOptions); static get version(): string; /** * DisTube version */ get version(): string; /** * Play / add a song or playlist from url. Search and play a song if it is not a * valid url. * * @example * ```ts * client.on('message', (message) => { * if (!message.content.startsWith(config.prefix)) return; * const args = message.content.slice(config.prefix.length).trim().split(/ +/g); * const command = args.shift(); * if (command == "play") * distube.play(message.member.voice.channel, args.join(" "), { * member: message.member, * textChannel: message.channel, * message * }); * }); * ```ts * * @throws {@link DisTubeError} * * @param voiceChannel - The channel will be joined if the bot isn't in any channels, the bot will be * moved to this chann