UNPKG

lavalink-client

Version:

Easy, flexible and feature-rich lavalink@v4 Client. Both for Beginners and Proficients. - Supports NodeLink@v3 too.

1,063 lines (1,056 loc) 296 kB
// src/structures/LavalinkManager.ts import { EventEmitter as EventEmitter2 } from "events"; // src/structures/Constants.ts var DebugEvents = /* @__PURE__ */ ((DebugEvents2) => { DebugEvents2["SetSponsorBlock"] = "SetSponsorBlock"; DebugEvents2["DeleteSponsorBlock"] = "DeleteSponsorBlock"; DebugEvents2["TrackEndReplaced"] = "TrackEndReplaced"; DebugEvents2["AutoplayExecution"] = "AutoplayExecution"; DebugEvents2["AutoplayNoSongsAdded"] = "AutoplayNoSongsAdded"; DebugEvents2["AutoplayThresholdSpamLimiter"] = "AutoplayThresholdSpamLimiter"; DebugEvents2["TriggerQueueEmptyInterval"] = "TriggerQueueEmptyInterval"; DebugEvents2["QueueEnded"] = "QueueEnded"; DebugEvents2["TrackStartNewSongsOnly"] = "TrackStartNewSongsOnly"; DebugEvents2["TrackStartNoTrack"] = "TrackStartNoTrack"; DebugEvents2["ResumingFetchingError"] = "ResumingFetchingError"; DebugEvents2["PlayerUpdateNoPlayer"] = "PlayerUpdateNoPlayer"; DebugEvents2["PlayerUpdateFilterFixApply"] = "PlayerUpdateFilterFixApply"; DebugEvents2["PlayerUpdateSuccess"] = "PlayerUpdateSuccess"; DebugEvents2["HeartBeatTriggered"] = "HeartBeatTriggered"; DebugEvents2["NoSocketOnDestroy"] = "NoSocketOnDestroy"; DebugEvents2["SocketCleanupError"] = "SocketCleanupError"; DebugEvents2["SocketTerminateHeartBeatTimeout"] = "SocketTerminateHeartBeatTimeout"; DebugEvents2["TryingConnectWhileConnected"] = "TryingConnectWhileConnected"; DebugEvents2["LavaSearchNothingFound"] = "LavaSearchNothingFound"; DebugEvents2["SearchNothingFound"] = "SearchNothingFound"; DebugEvents2["ValidatingBlacklistLinks"] = "ValidatingBlacklistLinks"; DebugEvents2["ValidatingWhitelistLinks"] = "ValidatingWhitelistLinks"; DebugEvents2["TrackErrorMaxTracksErroredPerTime"] = "TrackErrorMaxTracksErroredPerTime"; DebugEvents2["TrackStuckMaxTracksErroredPerTime"] = "TrackStuckMaxTracksErroredPerTime"; DebugEvents2["PlayerDestroyingSomewhereElse"] = "PlayerDestroyingSomewhereElse"; DebugEvents2["PlayerCreateNodeNotFound"] = "PlayerCreateNodeNotFound"; DebugEvents2["PlayerPlayQueueEmptyTimeoutClear"] = "PlayerPlayQueueEmptyTimeoutClear"; DebugEvents2["PlayerPlayWithTrackReplace"] = "PlayerPlayWithTrackReplace"; DebugEvents2["PlayerPlayUnresolvedTrack"] = "PlayerPlayUnresolvedTrack"; DebugEvents2["PlayerPlayUnresolvedTrackFailed"] = "PlayerPlayUnresolvedTrackFailed"; DebugEvents2["PlayerVolumeAsFilter"] = "PlayerVolumeAsFilter"; DebugEvents2["BandcampSearchLokalEngine"] = "BandcampSearchLokalEngine"; DebugEvents2["PlayerChangeNode"] = "PlayerChangeNode"; DebugEvents2["BuildTrackError"] = "BuildTrackError"; DebugEvents2["TransformRequesterFunctionFailed"] = "TransformRequesterFunctionFailed"; DebugEvents2["GetClosestTrackFailed"] = "GetClosestTrackFailed"; DebugEvents2["PlayerDeleteInsteadOfDestroy"] = "PlayerDeleteInsteadOfDestroy"; DebugEvents2["FailedToConnectToNodes"] = "FailedToConnectToNodes"; DebugEvents2["NoAudioDebug"] = "NoAudioDebug"; DebugEvents2["PlayerAutoReconnect"] = "PlayerAutoReconnect"; DebugEvents2["PlayerDestroyFail"] = "PlayerDestroyFail"; DebugEvents2["PlayerChangeNodeFailNoEligibleNode"] = "PlayerChangeNodeFailNoEligibleNode"; DebugEvents2["PlayerChangeNodeFail"] = "PlayerChangeNodeFail"; return DebugEvents2; })(DebugEvents || {}); var DestroyReasons = /* @__PURE__ */ ((DestroyReasons2) => { DestroyReasons2["QueueEmpty"] = "QueueEmpty"; DestroyReasons2["NodeDestroy"] = "NodeDestroy"; DestroyReasons2["NodeDeleted"] = "NodeDeleted"; DestroyReasons2["LavalinkNoVoice"] = "LavalinkNoVoice"; DestroyReasons2["NodeReconnectFail"] = "NodeReconnectFail"; DestroyReasons2["Disconnected"] = "Disconnected"; DestroyReasons2["PlayerReconnectFail"] = "PlayerReconnectFail"; DestroyReasons2["PlayerChangeNodeFail"] = "PlayerChangeNodeFail"; DestroyReasons2["PlayerChangeNodeFailNoEligibleNode"] = "PlayerChangeNodeFailNoEligibleNode"; DestroyReasons2["ChannelDeleted"] = "ChannelDeleted"; DestroyReasons2["DisconnectAllNodes"] = "DisconnectAllNodes"; DestroyReasons2["ReconnectAllNodes"] = "ReconnectAllNodes"; DestroyReasons2["TrackErrorMaxTracksErroredPerTime"] = "TrackErrorMaxTracksErroredPerTime"; DestroyReasons2["TrackStuckMaxTracksErroredPerTime"] = "TrackStuckMaxTracksErroredPerTime"; return DestroyReasons2; })(DestroyReasons || {}); var DisconnectReasons = /* @__PURE__ */ ((DisconnectReasons2) => { DisconnectReasons2["Disconnected"] = "Disconnected"; DisconnectReasons2["DisconnectAllNodes"] = "DisconnectAllNodes"; return DisconnectReasons2; })(DisconnectReasons || {}); var validSponsorBlocks = [ "sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "filler" ]; var audioOutputsData = { mono: { // totalLeft: 1, totalRight: 1 leftToLeft: 0.5, //each channel should in total 0 | 1, 0 === off, 1 === on, 0.5+0.5 === 1 leftToRight: 0.5, rightToLeft: 0.5, rightToRight: 0.5 }, stereo: { // totalLeft: 1, totalRight: 1 leftToLeft: 1, leftToRight: 0, rightToLeft: 0, rightToRight: 1 }, left: { // totalLeft: 1, totalRight: 0 leftToLeft: 1, leftToRight: 0, rightToLeft: 1, rightToRight: 0 }, right: { // totalLeft: 0, totalRight: 1 leftToLeft: 0, leftToRight: 1, rightToLeft: 0, rightToRight: 1 } }; var EQList = { /** A Bassboost Equalizer, so high it distorts the audio */ BassboostEarrape: [ { band: 0, gain: 0.6 * 0.375 }, { band: 1, gain: 0.67 * 0.375 }, { band: 2, gain: 0.67 * 0.375 }, { band: 3, gain: 0.4 * 0.375 }, { band: 4, gain: -0.5 * 0.375 }, { band: 5, gain: 0.15 * 0.375 }, { band: 6, gain: -0.45 * 0.375 }, { band: 7, gain: 0.23 * 0.375 }, { band: 8, gain: 0.35 * 0.375 }, { band: 9, gain: 0.45 * 0.375 }, { band: 10, gain: 0.55 * 0.375 }, { band: 11, gain: -0.6 * 0.375 }, { band: 12, gain: 0.55 * 0.375 }, { band: 13, gain: -0.5 * 0.375 }, { band: 14, gain: -0.75 * 0.375 } ], /** A High and decent Bassboost Equalizer */ BassboostHigh: [ { band: 0, gain: 0.6 * 0.25 }, { band: 1, gain: 0.67 * 0.25 }, { band: 2, gain: 0.67 * 0.25 }, { band: 3, gain: 0.4 * 0.25 }, { band: 4, gain: -0.5 * 0.25 }, { band: 5, gain: 0.15 * 0.25 }, { band: 6, gain: -0.45 * 0.25 }, { band: 7, gain: 0.23 * 0.25 }, { band: 8, gain: 0.35 * 0.25 }, { band: 9, gain: 0.45 * 0.25 }, { band: 10, gain: 0.55 * 0.25 }, { band: 11, gain: -0.6 * 0.25 }, { band: 12, gain: 0.55 * 0.25 }, { band: 13, gain: -0.5 * 0.25 }, { band: 14, gain: -0.75 * 0.25 } ], /** A decent Bassboost Equalizer */ BassboostMedium: [ { band: 0, gain: 0.6 * 0.1875 }, { band: 1, gain: 0.67 * 0.1875 }, { band: 2, gain: 0.67 * 0.1875 }, { band: 3, gain: 0.4 * 0.1875 }, { band: 4, gain: -0.5 * 0.1875 }, { band: 5, gain: 0.15 * 0.1875 }, { band: 6, gain: -0.45 * 0.1875 }, { band: 7, gain: 0.23 * 0.1875 }, { band: 8, gain: 0.35 * 0.1875 }, { band: 9, gain: 0.45 * 0.1875 }, { band: 10, gain: 0.55 * 0.1875 }, { band: 11, gain: -0.6 * 0.1875 }, { band: 12, gain: 0.55 * 0.1875 }, { band: 13, gain: -0.5 * 0.1875 }, { band: 14, gain: -0.75 * 0.1875 } ], /** A slight Bassboost Equalizer */ BassboostLow: [ { band: 0, gain: 0.6 * 0.125 }, { band: 1, gain: 0.67 * 0.125 }, { band: 2, gain: 0.67 * 0.125 }, { band: 3, gain: 0.4 * 0.125 }, { band: 4, gain: -0.5 * 0.125 }, { band: 5, gain: 0.15 * 0.125 }, { band: 6, gain: -0.45 * 0.125 }, { band: 7, gain: 0.23 * 0.125 }, { band: 8, gain: 0.35 * 0.125 }, { band: 9, gain: 0.45 * 0.125 }, { band: 10, gain: 0.55 * 0.125 }, { band: 11, gain: -0.6 * 0.125 }, { band: 12, gain: 0.55 * 0.125 }, { band: 13, gain: -0.5 * 0.125 }, { band: 14, gain: -0.75 * 0.125 } ], /** Makes the Music slightly "better" */ BetterMusic: [ { band: 0, gain: 0.25 }, { band: 1, gain: 0.025 }, { band: 2, gain: 0.0125 }, { band: 3, gain: 0 }, { band: 4, gain: 0 }, { band: 5, gain: -0.0125 }, { band: 6, gain: -0.025 }, { band: 7, gain: -0.0175 }, { band: 8, gain: 0 }, { band: 9, gain: 0 }, { band: 10, gain: 0.0125 }, { band: 11, gain: 0.025 }, { band: 12, gain: 0.25 }, { band: 13, gain: 0.125 }, { band: 14, gain: 0.125 } ], /** Makes the Music sound like rock music / sound rock music better */ Rock: [ { band: 0, gain: 0.3 }, { band: 1, gain: 0.25 }, { band: 2, gain: 0.2 }, { band: 3, gain: 0.1 }, { band: 4, gain: 0.05 }, { band: 5, gain: -0.05 }, { band: 6, gain: -0.15 }, { band: 7, gain: -0.2 }, { band: 8, gain: -0.1 }, { band: 9, gain: -0.05 }, { band: 10, gain: 0.05 }, { band: 11, gain: 0.1 }, { band: 12, gain: 0.2 }, { band: 13, gain: 0.25 }, { band: 14, gain: 0.3 } ], /** Makes the Music sound like Classic music / sound Classic music better */ Classic: [ { band: 0, gain: 0.375 }, { band: 1, gain: 0.35 }, { band: 2, gain: 0.125 }, { band: 3, gain: 0 }, { band: 4, gain: 0 }, { band: 5, gain: 0.125 }, { band: 6, gain: 0.55 }, { band: 7, gain: 0.05 }, { band: 8, gain: 0.125 }, { band: 9, gain: 0.25 }, { band: 10, gain: 0.2 }, { band: 11, gain: 0.25 }, { band: 12, gain: 0.3 }, { band: 13, gain: 0.25 }, { band: 14, gain: 0.3 } ], /** Makes the Music sound like Pop music / sound Pop music better */ Pop: [ { band: 0, gain: 0.2635 }, { band: 1, gain: 0.22141 }, { band: 2, gain: -0.21141 }, { band: 3, gain: -0.1851 }, { band: 4, gain: -0.155 }, { band: 5, gain: 0.21141 }, { band: 6, gain: 0.22456 }, { band: 7, gain: 0.237 }, { band: 8, gain: 0.237 }, { band: 9, gain: 0.237 }, { band: 10, gain: -0.05 }, { band: 11, gain: -0.116 }, { band: 12, gain: 0.192 }, { band: 13, gain: 0 } ], /** Makes the Music sound like Electronic music / sound Electronic music better */ Electronic: [ { band: 0, gain: 0.375 }, { band: 1, gain: 0.35 }, { band: 2, gain: 0.125 }, { band: 3, gain: 0 }, { band: 4, gain: 0 }, { band: 5, gain: -0.125 }, { band: 6, gain: -0.125 }, { band: 7, gain: 0 }, { band: 8, gain: 0.25 }, { band: 9, gain: 0.125 }, { band: 10, gain: 0.15 }, { band: 11, gain: 0.2 }, { band: 12, gain: 0.25 }, { band: 13, gain: 0.35 }, { band: 14, gain: 0.4 } ], /** Boosts all Bands slightly for louder and fuller sound */ FullSound: [ { band: 0, gain: 0.25 + 0.375 }, { band: 1, gain: 0.25 + 0.025 }, { band: 2, gain: 0.25 + 0.0125 }, { band: 3, gain: 0.25 + 0 }, { band: 4, gain: 0.25 + 0 }, { band: 5, gain: 0.25 + -0.0125 }, { band: 6, gain: 0.25 + -0.025 }, { band: 7, gain: 0.25 + -0.0175 }, { band: 8, gain: 0.25 + 0 }, { band: 9, gain: 0.25 + 0 }, { band: 10, gain: 0.25 + 0.0125 }, { band: 11, gain: 0.25 + 0.025 }, { band: 12, gain: 0.25 + 0.375 }, { band: 13, gain: 0.25 + 0.125 }, { band: 14, gain: 0.25 + 0.125 } ], /** Boosts basses + lower highs for a pro gaming sound */ Gaming: [ { band: 0, gain: 0.35 }, { band: 1, gain: 0.3 }, { band: 2, gain: 0.25 }, { band: 3, gain: 0.2 }, { band: 4, gain: 0.15 }, { band: 5, gain: 0.1 }, { band: 6, gain: 0.05 }, { band: 7, gain: -0 }, { band: 8, gain: -0.05 }, { band: 9, gain: -0.1 }, { band: 10, gain: -0.15 }, { band: 11, gain: -0.2 }, { band: 12, gain: -0.25 }, { band: 13, gain: -0.3 }, { band: 14, gain: -0.35 } ] }; var RecommendationsStrings = { highCPULoad: (cpuLoad) => `High CPU load (${(cpuLoad * 100).toFixed(1)}%). Consider reducing player count or upgrading CPU.`, highSystemLoad: (systemLoad) => `High system load (${(systemLoad * 100).toFixed(1)}%). Check other processes on the server.`, highMemoryUsage: (memoryUsagePercent) => `High memory usage (${memoryUsagePercent.toFixed(1)}%). Consider increasing allocated memory or reducing player count.`, frameDeficit: (frameDeficit) => `Frame deficit detected (${frameDeficit}). Audio quality may be affected. Check network and CPU.`, highLatency: (ping) => `High latency (${ping}ms). Check network connection to the node.`, nodeRestart: "Node restart recommended to clear memory and reset connections.", highPlayercount: (players) => `High player count (${players}). Consider load balancing across multiple nodes.`, nodeOffline: "Node is offline or disconnected", checkConnectivity: "Check node connectivity and restart if needed" }; var NodeLinkExclusiveEvents = [ "PlayerCreatedEvent", "PlayerDestroyedEvent", "PlayerConnectedEvent", "PlayerReconnectingEvent", "VolumeChangedEvent", "FiltersChangedEvent", "SeekEvent", "PauseEvent", "ConnectionStatusEvent", "MixStartedEvent", "MixEndedEvent", "LyricsFoundEvent", "LyricsLineEvent", "LyricsNotFoundEvent" ]; // src/structures/Node.ts import { isAbsolute } from "path"; import WebSocket from "ws"; // src/structures/Types/Node.ts var ReconnectionState = /* @__PURE__ */ ((ReconnectionState2) => { ReconnectionState2["IDLE"] = "IDLE"; ReconnectionState2["RECONNECTING"] = "RECONNECTING"; ReconnectionState2["PENDING"] = "PENDING"; ReconnectionState2["DESTROYING"] = "DESTROYING"; return ReconnectionState2; })(ReconnectionState || {}); var NodeType = /* @__PURE__ */ ((NodeType2) => { NodeType2["Lavalink"] = "Lavalink"; NodeType2["NodeLink"] = "NodeLink"; return NodeType2; })(NodeType || {}); // src/structures/Utils.ts import { URL as URL2 } from "url"; import { isRegExp } from "util/types"; // src/structures/LavalinkManagerStatics.ts var DefaultSources = { // youtubemusic "youtube music": "ytmsearch", youtubemusic: "ytmsearch", ytmsearch: "ytmsearch", ytm: "ytmsearch", musicyoutube: "ytmsearch", "music youtube": "ytmsearch", // youtube youtube: "ytsearch", yt: "ytsearch", ytsearch: "ytsearch", // soundcloud soundcloud: "scsearch", scsearch: "scsearch", sc: "scsearch", // apple music "apple music": "amsearch", apple: "amsearch", applemusic: "amsearch", amsearch: "amsearch", am: "amsearch", musicapple: "amsearch", "music apple": "amsearch", // spotify spotify: "spsearch", spsearch: "spsearch", sp: "spsearch", "spotify.com": "spsearch", spotifycom: "spsearch", sprec: "sprec", spsuggestion: "sprec", // deezer deezer: "dzsearch", dz: "dzsearch", dzsearch: "dzsearch", dzisrc: "dzisrc", dzrec: "dzrec", // yandexmusic "yandex music": "ymsearch", yandexmusic: "ymsearch", yandex: "ymsearch", ymsearch: "ymsearch", ymrec: "ymrec", // VK Music (lavasrc) vksearch: "vksearch", vkmusic: "vksearch", "vk music": "vksearch", vkrec: "vkrec", vk: "vksearch", // Qobuz (lavasrc) qbsearch: "qbsearch", qobuz: "qbsearch", qbisrc: "qbisrc", qbrec: "qbrec", // pandora (lavasrc) pandora: "pdsearch", pd: "pdsearch", pdsearch: "pdsearch", "pandora music": "pdsearch", pandoramusic: "pdsearch", // speak PLUGIN speak: "speak", tts: "tts", ftts: "ftts", flowery: "ftts", "flowery.tts": "ftts", flowerytts: "ftts", // Client sided search platforms (after lavalinkv4.0.6 it will search via bcsearch on the node itself) bandcamp: "bcsearch", bc: "bcsearch", bcsearch: "bcsearch", // other searches: phsearch: "phsearch", pornhub: "phsearch", porn: "phsearch", // local files local: "local", // http requests http: "http", https: "https", link: "link", uri: "uri", // tidal tidal: "tdsearch", td: "tdsearch", "tidal music": "tdsearch", tdsearch: "tdsearch", tdrec: "tdrec", // jiosaavn jiosaavn: "jssearch", js: "jssearch", jssearch: "jssearch", jsrec: "jsrec", // amazon music amzsearch: "amzsearch", // audiomack admsearch: "admsearch", // gaana gnsearch: "gnsearch", // shazam szsearch: "szsearch" }; var LavalinkPlugins = { DuncteBot_Plugin: "DuncteBot-plugin", LavaSrc: "lavasrc-plugin", GoogleCloudTTS: "tts-plugin", LavaSearch: "lavasearch-plugin", Jiosaavn_Plugin: "jiosaavn-plugin", LavalinkFilterPlugin: "lavalink-filter-plugin", JavaTimedLyricsPlugin: "java-lyrics-plugin", PulseLinkPlugin: "pulselink-plugin" }; var SourceLinksRegexes = { /** DEFAULT SUPPORTED BY LAVALINK */ YoutubeRegex: /https?:\/\/?(?:www\.)?(?:(m|www)\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|shorts|playlist\?|watch\?v=|watch\?.+(?:&|&#38;);v=))([a-zA-Z0-9\-_]{11})?(?:(?:\?|&|&#38;)index=((?:\d){1,3}))?(?:(?:\?|&|&#38;)?list=([a-zA-Z\-_0-9]{34}))?(?:\S+)?/, YoutubeMusicRegex: /https?:\/\/?(?:www\.)?(?:(music|m|www)\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|shorts|playlist\?|watch\?v=|watch\?.+(?:&|&#38;);v=))([a-zA-Z0-9\-_]{11})?(?:(?:\?|&|&#38;)index=((?:\d){1,3}))?(?:(?:\?|&|&#38;)?list=([a-zA-Z\-_0-9]{34}))?(?:\S+)?/, SoundCloudRegex: /https?:\/\/(?:on\.)?soundcloud\.com\//, SoundCloudMobileRegex: /https?:\/\/(soundcloud\.app\.goo\.gl)\/(\S+)/, bandcamp: /https?:\/\/?(?:www\.)?([\d|\w]+)\.bandcamp\.com\/(\S+)/, TwitchTv: /https?:\/\/?(?:www\.)?twitch\.tv\/\w+/, vimeo: /https?:\/\/(www\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|)(\d+)(?:|\/\?)/, mp3Url: /(https?|ftp|file):\/\/(www.)?(.*?)\.(mp3)$/, m3uUrl: /(https?|ftp|file):\/\/(www.)?(.*?)\.(m3u)$/, m3u8Url: /(https?|ftp|file):\/\/(www.)?(.*?)\.(m3u8)$/, mp4Url: /(https?|ftp|file):\/\/(www.)?(.*?)\.(mp4)$/, m4aUrl: /(https?|ftp|file):\/\/(www.)?(.*?)\.(m4a)$/, wavUrl: /(https?|ftp|file):\/\/(www.)?(.*?)\.(wav)$/, aacpUrl: /(https?|ftp|file):\/\/(www.)?(.*?)\.(aacp)$/, /** FROM LAVA SOURCE */ DeezerTrackRegex: /(https?:\/\/|)?(?:www\.)?deezer\.com\/(?:\w{2}\/)?track\/(\d+)/, DeezerPageLinkRegex: /(https?:\/\/|)?(?:www\.)?deezer\.page\.link\/(\S+)/, DeezerPlaylistRegex: /(https?:\/\/|)?(?:www\.)?deezer\.com\/(?:\w{2}\/)?playlist\/(\d+)/, DeezerAlbumRegex: /(https?:\/\/|)?(?:www\.)?deezer\.com\/(?:\w{2}\/)?album\/(\d+)/, DeezerArtistRegex: /(https?:\/\/|)?(?:www\.)?deezer\.com\/(?:\w{2}\/)?artist\/(\d+)/, DeezerMixesRegex: /(https?:\/\/|)?(?:www\.)?deezer\.com\/(?:\w{2}\/)?mixes\/genre\/(\d+)/, DeezerEpisodeRegex: /(https?:\/\/|)?(?:www\.)?deezer\.com\/(?:\w{2}\/)?episode\/(\d+)/, // DeezerPodcastRegex: /(https?:\/\/|)?(?:www\.)?deezer\.com\/(?:\w{2}\/)?podcast\/(\d+)/, AllDeezerRegexWithoutPageLink: /(https?:\/\/|)?(?:www\.)?deezer\.com\/(?:\w{2}\/)?(track|playlist|album|artist|mixes\/genre|episode)\/(\d+)/, AllDeezerRegex: /((https?:\/\/|)?(?:www\.)?deezer\.com\/(?:\w{2}\/)?(track|playlist|album|artist|mixes\/genre|episode)\/(\d+)|(https?:\/\/|)?(?:www\.)?deezer\.page\.link\/(\S+))/, SpotifySongRegex: /(https?:\/\/)(www\.)?open\.spotify\.com\/((?<region>[a-zA-Z-]+)\/)?(user\/(?<user>[a-zA-Z0-9-_]+)\/)?track\/(?<identifier>[a-zA-Z0-9-_]+)/, SpotifyPlaylistRegex: /(https?:\/\/)(www\.)?open\.spotify\.com\/((?<region>[a-zA-Z-]+)\/)?(user\/(?<user>[a-zA-Z0-9-_]+)\/)?playlist\/(?<identifier>[a-zA-Z0-9-_]+)/, SpotifyArtistRegex: /(https?:\/\/)(www\.)?open\.spotify\.com\/((?<region>[a-zA-Z-]+)\/)?(user\/(?<user>[a-zA-Z0-9-_]+)\/)?artist\/(?<identifier>[a-zA-Z0-9-_]+)/, SpotifyEpisodeRegex: /(https?:\/\/)(www\.)?open\.spotify\.com\/((?<region>[a-zA-Z-]+)\/)?(user\/(?<user>[a-zA-Z0-9-_]+)\/)?episode\/(?<identifier>[a-zA-Z0-9-_]+)/, SpotifyShowRegex: /(https?:\/\/)(www\.)?open\.spotify\.com\/((?<region>[a-zA-Z-]+)\/)?(user\/(?<user>[a-zA-Z0-9-_]+)\/)?show\/(?<identifier>[a-zA-Z0-9-_]+)/, SpotifyAlbumRegex: /(https?:\/\/)(www\.)?open\.spotify\.com\/((?<region>[a-zA-Z-]+)\/)?(user\/(?<user>[a-zA-Z0-9-_]+)\/)?album\/(?<identifier>[a-zA-Z0-9-_]+)/, AllSpotifyRegex: /(https?:\/\/)(www\.)?open\.spotify\.com\/((?<region>[a-zA-Z-]+)\/)?(user\/(?<user>[a-zA-Z0-9-_]+)\/)?(?<type>track|album|playlist|artist|episode|show)\/(?<identifier>[a-zA-Z0-9-_]+)/, appleMusic: /https?:\/\/?(?:www\.)?music\.apple\.com\/(\S+)/, /** From tidal */ tidal: /https?:\/\/?(?:www\.)?(?:tidal|listen)\.tidal\.com\/(?<type>track|album|playlist|artist)\/(?<identifier>[a-zA-Z0-9-_]+)/, /** From jiosaavn-plugin */ jiosaavn: /(https?:\/\/)(www\.)?jiosaavn\.com\/(?<type>song|album|featured|artist)\/([a-zA-Z0-9-_/,]+)/, /** From pandora */ PandoraTrackRegex: /^@?(?:https?:\/\/)?(?:www\.)?pandora\.com\/artist\/[\w-]+(?:\/[\w-]+)*\/(?<identifier>TR[A-Za-z0-9]+)(?:[?#].*)?$/, PandoraAlbumRegex: /^@?(?:https?:\/\/)?(?:www\.)?pandora\.com\/artist\/[\w-]+(?:\/[\w-]+)*\/(?<identifier>AL[A-Za-z0-9]+)(?:[?#].*)?$/, PandoraArtistRegex: /^@?(?:https?:\/\/)?(?:www\.)?pandora\.com\/artist\/[\w-]+\/(?<identifier>AR[A-Za-z0-9]+)(?:[?#].*)?$/, PandoraPlaylistRegex: /^@?(?:https?:\/\/)?(?:www\.)?pandora\.com\/playlist\/(?<identifier>PL:[\d:]+)(?:[?#].*)?$/, AllPandoraRegex: /^@?(?:https?:\/\/)?(?:www\.)?pandora\.com\/(?:playlist\/(?<playlistId>PL:[\d:]+)|artist\/[\w-]+(?:\/[\w-]+)*\/(?<identifier>(?:TR|AL|AR)[A-Za-z0-9]+))(?:[?#].*)?$/, /** FROM DUNCTE BOT PLUGIN */ tiktok: /https:\/\/www\.tiktok\.com\//, mixcloud: /https:\/\/www\.mixcloud\.com\//, musicYandex: /https:\/\/music\.yandex\.ru\//, radiohost: /https?:\/\/[^.\s]+\.radiohost\.de\/(\S+)/ }; // src/structures/Utils.ts var TrackSymbol = /* @__PURE__ */ Symbol("LC-Track"); var UnresolvedTrackSymbol = /* @__PURE__ */ Symbol("LC-Track-Unresolved"); var QueueSymbol = /* @__PURE__ */ Symbol("LC-Queue"); var NodeSymbol = /* @__PURE__ */ Symbol("LC-Node"); var escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); function parseLavalinkConnUrl(connectionUrl) { if (!connectionUrl) throw new Error("ConnectionUrl is required"); const lowered = connectionUrl.toLowerCase(); if (!lowered.startsWith("lavalink://") && !lowered.startsWith("nodelink://")) throw new Error(`ConnectionUrl (${connectionUrl}) must start with 'lavalink://' or 'nodelink://'`); const parsed = new URL2(connectionUrl); return { authorization: parsed.password, nodeType: lowered.startsWith("lavalink://") ? "Lavalink" /* Lavalink */ : "NodeLink" /* NodeLink */, id: parsed.username, host: parsed.hostname, port: Number(parsed.port) }; } var ManagerUtils = class { LavalinkManager = void 0; /** Override this with your custom sources record if you want to use custom sources for your node */ SourcesRecord = DefaultSources; constructor(LavalinkManager2) { this.LavalinkManager = LavalinkManager2; } /** * Builds a pluginInfo object based on the provided data, extracting relevant information from the data and clientData parameters. This function is used to construct the pluginInfo property for tracks, allowing for consistent handling of plugin-related information across different track sources and formats. * @param data * @param clientData * @returns */ buildPluginInfo(data, clientData = {}) { return { clientData, ...data.pluginInfo || data.plugin }; } /** * Builds a Track object from the provided data and requester information. It validates the presence of required properties in the data, transforms the requester using a custom transformer function if provided, and constructs a Track object with the appropriate properties and plugin information. The function also includes error handling to ensure that the input data is valid and provides debug information if track building fails. * @param data * @param requester * @returns */ buildTrack(data, requester) { if (!data?.encoded || typeof data.encoded !== "string") throw new RangeError("Argument 'data.encoded' must be present."); if (!data.info) throw new RangeError("Argument 'data.info' must be present."); try { let transformedRequester = typeof requester === "object" ? this.getTransformedRequester(requester) : void 0; if (!transformedRequester && typeof data?.userData?.requester === "object" && data.userData.requester !== null) { transformedRequester = this.getTransformedRequester(data.userData.requester); } const r = { encoded: data.encoded, info: { identifier: data.info.identifier, title: data.info.title, author: data.info.author, duration: data.info?.duration || data.info?.length, artworkUrl: data.info.artworkUrl || data.pluginInfo?.artworkUrl || data.plugin?.artworkUrl, uri: data.info.uri, sourceName: data.info.sourceName, isSeekable: data.info.isSeekable, isStream: data.info.isStream, isrc: data.info.isrc }, userData: { ...data.userData, requester: transformedRequester }, pluginInfo: this.buildPluginInfo(data, "clientData" in data ? data.clientData : {}), requester: transformedRequester || this.getTransformedRequester(this.LavalinkManager?.options?.client) }; Object.defineProperty(r, TrackSymbol, { configurable: true, value: true }); return r; } catch (error) { if (this.LavalinkManager?.options?.advancedOptions?.enableDebugEvents) { this.LavalinkManager?.emit("debug", "BuildTrackError" /* BuildTrackError */, { error, functionLayer: "ManagerUtils > buildTrack()", message: "Error while building track", state: "error" }); } throw new RangeError(`Argument "data" is not a valid track: ${error.message}`); } } /** * Builds a UnresolvedTrack to be resolved before being played . * @param query * @param requester */ buildUnresolvedTrack(query, requester) { if (typeof query === "undefined") throw new RangeError('Argument "query" must be present.'); const unresolvedTrack = { encoded: query.encoded || void 0, info: query.info ? query.info : query.title ? query : void 0, pluginInfo: this.buildPluginInfo(query), requester: this.getTransformedRequester(requester), async resolve(player) { const closest = await getClosestTrack(this, player); if (!closest) throw new SyntaxError("No closest Track found"); for (const prop of Object.getOwnPropertyNames(this)) delete this[prop]; delete this[UnresolvedTrackSymbol]; Object.defineProperty(this, TrackSymbol, { configurable: true, value: true }); return Object.assign(this, closest); } }; if (!this.isUnresolvedTrack(unresolvedTrack)) throw SyntaxError("Could not build Unresolved Track"); Object.defineProperty(unresolvedTrack, UnresolvedTrackSymbol, { configurable: true, value: true }); return unresolvedTrack; } /** * Validate if a data is equal to a node * @param data */ isNode(data) { if (!data) return false; const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(data)); if (!keys.includes("constructor")) return false; if (!keys.length) return false; if (![ "connect", "destroy", "destroyPlayer", "fetchAllPlayers", "fetchInfo", "fetchPlayer", "fetchStats", "fetchVersion", "request", "updatePlayer", "updateSession" ].every((v) => keys.includes(v))) return false; return true; } /** * Gets the transformed requester based on the LavalinkManager options. If a custom requester transformer function is provided in the player options, it applies that function to the requester; otherwise, it returns the requester as is. The function also includes error handling to catch any exceptions that may occur during the transformation process and emits a debug event if the transformation fails. * @param requester * @returns */ getTransformedRequester(requester) { try { return typeof this.LavalinkManager?.options?.playerOptions?.requesterTransformer === "function" ? this.LavalinkManager?.options?.playerOptions?.requesterTransformer(requester) : requester; } catch (e) { if (this.LavalinkManager?.options?.advancedOptions?.enableDebugEvents) { this.LavalinkManager?.emit("debug", "TransformRequesterFunctionFailed" /* TransformRequesterFunctionFailed */, { error: e, functionLayer: "ManagerUtils > getTransformedRequester()", message: "Your custom transformRequesterFunction failed to execute, please check your function for errors.", state: "error" }); } return requester; } } /** * Validate if a data is equal to node options * @param data */ isNodeOptions(data) { if (!data || typeof data !== "object" || Array.isArray(data)) return false; if (typeof data.host !== "string" || !data.host.length) return false; if (typeof data.port !== "number" || isNaN(data.port) || data.port < 0 || data.port > 65535) return false; if (typeof data.authorization !== "string" || !data.authorization.length) return false; if ("secure" in data && typeof data.secure !== "boolean" && data.secure !== void 0) return false; if ("sessionId" in data && typeof data.sessionId !== "string" && data.sessionId !== void 0) return false; if ("id" in data && typeof data.id !== "string" && data.id !== void 0) return false; if ("regions" in data && (!Array.isArray(data.regions) || !data.regions.every((v) => typeof v === "string") && data.regions !== void 0)) return false; if ("poolOptions" in data && typeof data.poolOptions !== "object" && data.poolOptions !== void 0) return false; if ("retryAmount" in data && (typeof data.retryAmount !== "number" || isNaN(data.retryAmount) || data.retryAmount <= 0 && data.retryAmount !== void 0)) return false; if ("retryDelay" in data && (typeof data.retryDelay !== "number" || isNaN(data.retryDelay) || data.retryDelay <= 0 && data.retryDelay !== void 0)) return false; if ("requestTimeout" in data && (typeof data.requestTimeout !== "number" || isNaN(data.requestTimeout) || data.requestTimeout <= 0 && data.requestTimeout !== void 0)) return false; return true; } /** * Validate tracks based on duration whether they are playble or broken tracks. * most tracks should be longer than 30s, so you can put a minDuration of 29e3 (cause preview tracks are exactly 30s) or put 0. * This check is not done automatically, you need to check it yourself by doing: * @example * ```ts * const res = await player.search("Adele"); * * // short hand: * const validTracks = res.tracks.filter(client.lavalink.utils.isNotBrokenTrack) * // or with options: * const validTracks = res.tracks.filter(t => client.lavalink.utils.isNotBrokenTrack(t, 29e3)); * * // then you can add it to the queue. * await player.queue.add(validTracks); * ``` */ isNotBrokenTrack(data, minDuration = 29e3) { if (typeof data?.info?.duration !== "number" || isNaN(data?.info?.duration)) return false; if (data.info.duration <= Math.max(minDuration, 0)) return false; if (!data.info) return false; return this.isTrack(data); } /** * Validate if a data is equal to a track * @param data the Track to validate * @returns */ isTrack(data) { if (!data) return false; if (data[TrackSymbol] === true) return true; return typeof data?.encoded === "string" && typeof data?.info === "object" && !("resolve" in data); } /** * Checks if the provided argument is a valid UnresolvedTrack. * @param track */ isUnresolvedTrack(data) { if (!data) return false; if (data[UnresolvedTrackSymbol] === true) return true; return typeof data === "object" && ("info" in data && typeof data.info.title === "string" || typeof data.encoded === "string") && "resolve" in data && typeof data.resolve === "function"; } /** * Checks if the provided argument is a valid UnresolvedTrack. * @param track */ isUnresolvedTrackQuery(data) { return typeof data === "object" && !("info" in data) && typeof data.title === "string"; } /** * Gets the closest track by resolving the provided UnresolvedTrack using the getClosestTrack function. It includes error handling to catch any exceptions that may occur during the resolution process and emits a debug event if the resolution fails. The function returns a Promise that resolves to a Track object if successful, or undefined if no closest track is found. * @param data * @param player * @returns */ async getClosestTrack(data, player) { try { return getClosestTrack(data, player); } catch (e) { if (this.LavalinkManager?.options?.advancedOptions?.enableDebugEvents) { this.LavalinkManager?.emit("debug", "GetClosestTrackFailed" /* GetClosestTrackFailed */, { error: e, functionLayer: "ManagerUtils > getClosestTrack()", message: "Failed to resolve track because the getClosestTrack function failed.", state: "error" }); } throw e; } } /** * Validates the query string against various criteria, including checking for empty strings, length limits for specific sources, blacklisted links or words, and ensuring that the Lavalink node has the necessary source managers enabled for the provided query. The function also includes debug event emissions to assist with troubleshooting and understanding the validation process. * @param node * @param queryString * @param sourceString * @returns */ validateQueryString(node, queryString, sourceString) { if (!node.info) throw new Error("No Lavalink Node was provided"); if (node._checkForSources && !node.info.sourceManagers?.length) throw new Error("Lavalink Node, has no sourceManagers enabled"); if (!queryString.trim().length) throw new Error(`Query string is empty, please provide a valid query string.`); if (sourceString === "speak" && queryString.length > 100) throw new Error(`Query is speak, which is limited to 100 characters.`); if (this.LavalinkManager.options?.linksBlacklist?.length > 0) { if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) { this.LavalinkManager.emit("debug", "ValidatingBlacklistLinks" /* ValidatingBlacklistLinks */, { state: "log", message: `Validating Query against LavalinkManager.options.linksBlacklist, query: "${queryString}"`, functionLayer: "(LavalinkNode > node | player) > search() > validateQueryString()" }); } if (this.LavalinkManager.options?.linksBlacklist.some( (v) => typeof v === "string" && queryString.toLowerCase().includes(v.toLowerCase()) || isRegExp(v) && v.test(queryString) )) { throw new Error(`Query string contains a link / word which is blacklisted.`); } } if (!/^https?:\/\//.test(queryString)) return; else if (this.LavalinkManager.options?.linksAllowed === false) throw new Error("Using links to make a request is not allowed."); if (this.LavalinkManager.options?.linksWhitelist?.length > 0) { if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) { this.LavalinkManager.emit("debug", "ValidatingWhitelistLinks" /* ValidatingWhitelistLinks */, { state: "log", message: `Link was provided to the Query, validating against LavalinkManager.options.linksWhitelist, query: "${queryString}"`, functionLayer: "(LavalinkNode > node | player) > search() > validateQueryString()" }); } if (!this.LavalinkManager.options?.linksWhitelist.some( (v) => typeof v === "string" && queryString.toLowerCase().includes(v.toLowerCase()) || isRegExp(v) && v.test(queryString) )) { throw new Error(`Query string contains a link / word which isn't whitelisted.`); } } if (!node._checkForSources) return; if ((SourceLinksRegexes.YoutubeMusicRegex.test(queryString) || SourceLinksRegexes.YoutubeRegex.test(queryString)) && !node.info?.sourceManagers?.includes("youtube")) { throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'youtube' enabled"); } if ((SourceLinksRegexes.SoundCloudMobileRegex.test(queryString) || SourceLinksRegexes.SoundCloudRegex.test(queryString)) && !node.info?.sourceManagers?.includes("soundcloud")) { throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'soundcloud' enabled"); } if (SourceLinksRegexes.bandcamp.test(queryString) && !node.info?.sourceManagers?.includes("bandcamp")) { throw new Error( "Query / Link Provided for this Source but Lavalink Node has not 'bandcamp' enabled (introduced with lavaplayer 2.2.0 or lavalink 4.0.6)" ); } if (SourceLinksRegexes.TwitchTv.test(queryString) && !node.info?.sourceManagers?.includes("twitch")) { throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'twitch' enabled"); } if (SourceLinksRegexes.vimeo.test(queryString) && !node.info?.sourceManagers?.includes("vimeo")) { throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'vimeo' enabled"); } if (SourceLinksRegexes.tiktok.test(queryString) && !node.info?.sourceManagers?.includes("tiktok")) { throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'tiktok' enabled"); } if (SourceLinksRegexes.mixcloud.test(queryString) && !node.info?.sourceManagers?.includes("mixcloud")) { throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'mixcloud' enabled"); } if (SourceLinksRegexes.AllSpotifyRegex.test(queryString) && !node.info?.sourceManagers?.includes("spotify")) { throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'spotify' enabled"); } if (SourceLinksRegexes.appleMusic.test(queryString) && !node.info?.sourceManagers?.includes("applemusic")) { throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'applemusic' enabled"); } if (SourceLinksRegexes.AllDeezerRegex.test(queryString) && !node.info?.sourceManagers?.includes("deezer")) { throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'deezer' enabled"); } if (SourceLinksRegexes.musicYandex.test(queryString) && !node.info?.sourceManagers?.includes("yandexmusic")) { throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'yandexmusic' enabled"); } if (SourceLinksRegexes.jiosaavn.test(queryString) && !node.info?.sourceManagers?.includes("jiosaavn")) { throw new Error( "Query / Link Provided for this Source but Lavalink Node has not 'jiosaavn' (via jiosaavn-plugin) enabled" ); } if (SourceLinksRegexes.tidal.test(queryString) && !node.info?.sourceManagers?.includes("tidal")) { throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'tidal' enabled"); } if (SourceLinksRegexes.AllPandoraRegex.test(queryString) && !node.info?.sourceManagers?.includes("pandora")) { throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'pandora' enabled"); } return; } /** * Finds the source of a query string by checking if it starts with a valid source prefix defined in the Default Sources object. If a valid source prefix is found, it returns the corresponding SearchPlatform; otherwise, it returns null. This function is useful for determining the intended search platform for a given query string, allowing for more accurate search results when the user specifies a source (e.g., "ytsearch:Never Gonna Give You Up" would indicate that the search should be performed on YouTube). * @param queryString * @returns */ findSourceOfQuery(queryString) { const foundSource = Object.keys(this.SourcesRecord).find((source) => queryString?.toLowerCase?.()?.startsWith(`${source}:`.toLowerCase()))?.trim?.()?.toLowerCase?.(); if (foundSource && !["https", "http"].includes(foundSource) && this.SourcesRecord[foundSource]) { return foundSource; } return null; } /** * Extracts the source from the query if it starts with a valid source prefix (e.g., "ytsearch:") and updates the searchQuery object accordingly. * @param searchQuery * @returns The updated searchQuery object with the extracted source and modified query string. */ extractSourceOfQuery(searchQuery) { const foundSource = this.findSourceOfQuery(searchQuery.query); if (foundSource) { searchQuery.source = this.SourcesRecord[foundSource]; searchQuery.query = searchQuery.query.slice(`${foundSource}:`.length, searchQuery.query.length); } return searchQuery; } /** * Converts a string to lowercase if the input is a string, otherwise returns the input as is. This is useful for ensuring that search platform identifiers are case-insensitive while allowing other types of input to pass through unchanged. * @param input * @returns */ typedLowerCase(input) { if (!input) return input; if (typeof input === "string") return input.toLowerCase(); return input; } /** * Transforms a search query by determining the appropriate search platform based on the query string and the default search platform specified in the LavalinkManager options. It checks if the query string starts with a valid source prefix and extracts it if present. The function returns an object containing the modified query string, any extra URL parameters, and the determined search platform to be used for the search operation. * @param query * @returns */ transformQuery(query) { const typedDefault = this.typedLowerCase(this.LavalinkManager?.options?.playerOptions?.defaultSearchPlatform); if (typeof query === "string") { const Query = { query, extraQueryUrlParams: void 0, source: typedDefault }; return this.extractSourceOfQuery(Query); } const providedSource = query?.source?.trim?.()?.toLowerCase?.(); const validSourceExtracted = this.SourcesRecord[providedSource ?? typedDefault]; return this.extractSourceOfQuery({ query: query.query, extraQueryUrlParams: query.extraQueryUrlParams, source: validSourceExtracted ?? providedSource ?? typedDefault }); } /** * Transforms a LavaSearchQuery by determining the appropriate search platform based on the query string and the default search platform specified in the LavalinkManager options. It checks if the query string starts with a valid source prefix and extracts it if present. The function returns an object containing the modified query string, any extra URL parameters, the determined search platform to be used for the search operation, and the types of search (track, playlist, artist, album, text) to be performed. * @param query * @returns */ transformLavaSearchQuery(query) { const typedDefault = this.typedLowerCase(this.LavalinkManager?.options?.playerOptions?.defaultSearchPlatform); if (typeof query === "string") { const Query2 = { query, types: [], extraQueryUrlParams: void 0, source: typedDefault }; return this.extractSourceOfQuery(Query2); } const providedSource = query?.source?.trim?.()?.toLowerCase?.(); const validSourceExtracted = this.SourcesRecord[providedSource ?? typedDefault]; const Query = { query: query.query, types: query.types ? ["track", "playlist", "artist", "album", "text"].filter( (v) => query.types?.find((x) => x.toLowerCase().startsWith(v)) ) : [ "track", "playlist", "artist", "album" /*"text"*/ ], source: validSourceExtracted ?? providedSource ?? typedDefault }; return this.extractSourceOfQuery(Query); } /** * Validates the provided source string against the capabilities of the Lavalink node. It checks if the source string is supported by the node's enabled source managers and plugins, throwing errors if any required sources or plugins are missing for the specified search platform. This ensures that search queries are only executed with compatible sources based on the node's configuration. * @param node * @param sourceString * @returns */ validateSourceString(node, sourceString) { if (!sourceString) throw new Error(`No SourceString was provided`); const source = this.SourcesRecord[sourceString.toLowerCase().trim()]; if (!source && !!this.LavalinkManager.options.playerOptions.allowCustomSources) throw new Error( `Lavalink-Client does not support SearchQuerySource: '${sourceString}'. You can disable this check by setting 'ManagerOptions.PlayerOptions.allowCustomSources' to true` ); if (!node.info) throw new Error("Lavalink Node does not have any info cached yet, not ready yet!"); if (!node._checkForSources) return; if (source === "amsearch" && !node.info?.sourceManagers?.includes("applemusic")) { throw new Error("Lavalink Node has not 'applemusic' enabled, which is required to have 'amsearch' work"); } if (source === "dzisrc" && !node.info?.sourceManagers?.includes("deezer")) { throw new Error("Lavalink Node has not 'deezer' enabled, which is required to have 'dzisrc' work"); } if (source === "dzsearch" && !node.info?.sourceManagers?.includes("deezer")) { throw new Error("Lavalink Node has not 'deezer' enabled, which is required to have 'dzsearch' work"); } if (source === "dzisrc" && node.info?.sourceManagers?.includes("deezer") && !node.info?.sourceManagers?.includes("http")) { throw new Error("Lavalink Node has not 'http' enabled, which is required to have 'dzisrc' to work"); } if (source === "jsrec" && !node.info?.sourceManagers?.includes("jiosaavn")) { throw new Error( "Lavalink Node has not 'jiosaavn' (via jiosaavn-plugin) enabled, which is required to have 'jsrec' to work" ); } if (source === "jssearch" && !node.info?.sourceManagers?.includes("jiosaavn")) { throw new Error( "Lavalink Node has not 'jiosaavn' (via jiosaavn-plugin) enabled, which is required to have 'jssearch' to work" ); } if (source === "scsearch" && !node.info?.sourceManagers?.includes("soundcloud")) { throw new Error("Lavalink Node has not 'soundcloud' enabled, which is required to have 'scsearch' work"); } if (source === "speak" && node._checkForPlugins && !node.info?.plugins?.find( (c) => c.name.toLowerCase().includes(LavalinkPlugins.DuncteBot_Plugin.toLowerCase()) )) { throw new Error("Lavalink Node has not 'speak' enabled, which is required to have 'speak' work"); } if (source === "tdsearch" && !node.info?.sourceManagers?.includes("tidal")) { throw new Error("Lavalink Node has not 'tidal' enabled, which is required to have 'tdsearch' work"); } if (source === "tdrec" && !node.info?.sourceManagers?.includes("tidal")) { throw new Error("Lavalink Node has not 'tidal' enabled, which is required to have 'tdrec' work"); } if (source === "tts" && node._checkForPlugins && !node.info?.plugins?.find( (c) => c.name.toLowerCase().includes(LavalinkPlugins.GoogleCloudTTS.toLowerCase()) )) { throw new Error("Lavalink Node has not 'tts' enabled, which is required to have 'tts' work"); } if (source === "ftts" && !(node.info?.sourceManagers?.includes("ftts") || node.info?.sourceManagers?.includes("flowery-tts") || node.info?.sourceManagers?.includes("flowerytts"))) { throw new Error("Lavalink Node has not 'flowery-tts' enabled, which is required to have 'ftts' work"); } if (source === "ymsearch" && !node.info?.sourceManagers?.includes("yandexmusic")) { throw new Error("Lavalink Node has not 'yandexmusic' enabled, which is required to have 'ymsearch' work"); } if (source === "ytmsearch" && !node.info?.sourceManagers?.includes("youtube")) { throw new Error("Lavalink Node has not 'youtube' enabled, which is required to have 'ytmsearch' work"); } if (source === "ytsearch" && !node.info?.sourceManagers?.includes("youtube")) { throw new Error("Lavalink Node has not 'youtube' enabled, which is required to have 'ytsearch' work"); } if (source === "vksearch" && !node.info?.sourceManagers?.includes("vkmusic")) { throw new Error("Lavalink Node has not 'vkmusic' enabled, which is required to have 'vksearch' work"); } if (source === "vkrec" && !node.info?.sourceManagers?.includes("vkmusic")) { throw new Error("Lavalink Node has not 'vkmusic' enabled, which is required to have 'vkrec' work"); } if (source === "qbsearch" && !node.info?.sourceManagers?.includes("qobuz")) { throw new Error("Lavalink Node has not 'qobuz' enabled, which is required to have 'qbsearch' work"); } if (source === "qbisrc" && !node.info?.sourceManagers?.includes("qobuz")) { throw new Error("Lavalink Node has not 'qobuz' enabled, which is required to have 'qbisrc' work"); } if (source === "qbrec" && !node.info?.sourceManagers?.includes("qobuz")) { throw new Error("Lavalink Node has not 'qobuz' enabled, which is required to have 'qbrec' work"); } if (["pdsearch", "pdisrc", "pdrec"].includes(source) && !node.info?.sourceManagers?.includes("pandora")) { throw new Error("Lavalink Node has not 'pandora' enabled, which is required to have '" + source + "' work"); } if (source === "amzsearch" && !node.info?.sourceManagers?.includes("amazonmusic")) { throw new Error("Lavalink Node has not 'amazonmusic' enabled, which is required to have 'amzsearch' work"); } if (source === "admsearch" && !node.info?.sourceManagers?.includes("audiomack")) { throw new Error("Lavalink Node has not 'audiomack' enabled, which is required to have 'admsearch' work"); }