UNPKG

discord-player

Version:

Complete framework to facilitate music commands using discord.js

1,523 lines (1,508 loc) 819 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __typeError = (msg) => { throw TypeError(msg); }; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default")); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method); // src/index.ts var src_exports = {}; __export(src_exports, { AF_NIGHTCORE_RATE: () => import_equalizer3.AF_NIGHTCORE_RATE, AF_VAPORWAVE_RATE: () => import_equalizer3.AF_VAPORWAVE_RATE, AFilterGraph: () => AFilterGraph, AsyncQueue: () => AsyncQueue, AsyncQueueEntry: () => AsyncQueueEntry, AudioFilters: () => AudioFilters, AudioPlayer: () => import_discord_voip6.AudioPlayer, BASS_EQ_BANDS: () => import_equalizer3.BASS_EQ_BANDS, BaseExtractor: () => BaseExtractor, BiquadFilterType: () => import_equalizer3.FilterType, Context: () => Context, DependencyReportGenerator: () => DependencyReportGenerator, DiscordPlayerQueryResultCache: () => DiscordPlayerQueryResultCache, EqualizerConfigurationPreset: () => EqualizerConfigurationPreset, ExtractorExecutionContext: () => ExtractorExecutionContext, FFMPEG_ARGS_PIPED: () => FFMPEG_ARGS_PIPED, FFMPEG_ARGS_STRING: () => FFMPEG_ARGS_STRING, FFMPEG_SRATE_REGEX: () => FFMPEG_SRATE_REGEX, FFmpegFilterer: () => FFmpegFilterer, FiltersChain: () => import_equalizer3.FiltersChain, GuildNodeManager: () => GuildNodeManager, GuildQueue: () => GuildQueue5, GuildQueueAudioFilters: () => GuildQueueAudioFilters, GuildQueueEvent: () => GuildQueueEvent, GuildQueueHistory: () => GuildQueueHistory, GuildQueuePlayerNode: () => GuildQueuePlayerNode, GuildQueueStatistics: () => GuildQueueStatistics, InterceptedStream: () => InterceptedStream, LrcLib: () => LrcLib, PCMAudioFilters: () => import_equalizer3.AudioFilters, Player: () => Player, PlayerEvent: () => PlayerEvent, PlayerEventsEmitter: () => PlayerEventsEmitter, PlayerStreamInterceptor: () => PlayerStreamInterceptor, Playlist: () => Playlist, Q_BUTTERWORTH: () => import_equalizer3.Q_BUTTERWORTH, QueryCache: () => QueryCache, QueryResolver: () => QueryResolver, QueryType: () => QueryType, QueueRepeatMode: () => QueueRepeatMode, SearchResult: () => SearchResult, SequentialBucket: () => SequentialBucket, SerializedType: () => SerializedType, StreamDispatcher: () => StreamDispatcher, StreamType: () => import_discord_voip6.StreamType, Track: () => Track, TrackSkipReason: () => TrackSkipReason, TypeUtil: () => TypeUtil, Util: () => Util, VALIDATE_QUEUE_CAP: () => VALIDATE_QUEUE_CAP, VoiceUtils: () => VoiceUtils, VolumeTransformer: () => import_equalizer3.VolumeTransformer, createAudioPlayer: () => import_discord_voip6.createAudioPlayer, createAudioResource: () => import_discord_voip6.createAudioResource, createContext: () => createContext, createErisCompat: () => createErisCompat, createFFmpegStream: () => createFFmpegStream, createOceanicCompat: () => createOceanicCompat, decode: () => decode, deserialize: () => deserialize, encode: () => encode, getVoiceConnection: () => import_discord_voip6.getVoiceConnection, getVoiceConnections: () => import_discord_voip6.getVoiceConnections, isErisProxy: () => isErisProxy, isOceanicProxy: () => isOceanicProxy, joinVoiceChannel: () => import_discord_voip6.joinVoiceChannel, onAfterCreateStream: () => onAfterCreateStream, onBeforeCreateStream: () => onBeforeCreateStream, onStreamExtracted: () => onStreamExtracted, serialize: () => serialize, tryIntoThumbnailString: () => tryIntoThumbnailString, useContext: () => useContext, useHistory: () => useHistory, useMainPlayer: () => useMainPlayer, useMetadata: () => useMetadata, usePlayer: () => usePlayer, useQueue: () => useQueue, useTimeline: () => useTimeline, useVolume: () => useVolume, version: () => version }); module.exports = __toCommonJS(src_exports); // src/compat/createErisCompat.ts var import_v10 = require("discord-api-types/v10"); // src/compat/common.ts var DiscordPlayerClientSymbol = Symbol("DiscordPlayerClient"); function createCompatClient(client, provider) { Reflect.set(client, DiscordPlayerClientSymbol, provider); return { provider, client }; } __name(createCompatClient, "createCompatClient"); function isClientProxy(client) { return Reflect.get(client, DiscordPlayerClientSymbol) != null; } __name(isClientProxy, "isClientProxy"); function getCompatName(client) { return Reflect.get(client, DiscordPlayerClientSymbol) ?? null; } __name(getCompatName, "getCompatName"); function isErisProxy(client) { return getCompatName(client) === "Eris"; } __name(isErisProxy, "isErisProxy"); function isOceanicProxy(client) { return getCompatName(client) === "Oceanic"; } __name(isOceanicProxy, "isOceanicProxy"); // src/utils/Util.ts var import_promises = require("timers/promises"); // src/fabric/Track.ts var import_discord = require("discord.js"); // src/errors/index.ts var _DiscordPlayerError = class _DiscordPlayerError extends Error { constructor(code, message) { super(message); __publicField(this, "code"); __publicField(this, "timestamp", Date.now()); this.name = this.constructor.name; this.code = code; if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } } toJSON() { return { name: this.constructor.name, code: this.code, message: this.message, timestamp: this.timestamp }; } }; __name(_DiscordPlayerError, "DiscordPlayerError"); var DiscordPlayerError = _DiscordPlayerError; var _OutOfSpaceError = class _OutOfSpaceError extends DiscordPlayerError { constructor(target, capacity, total) { super( ErrorCodes.ERR_OUT_OF_SPACE, `Max capacity reached for ${target} (Capacity ${capacity}/Total ${total})` ); } }; __name(_OutOfSpaceError, "OutOfSpaceError"); var OutOfSpaceError = _OutOfSpaceError; var _InvalidArgTypeError = class _InvalidArgTypeError extends DiscordPlayerError { constructor(target, expectation, found) { super( ErrorCodes.ERR_INVALID_ARG_TYPE, `Expected ${target} to be "${expectation}", received "${found}"` ); } }; __name(_InvalidArgTypeError, "InvalidArgTypeError"); var InvalidArgTypeError = _InvalidArgTypeError; var _NoResultError = class _NoResultError extends DiscordPlayerError { constructor(message) { super(ErrorCodes.ERR_NO_RESULT, message); } }; __name(_NoResultError, "NoResultError"); var NoResultError = _NoResultError; var _NotImplementedError = class _NotImplementedError extends DiscordPlayerError { constructor(target) { super(ErrorCodes.ERR_NOT_IMPLEMENTED, `${target} is not yet implemented`); } }; __name(_NotImplementedError, "NotImplementedError"); var NotImplementedError = _NotImplementedError; var _NotExistingError = class _NotExistingError extends DiscordPlayerError { constructor(target) { super(ErrorCodes.ERR_NOT_EXISTING, `${target} does not exist`); } }; __name(_NotExistingError, "NotExistingError"); var NotExistingError = _NotExistingError; var _OutOfRangeError = class _OutOfRangeError extends DiscordPlayerError { constructor(target, value, minimum, maximum) { super( ErrorCodes.ERR_OUT_OF_RANGE, `${target} is out of range (Expected minimum ${minimum} and maximum ${maximum}, got ${value})` ); } }; __name(_OutOfRangeError, "OutOfRangeError"); var OutOfRangeError = _OutOfRangeError; var _NoVoiceConnectionError = class _NoVoiceConnectionError extends DiscordPlayerError { constructor(message) { super( ErrorCodes.ERR_NO_VOICE_CONNECTION, message || "No voice connection available, maybe connect to a voice channel first?" ); } }; __name(_NoVoiceConnectionError, "NoVoiceConnectionError"); var NoVoiceConnectionError = _NoVoiceConnectionError; var _VoiceConnectionDestroyedError = class _VoiceConnectionDestroyedError extends DiscordPlayerError { constructor() { super( ErrorCodes.ERR_VOICE_CONNECTION_DESTROYED, "Cannot use destroyed voice connection" ); } }; __name(_VoiceConnectionDestroyedError, "VoiceConnectionDestroyedError"); var VoiceConnectionDestroyedError = _VoiceConnectionDestroyedError; var _NoVoiceChannelError = class _NoVoiceChannelError extends DiscordPlayerError { constructor() { super(ErrorCodes.ERR_NO_VOICE_CHANNEL, "Could not get the voice channel"); } }; __name(_NoVoiceChannelError, "NoVoiceChannelError"); var NoVoiceChannelError = _NoVoiceChannelError; var _NoAudioResourceError = class _NoAudioResourceError extends DiscordPlayerError { constructor(message) { super( ErrorCodes.ERR_NO_AUDIO_RESOURCE, message || "Expected an audio resource" ); } }; __name(_NoAudioResourceError, "NoAudioResourceError"); var NoAudioResourceError = _NoAudioResourceError; var _NoGuildQueueError = class _NoGuildQueueError extends DiscordPlayerError { constructor(message) { super(ErrorCodes.ERR_NO_GUILD_QUEUE, message || "Expected a guild queue"); } }; __name(_NoGuildQueueError, "NoGuildQueueError"); var NoGuildQueueError = _NoGuildQueueError; var _NoGuildError = class _NoGuildError extends DiscordPlayerError { constructor(message) { super(ErrorCodes.ERR_NO_GUILD, message || "Expected a guild"); } }; __name(_NoGuildError, "NoGuildError"); var NoGuildError = _NoGuildError; var _InfoRequiredError = class _InfoRequiredError extends DiscordPlayerError { constructor(target, actual) { super( ErrorCodes.ERR_INFO_REQUIRED, `Expected ${target}, found "${actual}"` ); } }; __name(_InfoRequiredError, "InfoRequiredError"); var InfoRequiredError = _InfoRequiredError; var _SerializationError = class _SerializationError extends DiscordPlayerError { constructor() { super( ErrorCodes.ERR_SERIALIZATION_FAILED, "Don't know how to serialize this data" ); } }; __name(_SerializationError, "SerializationError"); var SerializationError = _SerializationError; var _DeserializationError = class _DeserializationError extends DiscordPlayerError { constructor() { super( ErrorCodes.ERR_DESERIALIZATION_FAILED, "Don't know how to deserialize this data" ); } }; __name(_DeserializationError, "DeserializationError"); var DeserializationError = _DeserializationError; var _IllegalHookInvocationError = class _IllegalHookInvocationError extends DiscordPlayerError { constructor(target, message) { super( ErrorCodes.ERR_ILLEGAL_HOOK_INVOCATION, `Illegal invocation of ${target} hook.${message ? ` ${message}` : ""}` ); } }; __name(_IllegalHookInvocationError, "IllegalHookInvocationError"); var IllegalHookInvocationError = _IllegalHookInvocationError; var _BridgeFailedError = class _BridgeFailedError extends DiscordPlayerError { constructor(id, error) { super( ErrorCodes.ERR_BRIDGE_FAILED, `${id ? `(Extractor Execution Context ID is ${id})` : ""}Failed to bridge this query: ${error}` ); } }; __name(_BridgeFailedError, "BridgeFailedError"); var BridgeFailedError = _BridgeFailedError; var ErrorCodes = { ERR_OUT_OF_SPACE: "ERR_OUT_OF_SPACE", ERR_INVALID_ARG_TYPE: "ERR_INVALID_ARG_TYPE", ERR_NO_RESULT: "ERR_NO_RESULT", ERR_NOT_IMPLEMENTED: "ERR_NOT_IMPLEMENTED", ERR_NOT_EXISTING: "ERR_NOT_EXISTING", ERR_OUT_OF_RANGE: "ERR_OUT_OF_RANGE", ERR_NO_VOICE_CONNECTION: "ERR_NO_VOICE_CONNECTION", ERR_VOICE_CONNECTION_DESTROYED: "ERR_VOICE_CONNECTION_DESTROYED", ERR_NO_VOICE_CHANNEL: "ERR_NO_VOICE_CHANNEL", ERR_INVALID_VOICE_CHANNEL: "ERR_INVALID_VOICE_CHANNEL", ERR_NO_RECEIVER: "ERR_NO_RECEIVER", ERR_FFMPEG_LOCATOR: "ERR_FFMPEG_LOCATOR", ERR_NO_AUDIO_RESOURCE: "ERR_NO_AUDIO_RESOURCE", ERR_NO_GUILD_QUEUE: "ERR_NO_GUILD_QUEUE", ERR_NO_GUILD: "ERR_NO_GUILD", ERR_INFO_REQUIRED: "ERR_INFO_REQUIRED", ERR_SERIALIZATION_FAILED: "ERR_SERIALIZATION_FAILED", ERR_DESERIALIZATION_FAILED: "ERR_DESERIALIZATION_FAILED", ERR_ILLEGAL_HOOK_INVOCATION: "ERR_ILLEGAL_HOOK_INVOCATION", ERR_NOT_EXISTING_MODULE: "ERR_NOT_EXISTING_MODULE", ERR_BRIDGE_FAILED: "ERR_BRIDGE_FAILED" }; function isDiscordPlayerError(error) { return error != null && error instanceof DiscordPlayerError; } __name(isDiscordPlayerError, "isDiscordPlayerError"); // src/utils/TypeUtil.ts var _TypeUtil = class _TypeUtil { constructor() { return _TypeUtil; } // eslint-disable-next-line @typescript-eslint/ban-types static isFunction(t) { return typeof t === "function"; } static isNumber(t) { return typeof t === "number" && !isNaN(t); } static isString(t) { return typeof t === "string"; } static isBoolean(t) { return typeof t === "boolean"; } static isNullish(t) { return t == null; } static isArray(t) { return Array.isArray(t); } static isError(t) { return t instanceof Error; } static isDiscordPlayerError(t) { return isDiscordPlayerError(t); } }; __name(_TypeUtil, "TypeUtil"); var TypeUtil = _TypeUtil; // src/utils/serde.ts var import_buffer = require("buffer"); var SerializedType = /* @__PURE__ */ ((SerializedType2) => { SerializedType2["Track"] = "track"; SerializedType2["Playlist"] = "playlist"; return SerializedType2; })(SerializedType || {}); var isTrack = /* @__PURE__ */ __name((data) => data.$type === "track" /* Track */, "isTrack"); var isPlaylist = /* @__PURE__ */ __name((data) => data.$type === "playlist" /* Playlist */, "isPlaylist"); function serialize(data) { if (data instanceof Track) return data.serialize(); if (data instanceof Playlist) return data.serialize(); try { return data.toJSON(); } catch { throw new SerializationError(); } } __name(serialize, "serialize"); function deserialize(player, data) { if (isTrack(data)) return Track.fromSerialized(player, data); if (isPlaylist(data)) return Playlist.fromSerialized(player, data); throw new DeserializationError(); } __name(deserialize, "deserialize"); function encode(data) { const str = JSON.stringify(data); return import_buffer.Buffer.from(str).toString("base64"); } __name(encode, "encode"); function decode(data) { const str = import_buffer.Buffer.from(data, "base64").toString(); return JSON.parse(str); } __name(decode, "decode"); function tryIntoThumbnailString(data) { if (!data) return null; try { if (TypeUtil.isString(data)) return data; return data?.url ?? data?.thumbnail?.url ?? null; } catch { return null; } } __name(tryIntoThumbnailString, "tryIntoThumbnailString"); // src/fabric/Track.ts var _onSeek, _resource; var _Track = class _Track { /** * Track constructor * @param player The player that instantiated this Track * @param data Track data */ constructor(player, data) { this.player = player; __publicField(this, "title"); __publicField(this, "description"); __publicField(this, "author"); __publicField(this, "url"); __publicField(this, "thumbnail"); __publicField(this, "duration"); __publicField(this, "views"); __publicField(this, "requestedBy", null); __publicField(this, "playlist"); __publicField(this, "queryType", null); // eslint-disable-next-line @typescript-eslint/no-explicit-any __publicField(this, "raw"); __publicField(this, "extractor", null); __publicField(this, "id", import_discord.SnowflakeUtil.generate().toString()); __publicField(this, "__metadata", null); __publicField(this, "__reqMetadataFn"); __publicField(this, "cleanTitle"); __publicField(this, "live", false); __publicField(this, "bridgedExtractor", null); __publicField(this, "bridgedTrack", null); __privateAdd(this, _onSeek, null); __privateAdd(this, _resource, null); this.title = (0, import_discord.escapeMarkdown)(data.title ?? ""); this.author = data.author ?? ""; this.url = data.url ?? ""; this.thumbnail = data.thumbnail ?? ""; this.duration = data.duration ?? ""; this.views = data.views ?? 0; this.queryType = data.queryType; this.requestedBy = data.requestedBy || null; this.playlist = data.playlist; this.description = `${this.title} by ${this.author}`; this.raw = Object.assign( {}, { source: data.raw?.source ?? data.source }, data.raw ?? data ); this.__metadata = data.metadata ?? null; this.__reqMetadataFn = data.requestMetadata || (() => Promise.resolve(null)); this.cleanTitle = data.cleanTitle ?? Util.cleanTitle(this.title, this.source); this.live = data.live ?? false; } /** * Whether this track can be seeked */ get seekable() { return __privateGet(this, _onSeek) !== null; } /** * Set the onSeek event * @param fn The onSeek event */ handleSeek(fn) { __privateSet(this, _onSeek, fn); } /** * Request seek * @param event The seek event */ async seek(event) { if (__privateGet(this, _onSeek)) return __privateGet(this, _onSeek).call(this, event); } /** * Sets audio resource for this track. This is not useful outside of the library. * @param resource Audio resource */ setResource(resource) { __privateSet(this, _resource, resource); } /** * Gets audio resource for this track */ get resource() { return __privateGet(this, _resource); } /** * Whether this track has an audio resource */ get hasResource() { return __privateGet(this, _resource) != null; } /** * Request metadata for this track */ async requestMetadata() { const res = await this.__reqMetadataFn(); this.setMetadata(res); return res; } /** * Set metadata for this track */ setMetadata(m) { this.__metadata = m; } /** * Metadata of this track */ get metadata() { return this.__metadata; } /** * If this track has metadata */ get hasMetadata() { return this.metadata != null; } /** * The queue in which this track is located */ get queue() { return this.player.nodes.cache.find( (q) => q.tracks.some((ab) => ab.id === this.id) ); } /** * The track duration in millisecond */ get durationMS() { const times = /* @__PURE__ */ __name((n, t) => { let tn = 1; for (let i = 0; i < t; i++) tn *= n; return t <= 0 ? 1e3 : tn * 1e3; }, "times"); return this.duration.split(":").reverse().map((m, i) => parseInt(m) * times(60, i)).reduce((a, c) => a + c, 0); } /** * Discord hyperlink representation of this track */ toHyperlink() { return `[${this.title}](${this.url})`; } /** * Returns source of this track */ get source() { return this.raw?.source ?? "arbitrary"; } /** * String representation of this track */ toString() { return `${this.title} by ${this.author}`; } /** * Raw JSON representation of this track */ toJSON(hidePlaylist) { return { id: this.id, title: this.title, description: this.description, author: this.author, url: this.url, thumbnail: this.thumbnail, duration: this.duration, durationMS: this.durationMS, views: this.views, requestedBy: this.requestedBy?.id || null, playlist: hidePlaylist ? null : this.playlist?.toJSON() ?? null }; } /** * Serialized track data that can be reconstructed */ serialize() { return { title: this.title, description: this.description, author: this.author, url: this.url, thumbnail: TypeUtil.isString(this.thumbnail) ? this.thumbnail : tryIntoThumbnailString(this.thumbnail), duration: this.duration, views: this.views ?? 0, requested_by: this.requestedBy?.toJSON() ?? null, source: this.source, live: false, query_type: this.queryType, extractor: this.extractor?.identifier ?? null, metadata: this.metadata, $type: "track" /* Track */, $encoder_version: this.player.version }; } /** * Construct a track from serialized data * @param player Player instance * @param data Serialized data */ static fromSerialized(player, data) { if (data.$type !== "track" /* Track */) throw new InvalidArgTypeError( "data", "SerializedTrack", "malformed data" ); const track = new _Track(player, { ...data, requestedBy: data.requested_by ? (() => { const res = data.requested_by; try { const resolved = player.client.users.resolve(res.id); if (resolved) return resolved; if (player.client.users.cache.has(res.id)) return player.client.users.cache.get(res.id); const user = new import_discord.User(player.client, res); return user; } catch { return null; } })() : null, queryType: data.query_type ?? void 0 }); track.setMetadata(data.metadata); return track; } /** * Get belonging queues of this track */ getBelongingQueues() { const nodes = this.player.nodes.cache.filter( (node) => node.tracks.some((t) => t.id === this.id) ); return nodes; } /** * Play this track to the given voice channel. If queue exists and another track is being played, this track will be added to the queue. * @param channel Voice channel on which this track shall be played * @param options Node initialization options */ async play(channel, options) { const fn = this.player.play.bind(this.player); return await fn(channel, this, options); } }; _onSeek = new WeakMap(); _resource = new WeakMap(); __name(_Track, "Track"); var Track = _Track; // src/fabric/Playlist.ts var _Playlist = class _Playlist { // eslint-disable-line @typescript-eslint/no-explicit-any /** * Playlist constructor * @param {Player} player The player * @param {PlaylistInitData} data The data */ constructor(player, data) { __publicField(this, "player"); __publicField(this, "tracks"); __publicField(this, "title"); __publicField(this, "description"); __publicField(this, "thumbnail"); __publicField(this, "type"); __publicField(this, "source"); __publicField(this, "author"); __publicField(this, "id"); __publicField(this, "url"); __publicField(this, "rawPlaylist"); this.player = player; this.tracks = data.tracks ?? []; this.author = data.author; this.description = data.description; this.thumbnail = data.thumbnail; this.type = data.type; this.source = data.source; this.id = data.id; this.url = data.url; this.title = data.title; } *[Symbol.iterator]() { yield* this.tracks; } /** * Estimated duration of this playlist */ get estimatedDuration() { return this.tracks.reduce((p, c) => p + c.durationMS, 0); } /** * Formatted estimated duration of this playlist */ get durationFormatted() { return Util.buildTimeCode(Util.parseMS(this.estimatedDuration)); } /** * JSON representation of this playlist * @param {boolean} [withTracks=true] If it should build json with tracks * @returns {PlaylistJSON} */ toJSON(withTracks = true) { const payload = { id: this.id, url: this.url, title: this.title, description: this.description, thumbnail: this.thumbnail, type: this.type, source: this.source, author: this.author, tracks: [] }; if (withTracks) payload.tracks = this.tracks.map((m) => m.toJSON(true)); return payload; } /** * Serialize this playlist into reconstructable data */ serialize() { return { tracks: this.tracks.map((m) => m.serialize()), title: this.title, description: this.description, thumbnail: TypeUtil.isString(this.thumbnail) ? this.thumbnail : tryIntoThumbnailString(this.thumbnail), type: this.type, source: this.source, author: this.author, id: this.id, url: this.url, $type: "playlist" /* Playlist */, $encoder_version: this.player.version }; } /** * Deserialize this playlist from serialized data * @param player Player instance * @param data Serialized data */ static fromSerialized(player, data) { if (data.$type !== "playlist" /* Playlist */) throw new InvalidArgTypeError( "data", "SerializedPlaylist", "malformed data" ); return new _Playlist(player, { ...data, tracks: data.tracks.map((m) => Track.fromSerialized(player, m)) }); } /** * Play this playlist to the given voice channel. If queue exists and another track is being played, this playlist will be added to the queue. * @param channel Voice channel on which this playlist shall be played * @param options Node initialization options */ async play(channel, options) { const fn = this.player.play.bind(this.player); return await fn(channel, this, options); } }; __name(_Playlist, "Playlist"); var Playlist = _Playlist; // src/utils/QueryResolver.ts var import_undici = require("undici"); var spotifySongRegex = /^https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(intl-([a-z]|[A-Z])+\/)?(?:track\/|\?uri=spotify:track:)((\w|-){22})(\?si=.+)?$/; var spotifyPlaylistRegex = /^https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(intl-([a-z]|[A-Z])+\/)?(?:playlist\/|\?uri=spotify:playlist:)((\w|-){22})(\?si=.+)?$/; var spotifyAlbumRegex = /^https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(intl-([a-z]|[A-Z])+\/)?(?:album\/|\?uri=spotify:album:)((\w|-){22})(\?si=.+)?$/; var vimeoRegex = /^(http|https)?:\/\/(www\.|player\.)?vimeo\.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|video\/|)(\d+)(?:|\/\?)$/; var reverbnationRegex = /^https:\/\/(www.)?reverbnation.com\/(.+)\/song\/(.+)$/; var attachmentRegex = /^https?:\/\/.+$/; var appleMusicSongRegex = /^https?:\/\/music\.apple\.com\/.+?\/(song|album)\/.+?(\/.+?\?i=|\/)([0-9]+)$/; var appleMusicPlaylistRegex = /^https?:\/\/music\.apple\.com\/.+?\/playlist\/.+\/pl\.(u-|pm-)?[a-zA-Z0-9]+$/; var appleMusicAlbumRegex = /^https?:\/\/music\.apple\.com\/.+?\/album\/.+\/([0-9]+)$/; var soundcloudTrackRegex = /^https?:\/\/(m.|www.)?soundcloud.com\/(\w|-)+\/(\w|-)+(.+)?$/; var soundcloudPlaylistRegex = /^https?:\/\/(m.|www.)?soundcloud.com\/(\w|-)+\/sets\/(\w|-)+(.+)?$/; var youtubePlaylistRegex = /^https?:\/\/(www.)?youtube.com\/playlist\?list=((PL|FL|UU|LL|RD|OL)[a-zA-Z0-9-_]{16,41})$/; var youtubeVideoURLRegex = /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w-]+\?v=|embed\/|v\/)?)([\w-]+)(\S+)?$/; var youtubeVideoIdRegex = /^[a-zA-Z0-9-_]{11}$/; var discordPlayerBlobRegex = /^discord-player:\/\/blob\/\d+$/; var DomainsMap = { DiscordPlayer: ["discord-player"], YouTube: [ "youtube.com", "youtu.be", "music.youtube.com", "gaming.youtube.com", "www.youtube.com", "m.youtube.com" ], Spotify: ["open.spotify.com", "embed.spotify.com"], Vimeo: ["vimeo.com", "player.vimeo.com"], ReverbNation: ["reverbnation.com"], SoundCloud: ["soundcloud.com"], AppleMusic: ["music.apple.com"] }; var redirectDomains = /* @__PURE__ */ new Set( [ /^https?:\/\/spotify.link\/[A-Za-z0-9]+$/, /^https:\/\/on\.soundcloud\.com\/[a-zA-Z1-9]{0,17}$/ ] ); var QueryType = { AUTO: "auto", YOUTUBE: "youtube", YOUTUBE_PLAYLIST: "youtubePlaylist", SOUNDCLOUD_TRACK: "soundcloudTrack", SOUNDCLOUD_PLAYLIST: "soundcloudPlaylist", SOUNDCLOUD: "soundcloud", SPOTIFY_SONG: "spotifySong", SPOTIFY_ALBUM: "spotifyAlbum", SPOTIFY_PLAYLIST: "spotifyPlaylist", SPOTIFY_SEARCH: "spotifySearch", FACEBOOK: "facebook", VIMEO: "vimeo", ARBITRARY: "arbitrary", REVERBNATION: "reverbnation", YOUTUBE_SEARCH: "youtubeSearch", YOUTUBE_VIDEO: "youtubeVideo", SOUNDCLOUD_SEARCH: "soundcloudSearch", APPLE_MUSIC_SONG: "appleMusicSong", APPLE_MUSIC_ALBUM: "appleMusicAlbum", APPLE_MUSIC_PLAYLIST: "appleMusicPlaylist", APPLE_MUSIC_SEARCH: "appleMusicSearch", FILE: "file", AUTO_SEARCH: "autoSearch", DISCORD_PLAYER_BLOB: "discordPlayerBlob" }; var _QueryResolver = class _QueryResolver { /** * Query resolver */ constructor() { } // eslint-disable-line @typescript-eslint/no-empty-function static get regex() { return { spotifyAlbumRegex, spotifyPlaylistRegex, spotifySongRegex, vimeoRegex, reverbnationRegex, attachmentRegex, appleMusicAlbumRegex, appleMusicPlaylistRegex, appleMusicSongRegex, soundcloudTrackRegex, soundcloudPlaylistRegex, youtubePlaylistRegex, discordPlayerBlobRegex }; } /** * Pre-resolve redirect urls */ static async preResolve(query, maxDepth = 5) { if (!TypeUtil.isString(query)) throw new InvalidArgTypeError(query, "string", typeof query); for (const domain of redirectDomains) { if (domain.test(query)) { try { const res = await (0, import_undici.fetch)(query, { method: "GET", redirect: "follow" }); if (!res.ok) break; if (/^https?:\/\/spotify.app.link\/(.+)$/.test(res.url)) { const body = await res.text(); const target = body.split("https://open.spotify.com/track/")[1].split("?si=")[0]; if (!target) break; return `https://open.spotify.com/track/${target}`; } return maxDepth < 1 ? res.url : this.preResolve(res.url, maxDepth - 1); } catch { break; } } } return query; } /** * Resolves the given search query * @param {string} query The query */ static resolve(query, fallbackSearchEngine = QueryType.AUTO_SEARCH) { if (!TypeUtil.isString(query)) throw new InvalidArgTypeError(query, "string", typeof query); if (!query.length) throw new InfoRequiredError("query", String(query)); const resolver = /* @__PURE__ */ __name((type, query2) => ({ type, query: query2 }), "resolver"); if (discordPlayerBlobRegex.test(query)) return resolver(QueryType.DISCORD_PLAYER_BLOB, query); try { const url = new URL(query); if (DomainsMap.YouTube.includes(url.host)) { query = query.replace(/(m(usic)?|gaming)\./, "").trim(); const playlistId = url.searchParams.get("list"); if (playlistId) return resolver( QueryType.YOUTUBE_PLAYLIST, `https://www.youtube.com/${url.searchParams.size === 1 ? "playlist" : "watch"}${url.search}` ); if (_QueryResolver.validateId(query) || _QueryResolver.validateURL(query)) return resolver(QueryType.YOUTUBE_VIDEO, query); return resolver(fallbackSearchEngine, query); } else if (DomainsMap.Spotify.includes(url.host)) { query = query.replace(/intl-([a-zA-Z]+)\//, ""); if (spotifyPlaylistRegex.test(query)) return resolver(QueryType.SPOTIFY_PLAYLIST, query); if (spotifyAlbumRegex.test(query)) return resolver(QueryType.SPOTIFY_ALBUM, query); if (spotifySongRegex.test(query)) return resolver(QueryType.SPOTIFY_SONG, query); return resolver(fallbackSearchEngine, query); } else if (DomainsMap.Vimeo.includes(url.host)) { if (vimeoRegex.test(query)) return resolver(QueryType.VIMEO, query); return resolver(fallbackSearchEngine, query); } else if (DomainsMap.ReverbNation.includes(url.host)) { if (reverbnationRegex.test(query)) return resolver(QueryType.REVERBNATION, query); return resolver(fallbackSearchEngine, query); } else if (DomainsMap.SoundCloud.includes(url.host)) { if (soundcloudPlaylistRegex.test(query)) return resolver(QueryType.SOUNDCLOUD_PLAYLIST, query); if (soundcloudTrackRegex.test(query)) return resolver(QueryType.SOUNDCLOUD_TRACK, query); return resolver(fallbackSearchEngine, query); } else if (DomainsMap.AppleMusic.includes(url.host)) { if (appleMusicAlbumRegex.test(query)) return resolver(QueryType.APPLE_MUSIC_ALBUM, query); if (appleMusicPlaylistRegex.test(query)) return resolver(QueryType.APPLE_MUSIC_PLAYLIST, query); if (appleMusicSongRegex.test(query)) return resolver(QueryType.APPLE_MUSIC_SONG, query); return resolver(fallbackSearchEngine, query); } else { return resolver(QueryType.ARBITRARY, query); } } catch { return resolver(fallbackSearchEngine, query); } } /** * Parses vimeo id from url * @param {string} query The query * @returns {string} */ static getVimeoID(query) { return _QueryResolver.resolve(query).type === QueryType.VIMEO ? query.split("/").filter(Boolean).pop() : null; } static validateId(q) { return youtubeVideoIdRegex.test(q); } static validateURL(q) { return youtubeVideoURLRegex.test(q); } }; __name(_QueryResolver, "QueryResolver"); var QueryResolver = _QueryResolver; // src/fabric/SearchResult.ts var _SearchResult = class _SearchResult { constructor(player, _data) { this.player = player; this._data = _data; this._data.tracks?.forEach((track) => { track.extractor ?? (track.extractor = this._data.extractor || null); track.requestedBy ?? (track.requestedBy = _data.requestedBy || null); }); } setQueryType(type) { this._data.queryType = type; return this; } setRequestedBy(user) { this._data.requestedBy = user; this._data.tracks?.forEach((track) => { track.requestedBy = user; }); return this; } setExtractor(extractor) { this._data.extractor = extractor; this._data.tracks?.forEach((track) => { track.extractor = extractor; }); return this; } setTracks(tracks) { this._data.tracks = tracks; return this; } setQuery(query) { this._data.query = query; return this; } setPlaylist(playlist) { this._data.playlist = playlist; return this; } /** * The search query */ get query() { return this._data.query; } /** * The search query type */ get queryType() { return this._data.queryType || QueryType.AUTO; } /** * The extractor */ get extractor() { return this._data.extractor || null; } /** * Playlist result */ get playlist() { return this._data.playlist; } /** * Tracks result */ get tracks() { return this._data.tracks || []; } /** * Requested by */ get requestedBy() { return this._data.requestedBy || null; } /** * Re-execute this search */ async execute() { return this.player.search(this.query, { searchEngine: this.queryType, requestedBy: this.requestedBy }); } /** * If this search result is empty */ isEmpty() { return !this.tracks.length; } /** * If this search result has playlist */ hasPlaylist() { return this.playlist != null; } /** * If this search result has tracks */ hasTracks() { return this.tracks.length > 0; } /** * JSON representation of this search */ toJSON() { return { query: this.query, queryType: this.queryType, playlist: this.playlist?.toJSON(false) || null, tracks: this.tracks.map((m) => m.toJSON(true)), extractor: this.extractor?.identifier || null, requestedBy: this.requestedBy?.toJSON() || null }; } }; __name(_SearchResult, "SearchResult"); var SearchResult = _SearchResult; // src/utils/AudioFilters.ts var bass = /* @__PURE__ */ __name((g) => `bass=g=${g}:f=110:w=0.3`, "bass"); var _AudioFilters = class _AudioFilters { constructor() { return _AudioFilters; } static get(name) { return this.filters[name] ?? name; } static has(name) { return name in this.filters; } static *[Symbol.iterator]() { for (const [k, v] of Object.entries(this.filters)) { yield { name: k, value: v }; } } static get names() { return Object.keys(this.filters); } // @ts-ignore static get length() { return this.names.length; } static toString() { return this.names.map((m) => this[m]).join(","); } /** * Create ffmpeg args from the specified filters name * @param filter The filter name * @returns */ static create(filters) { if (!filters || !Array.isArray(filters)) return this.toString(); return filters.filter((predicate) => typeof predicate === "string").map((m) => this.get(m)).join(","); } /** * Defines audio filter * @param filterName The name of the filter * @param value The ffmpeg args */ static define(filterName, value) { this.filters[filterName] = value; } /** * Defines multiple audio filters * @param filtersArray Array of filters containing the filter name and ffmpeg args */ static defineBulk(filtersArray) { filtersArray.forEach((arr) => this.define(arr.name, arr.value)); } }; __name(_AudioFilters, "AudioFilters"); __publicField(_AudioFilters, "filters", { bassboost_low: bass(15), bassboost: bass(20), bassboost_high: bass(30), "8D": "apulsator=hz=0.09", vaporwave: "aresample=48000,asetrate=48000*0.8", nightcore: "aresample=48000,asetrate=48000*1.25", lofi: "aresample=48000,asetrate=48000*0.9,extrastereo=m=2.5:c=disabled", phaser: "aphaser=in_gain=0.4", tremolo: "tremolo", vibrato: "vibrato=f=6.5", reverse: "areverse", treble: "treble=g=5", normalizer2: "dynaudnorm=g=101", normalizer: "acompressor", surrounding: "surround", pulsator: "apulsator=hz=1", subboost: "asubboost", karaoke: "stereotools=mlev=0.03", flanger: "flanger", gate: "agate", haas: "haas", mcompand: "mcompand", mono: "pan=mono|c0=.5*c0+.5*c1", mstlr: "stereotools=mode=ms>lr", mstrr: "stereotools=mode=ms>rr", compressor: "compand=points=-80/-105|-62/-80|-15.4/-15.4|0/-12|20/-7.6", expander: "compand=attacks=0:points=-80/-169|-54/-80|-49.5/-64.6|-41.1/-41.1|-25.8/-15|-10.8/-4.5|0/0|20/8.3", softlimiter: "compand=attacks=0:points=-80/-80|-12.4/-12.4|-6/-8|0/-6.8|20/-2.8", chorus: "chorus=0.7:0.9:55:0.4:0.25:2", chorus2d: "chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3", chorus3d: "chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3", fadein: "afade=t=in:ss=0:d=10", dim: `afftfilt="'real=re * (1-clip((b/nb)*b,0,1))':imag='im * (1-clip((b/nb)*b,0,1))'"`, earrape: "channelsplit,sidechaingate=level_in=64", silenceremove: "silenceremove=1:0:-50dB" }); var AudioFilters = _AudioFilters; // src/utils/Util.ts var import_node_crypto = require("crypto"); var import_metadata_filter = require("@web-scrobbler/metadata-filter"); var _Util = class _Util { /** * Utils */ constructor() { } // eslint-disable-line @typescript-eslint/no-empty-function /** * Gets the runtime information */ static getRuntime() { const version2 = typeof navigator !== "undefined" ? navigator.userAgent : null; if (typeof Deno !== "undefined" && Deno.version) { return { name: "deno", version: Deno.version.deno }; } if (typeof Bun !== "undefined" && Bun.version) { return { name: "bun", version: Bun.version }; } if (typeof process !== "undefined" && process.version) return { name: "node", version: process.version }; return { name: "unknown", version: version2 ?? "unknown" }; } /** * Creates duration string * @param {object} durObj The duration object * @returns {string} */ static durationString(durObj) { return Object.values(durObj).map((m) => isNaN(m) ? 0 : m).join(":"); } /** * Parses milliseconds to consumable time object * @param {number} milliseconds The time in ms * @returns {TimeData} */ static parseMS(milliseconds) { if (isNaN(milliseconds)) milliseconds = 0; const round = milliseconds > 0 ? Math.floor : Math.ceil; return { days: round(milliseconds / 864e5), hours: round(milliseconds / 36e5) % 24, minutes: round(milliseconds / 6e4) % 60, seconds: round(milliseconds / 1e3) % 60 }; } /** * Builds time code * @param {TimeData} duration The duration object * @returns {string} */ static buildTimeCode(duration) { const items = Object.keys(duration); const required = ["days", "hours", "minutes", "seconds"]; const parsed = items.filter((x) => required.includes(x)).map((m) => duration[m]); const final = parsed.slice(parsed.findIndex((x) => x !== 0)).map((x) => x.toString().padStart(2, "0")).join(":"); return final.length <= 3 ? `0:${final.padStart(2, "0") || 0}` : final; } /** * Formats duration * @param {number} duration The duration in ms */ static formatDuration(duration) { return this.buildTimeCode(this.parseMS(duration)); } /** * Picks last item of the given array * @param {any[]} arr The array * @returns {any} */ // eslint-disable-next-line @typescript-eslint/no-explicit-any static last(arr) { if (!Array.isArray(arr)) return arr; return arr[arr.length - 1]; } /** * Checks if the voice channel is empty * @param {VoiceChannel|StageChannel} channel The voice channel * @returns {boolean} */ static isVoiceEmpty(channel) { return channel && channel.members.filter((member) => !member.user.bot).size === 0; } /** * Cleans the track title * @param title The title * @param source The source * @returns Cleaned title */ static cleanTitle(title, source) { try { const filterOpts = { // prettier-ignore track: [ import_metadata_filter.removeRemastered, import_metadata_filter.removeLive, import_metadata_filter.fixTrackSuffix, import_metadata_filter.removeZeroWidth, import_metadata_filter.replaceNbsp, import_metadata_filter.replaceSmartQuotes, import_metadata_filter.removeCleanExplicit ] }; const spotifyFilter = (0, import_metadata_filter.createFilter)(filterOpts); spotifyFilter.extend((0, import_metadata_filter.createSpotifyFilter)()); const defaultFilter = (0, import_metadata_filter.createFilter)(filterOpts); switch (source) { case "youtube": return (0, import_metadata_filter.youtube)(title); case "spotify": return spotifyFilter.filterField("track", title); default: return defaultFilter.filterField("track", title); } } catch { return title; } } /** * Safer require * @param {string} id Node require id * @returns {any} */ static require(id) { try { return { module: require(id), error: null }; } catch (error) { return { module: null, error }; } } static async import(id) { try { const mod = await import(id); return { module: mod, error: null }; } catch (error) { return { module: null, error }; } } /** * Asynchronous timeout * @param {number} time The time in ms to wait * @returns {Promise<unknown>} */ static wait(time) { return (0, import_promises.setTimeout)(time, void 0, { ref: false }); } static noop() { } // eslint-disable-line @typescript-eslint/no-empty-function static async getFetch() { if ("fetch" in globalThis) return globalThis.fetch; for (const lib of ["node-fetch", "undici"]) { try { return await import(lib).then( (res) => res.fetch || res.default?.fetch || res.default ); } catch { try { const res = require(lib); if (res) return res.fetch || res.default?.fetch || res.default; } catch { } } } } static warn(message, code = "DeprecationWarning", detail) { process.emitWarning(message, { code, detail }); } static randomChoice(src) { return src[(0, import_node_crypto.randomInt)(src.length)]; } static arrayCloneShuffle(src) { const arr = src.slice(); let m = arr.length; while (m) { const i = Math.floor(Math.random() * m--); [arr[m], arr[i]] = [arr[i], arr[m]]; } return arr; } }; __name(_Util, "Util"); var Util = _Util; var VALIDATE_QUEUE_CAP = /* @__PURE__ */ __name((queue, items) => { if (queue.maxSize < 1 || queue.maxSize === Infinity) return; const tracks = typeof items === "number" ? items : (items instanceof Playlist ? items.tracks : Array.isArray(items) ? items : [items]).length; const maxCap = queue.getCapacity(); if (maxCap < tracks) { throw new OutOfSpaceError("tracks queue", maxCap, tracks); } }, "VALIDATE_QUEUE_CAP"); // src/compat/createErisCompat.ts var DiscordPlayerClientSymbol2 = Symbol("DiscordPlayerClient"); function declareProperty(target, key, value) { Reflect.set(target, key, value); } __name(declareProperty, "declareProperty"); function getProperty(target, key) { return Reflect.get(target, key); } __name(getProperty, "getProperty"); function createErisCompat(client) { const { module: module2, error } = Util.require("eris"); if (error) throw error; const eris = module2; erisVoiceEventsHandler(client); const erisProxy = new Proxy(client, { get(target, p) { switch (p) { case "users": return erisUsersProxy(target, eris); case "guilds": return erisGuildsProxy(target, eris); case "channels": return erisChannelsProxy(target, eris); case "__dp_voiceStateUpdate_proxy": return (handler) => erisVoiceStateUpdateProxy(target, erisProxy, handler); case "incrementMaxListeners": return () => { client.setMaxListeners(client.getMaxListeners() + 1); }; case "decrementMaxListeners": return () => { const listeners = client.getMaxListeners() - 1; client.setMaxListeners(listeners < 0 ? 1 : listeners); }; default: return target[p]; } } }); Reflect.set(erisProxy, DiscordPlayerClientSymbol2, "Eris"); return createCompatClient(erisProxy, "Eris").client; } __name(createErisCompat, "createErisCompat"); function erisVoiceStateUpdateProxy(client, proxy, handler) { client.on("voiceStateUpdate", (member, oldState) => { try { const proxiedOldState = { channelId: oldState.channelID, serverMute: oldState.mute, suppress: oldState.suppress, guild: { id: oldState.guild.id }, member: { id: oldState.user.id } }; const me = member.guild.members.get(client.user.id); const resolvedChannel = member.guild.channels.get( member.voiceState.channelID ); const proxiedNewState = { channelId: member.voiceState.channelID, serverMute: member.voiceState.mute, suppress: member.voiceState.suppress, channel: erisResolvedChannelProxy(resolvedChannel, client), member: { id: member.id }, guild: { id: member.guild.id, members: { me: { id: me?.id, voice: { async setRequestToSpeak(value) { return me?.voiceState; } } } } } }; return handler(proxiedNewState, proxiedOldState); } catch { } }); } __name(erisVoiceStateUpdateProxy, "erisVoiceStateUpdateProxy"); function erisVoiceEventsHandler(client) { let adapters = getProperty(client, "adapters"); if (!adapters) { const collection = /* @__PURE__ */ new Map(); adapters = collection; declareProperty(client, "adapters", collection); } client.on("shardDisconnect", (_, shardId) => { for (const [guildId, adapter] of adapters.entries()) { if (client.guilds.get(guildId)?.shard.id === shardId) { adapter.destroy(); } } }); client.on("rawWS", (packet) => { switch (packet.t) { case import_v10.GatewayDispatchEvents.VoiceServerUpdate: { const payload = packet.d; adapters.get(payload.guild_id)?.onVoiceServerUpdate(payload); return; } case import_v10.GatewayDispatchEvents.VoiceStateUpdate: { const payload = packet.d; if (payload.guild_id