@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
TypeScript
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