2023.2.1 • Published 1 year ago

@thundernetworkrad/erela.js v2023.2.1

Weekly downloads
-
License
Apache-2.0
Repository
github
Last release
1 year ago

erela.js - Updated & Improved by Tomato6966

An up to date Lavalink Wrapper, which makes it easy to use Lavalink within your Discord Bot!

  • Works in Typescript, CommonJS and ModuleJS
  • Made for discord.js, but works in discordeno (example), eris, oceanic and many other libraries too!
  • Supports Regioning (e.g: Picking a USA Node for USA Voice Channel Regions)
  • Supports Latest Lavalink Version (v3.7) REST API
  • Inbuilt Filters
  • Many Settings like: instaFilterFix, volumeDecrementer, etc. which makes using it way easier!

Maintained + improved Fork from the official menudocs/erela.js repository

Within this Repository, I did:

  • updated erela.js to support Latest Lavalink Features (Sessions, REST-API, etc.)
  • Added new Features

Install my Version

npm:

npm i Tomato6966/erela.js

yarn:

yarn add Tomato6966/erela.js

You need a Lavalink Server

Check here how to set one up!

  • I made a dedicated cheatsheet for that including how to setup the new Lavalink Plugins!

Or shortly said here:

  1. Download the latest .jar File-Release from the releases page of the Lavalink Repository
  2. Download the example Application.yml file from the official Example and put it in the same directory as your Lavalink.jar file (note it must be named: application.yml)
  3. Adjust the settings of your Lavalink Server in the file called: application.yml: Port, Password are the most important ones! (they are used to connect to it via erela.js)
  4. Start the Lavalink-Server with a terminal by typing: java -jar Lavalink.jar within that directory!

Overview - Table of Contents

New features - Tutorials

See how specific features work!

Docs:

Examples

Contributers and Credits

Click here to jump to the credits and contributers! ❤


erela.js-Docs

class Manager(ManagerOptions)

client.musicManager = new Manager(ManagerOptions)

ManagerOptions

Variable NameValueDefaultDescription
nodesNodeOptions[]DefaultNodeWhat Nodes to use for this Manager
clientIdstring-the Bot-Client Id (Snowflake) / any
clientNamestring"erela.js"Value for the Lavalink Client-Name header
shardsnumber1The Shard count
pluginsPlugin[]-Array of erela.js Plugins
forceLoadPluginbooleantrueIf Plugins should be loaded, no matter which class they extend
autoPlaybooleantrueWheter it should skip to the next song in the queue on Errors, songFinish, etc.
trackPartialstring[]-an array of track Properties to keep
defaultSearchPlatformSearchPlatform"youtube"What Searchplatform to use, when searching without a source
volumeDecrementernumber1how much % the Volume should decrement, when sending the volume, e.g. u do setVolume(100) with a decrementer of 0.75 and lavalink gets told to set the volume to: 75%
position_update_intervalnumber250in how many ms interval steps, the player-position should be incremented client sided ( Lavalink sends it every N seconds defualt configuration is 5s )
validUnresolvedUrisstring[]"www.youtu", "music.youtu", "soundcloud.com"What Urls are allowed to be directly used from lavalink, when resolving unresolved tracks
allowedLinksstring[]-Array of URLS to allow which you are too lazy to write REGEXes of (must be 1:1)
allowedLinksRegexesRegExp[]Manager#regexall regexes which allow URLS (if 1 regex matches a given url, the url will be used)
onlyAllowAllowedLinksbooleantrueif it should only allow urls, from allowedLinksRegexes or allowedLinks
defaultLeastUsedNodeSortType"memory" / "calls" / "players""players"How it should sort the NODES on Manager#leastUsedNode() (Before it was calls, now it's players)
defaultLeastLoadNodeSortType"cpu" / "memory""memory"How it should sort the NODES on Manager#leastLoadNode() (Before it was cpu, now it's memory)
forceSearchLinkQueriesbooleantrueIf it should force-load links via Manager#searchLink (which should happen automatically, but this forces it)
useUnresolvedDatabooleantrueIf it should use unresolved Tracks Data uppon their resolved ones
userAgentstring"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 OPR/93.0.0.0"What user Agent to use when doing Requests to Lavalink
restTimeoutnumber-Amount of time to wait until the Rest Methods are rejected
sendFunction(id: string, payload:Payload)-The function to send payload to discord...
applyVolumeAsFilterBooleanfalseIf volume should only be applied via the filter property (no smooth transition), you can always set manual by using Player#setVolumeFilter(0-5)

Manager-Methods / Properties

Method-Name / Variable-NamereturnDataDescription
regexRecord<SourcesRegex, RegExp>An Object of all default Source Regexes, which you may use too!
DEFAULT_SOURCESRecord<SearchPlatform, string>An Object of all SearchPlatform strings you may use, to convert to lavalink sources
playersCollection<guildId:string, Player>A Collection of all Players
nodesCollection<nodeIdentifier:string, node>A Collection of all Nodes
optionsManagerOptionsall Options given to the Manager
leastUsedNodesCollection<nodeIdentifier:string, Node>A Collection of all Nodes, but sorted by their Calls/Players/Memory Usages (Manager#defaultLeastUsedNodeSortType)
leastUsedNodesCallsCollection<nodeIdentifier:string, Node>A Collection of all Nodes, but sorted by their Calls Usages
leastUsedNodesPlayersCollection<nodeIdentifier:string, Node>A Collection of all Nodes, but sorted by their Players Usages
leastUsedNodesMemoryCollection<nodeIdentifier:string, Node>A Collection of all Nodes, but sorted by their Memory Usages
leastLoadNodesCollection<nodeIdentifier:string, Node>A Collection of all Nodes, but sorted by their Cpu/Memory Usages (Manager#defaultLeastLoadNodeSortType)
leastLoadNodesMemoryCollection<nodeIdentifier:string, Node>A Collection of all Nodes, but sorted by their Memory Usages
leastLoadNodesCpuCollection<nodeIdentifier:string, Node>A Collection of all Nodes, but sorted by their Cpu Usages
init(clientID:string, { clientId?: string, clientName?:string, shards?:number })-Initialize the Manager (+ you can now add clientName and shards etc. too)
search({ query: string, source:SearchPlatform }, requester:unknown, customNode:Node)Promise\Search for a Query, you can specifcy the Node too (player.node) if you wanna use regioning!
searchLink(link:string, requester:unknown, customNode:Node)Promise\Search for a Link on Lavalink
decodeTracks(tracks:string[])Promise\<TrackData[]>Decode Tracks from Lavalink
decodeTrack(track:string)Promise\Decode a single Track
create(PlayerOptions)PlayerCreate a Player
get(guildId:string)Playerget a Player from a guildId
destroy(guildId:string)voidDestroy a Player of a GuildId
createNode(NodeOptions)NodeCreates a Node
destroyNode(NodeIdentifier:string)voidDestroy a Node
updateVoiceState(data)Promise\update the Voice State to Lavalink

Manager-Events

Event-NamesParametersDescription
nodeCreatenodeEmitted once a node gets Created
nodeConnectnodeEmits when a node Connects
nodeReconnectnodeEmits when a node attempts a reconnect
nodeDisconnectnode, reason: { code?: number, reason?: string}Emits when a node disconnects
nodeErrornode, errorEmits when a node throws errors
nodeRawpayloadEmits every payload from a Node
playerCreateplayerEmits when a player gets created
playerDestroyplayerEmits when a player get's destroyed
queueEndplayer, track, payloadEmits when the queue End
playerMoveplayer, initChannel, newChannelEmits when the player moves from a VC to another one
playerDisconnectplayer, oldChannelEmits when the player Leaves the VC
trackStartplayer, track, payloadEmits when a track starts to play
trackEndplayer, track, payloadEmits when a track ends playing
trackStuckplayer, track, payloadEmits when a track gets stucked and skips the track
trackErrorplayer, track, payloadEmits when a track errors and skips it
socketClosedplayer, payloadEmits when a connection gets closed

Everything about Nodes

Creating a Node

You tell the Manager in it's ManagerOptions, which Nodes it shall use:

client.musicManager = new Manager({
    ...ManagerOptions,
    nodes: [ { identifier: "MyNode1", host: "localhost", port: 2333, password: "youshallnotpass", version: "v3", useVersionPath: true, secure: false, } ]
})

You can pass multiple Nodes too, if you do so the identifier Property is very Important! How to create a Node after Manager-Creation?

client.musicManager.createNode({ identifier: "MyNode3", host: "localhost", port: 2333, password: "youshallnotpass", version: "v3", useVersionPath: true, secure: false });

NodeOptions

Variable-NameValueDefaultDescription
hoststring-The Hostname / Ipaddress of your Lavalink-Node
portnumber2333The Port of the Node
passwordstring"youshallnotpass"The Authentication Creditentals aka Password
securebooleanfalseIf it shall use Port 443 and https
identifierstring-The identifier of the Node (for your manager#nodes Collection key)
retryAmountnumber5How often it shall retry to connect on fails
retryDelaynumber30e3How long to wait between each retry
requestTimeoutnumber10e3How long to wait until a REST-Request gets rejected
poolOptionsundici.Pool.Options-The Undici Pool Options
regionsstring[]-Discord Voice Regions array (for that node) to support regioning
version"v2""v3"-Lavalink Version (use v3 for REST API)
useVersionPathboolean-If it should use the path while making Requests (if your lavalink node is on v2 make it on false)

defaultnode

The Default Node is beeing used, when no node is provided while creating the Manager -> You can also see it as an "example" of Node Data

{
    identifier: "default",
    host: "localhost",
    port: 2333,
    password: "youshallnotpass",
    secure: false,
    retryAmount: 5,
    retryDelay: 30e3,
    requestTimeout: 10e3,
    version: "v3",
    useVersionPath: true, // should be set on true, to use the latest rest api correctly!
}

Node-Methods / Properties

SOON


Everything about the Player

Creating a Player

const player = client.musicManager.create({
    guild: voiceChannel.guild.id,
    voiceChannel: voiceChannel.id,
    textChannel: message.channel.id,
    selfMute: false,
    selfDeafen: true,
    region: voiceChannel?.rtcRegion || undefined, // region
    instaUpdateFiltersFix: true, // to disable it (and save resources) set it to false
})

Player-Create-Options

Variable-NameValueDefaultDescription
guildstring-The Guild-ID of the Player
voiceChannelstring-The VOICE-CHANNEl-ID of the Player
textChannelstring-The TEXT-CHANNEl-ID of the Player
selfMutebooleanfalseIf the Player shall join self-muted
selfDeafenbooleantrueIf the Player shall join self-Deafend
regionstring-In What VC-Region this Player is in (regioning support)
instaUpdateFiltersFixbooleantrueIf it should update Filters instantly, when applying

Player-Methods / Properties


Everything about the Utils

Utils


Examples & Tutorials

Regioning System

When creating the node(s) pass the variable regions, to auto-select the right region based on what region u provide in the players.create(options#region) options!

const nodes = [
  {
    identifier: "USA_NODE", host, password, port, version: "v3", useVersionPath: true,
    regions: ["us-east", "us-central", "us-south", "us-west", "brazil"],
  },
  {
    identifier: "GERMANY_NODE", host, password, port, version: "v3", useVersionPath: true,
    regions: ["rotterdam", "russia"],
  },
]

When creating the Player, pass the rtcRegion like that:

const { channel } = message.member.voice;
const player = client.musicManager.create({
  region: channel?.rtcRegion || undefined, // region
  guild: channel.guild.id,
  voiceChannel: channel.id,
  textChannel: message.channel.id,
  selfDeafen: true,
});

// searching tracks requires you to pass the node now!
client.musicManager.search(query, requester, player.node);


// or search via player, so you don't need to pass the node:
player.search(query, requester);

IMPORTANT NOTE!

When using regioning, you must SEARCH on the same NODE as you play the TRACK on, best practice can be found in the JS script above for the search() method, just provide the correct node (the node the player is using)


How to use custom Sources

Lavalink provides default sources from: scsearch, ytsearch, ytmsearch, so that you don't need to type these weird names, you can simply type soundcloud or youtube or youtube music. However since Lavalink supports Plugins too, you can now also use them to search on things like: Spotify, Deezer, etc. Use the LavaSrc Plugin for that! -> so you can also pass as a source spotify, or short: sp Check out SearchPlatforms to figure what all you can use!

const source = "yt"; // "yt" / "ap" / "sp" / "sc" / "ytm" / "..."
// request possibility
client.musicManager.search({query, source}, requester, player.node);
// e.g. search on spotify
client.musicManager.search({
   query: "Adele - Hello",
   source: "spotify", // "sp"/ "spsearch" / "sprec" / etc. check out: SearchPlatforms on what you can all use!
   // or for deezer: source: "dz" / "deezer"
}, interaction.user, player.node);

How to make filters apply instantly

Added the Player-Create-Property #instaUpdateFiltersFix Updates filters and Equalizers almost instantly, by seeking to the same player position after a filter/eq got applied -> On default it's set to: true aka enabled, if your system is not able to handle the extra load, disable it!

const player = client.musicManager.create({
    ...PlayerOptions,
    instaUpdateFiltersFix: true, // to disable it (and save resources) set it to false
})

Update the Player Position way faster

Lavalink sends the Player Stats (Position) every 5s. With the newest Lavalink Version you can adjust that, however if it's less than 2s your CPU Usage goes crazy high!! -> To make it simpler, you can count the position on your Manager! -> the default value is: 250 (in ms) to adjust / change that, do it with:

new Manager({
    ...ManagerOptions,
    position_update_interval: 150
})

It is client sided, not server sided, means, that on your client player.position will be more accurate, if the server changes, the client will be updated either way by the server (real) data.

Added Manager#Property: volumeDecrementer

// e.g: new Manager({volumeDecrementer:0.75}); will send to lavalink 75% of the volume not 100%, aka you will hear 75% of the set volume, tho see in the player.volume property the full 100% aka if you do player.setVolume(100); then player.volume == 100 but lavalink receives 75

  • I use smt between 0.5 - 0.75 cause then the volume is not TOO LOUD at 100% (default) and not really earrapy at 150% (which users like to do when trolling ;) ) (for reference 1 is the same as not adding the option)

This lib works for many discord Libs

I tested it in: discord.js, discordeno, oceanicJS, eris. -> U just have to be sure, to pass all IDs as STRINGS (as that'S discord.js's default caching value-type)

Inbuilt Filters

Variables to see which Filter is active:

player.filters.nightcore // READONLY - {Boolean|String} if it's enabled or not
player.filters.rotating // READONLY - {Boolean} if it's enabled or not
player.filters.tremolo // READONLY - {Boolean} if it's enabled or not
player.filters.vibrato // READONLY - {Boolean} if it's enabled or not
player.filters.lowPass // READONLY - {Boolean} if it's enabled or not
player.filters.karaoke // READONLY - {Boolean} if it's enabled or not
player.filters.audioOutput // READONLY - {"stereo"|"mono"|"right"|"left"} how the audio is getting outputted

// only available with lavalink-filter-plugin
player.filters.echo // READONLY - {Boolean} if it's enabled or not

Functions to set the Filters:

-> You can even use custom Data if you want, just pass them to the function parameter(s)!

player.toggleRotating(rotationHz = 0.2);
player.toggleVibrato(frequency = 2, depth = 0.5);
player.toggleTremolo(frequency = 2, depth = 0.5);
player.toggleLowPass(smoothing = 20);
player.toggleNightcore(speed = 1.2999999523162842, pitch = 1.2999999523162842, rate=1);
player.toggleKaraoke(level = 1, monoLevel = 1, filterBand = 220, filterWidth = 100);
// default: stereo, rightChannel is right 100% and left 0%, and leftChannel is invert of rightChannel
player.setAudioOutput("stereo"|"mono"|"right"|"left");

// reset all filters to default
player.resetFilters();

// only available with lavalink-filter-plugin
player.toggleEcho(delay = 1, decay = 0.5);

Added Manager#validUnresolvedUris for unresolved tracks

  • If an unresolved Track is saved from lavalink plugins, like spotify and u wanna search on spotify with that uri, then u should do this:
const Manager = new Manager({
    ...,
    validUnresolvedUris: ["spotify.com"]
})

Self Made Plugins

You can also use my plugins, which make are better then their originals due to some things missing..

Added Manager#forceLoadPlugin for forcing plugin loadings

If you get errors like does not extend plugin, then you can do this to force load it!

const Manager = new Manager({
    ...,
    forceLoadPlugin: true,
})

Added manager.init("clientId", { clientId, clientName, shards })

This allows it, that you can add data from the client once you init the manager! Like clientName or shards Count, you should init the manager either way, once it's ready!

Added More player parameters

 - player.createdAt Date object, when the player was created in LAVALINK  - player.createdTimeStamp Date Timestamp in MS, when the player was created in LAVALINK  - player.connected Lavalink connected state, if it's true, lavalink thinks player is connected...  - player.payload Last payload from playerUpdate Event lavalink sent  - player.ping Ping in ms from Lavalink server. if it's less then 0, it means player is not connected yet  - player.wsPing Ping in ms from Lavalink server. if it's less then 0, it means player is not connected yet

Added Link (Url) Query Validation

  • Manager#onlyAllowAllowedLinks --> must be true to make it work (default: true)  - Manager#allowedLinks Array of Strings of Urls which are allowed  - Manager#allowedLinksRegexes Array of Regexes which must match the link     - Manager#regex --> Default Regexes applied to allowedLinksRegexes, if not Set (except spotify and deezer links) If provided link to query is not valid, then it throws an error,

e.g.

new Manager({

  allowedLinksRegexes: [

    Manager.regex.YoutubeRegex,

    Manager.regex.YoutubeMusicRegex,

    Manager.regex.SoundCloudRegex,

    Manager.regex.SoundCloudMobileRegex,

    // Manager.regex.AllDeezerRegex,

    // Manager.regex.AllSpotifyRegex,

    Manager.regex.mp3Url,

    Manager.regex.m3uUrl,

    Manager.regex.m3u8Url,

    Manager.regex.mp4Url,

    Manager.regex.m4aUrl,

    Manager.regex.wavUrl,

  

    //Manager.regex.vimeo,

    //Manager.regex.TwitchTv,

    //Manager.regex.appleMusic,

    //Manager.regex.bandcamp,

    //Manager.regex.radiohost,

    //Manager.regex.yandexmusic,

  ] // these are the default Values with comments, are all available, you can add your custom regexes if you want like this: /regex/ or new RegExp("regex", "flags");

})

// or: accept all the provided regexes:

new Manager({

  ...,

  allowedLinksRegexes: Object.values(Manager.regex),

})

EVERYTHING BELOW IS OLD

-> These are from the old readme of erela.js -> Will remove/adjust it soon!

   

   

   

   

Erela was transferred to MenuDocs, because I no longer wish to work with Discord related development. It will from now on be maintained by [MenuDocs](https://github.com/MenuDocs). ~ @Solaris9 + now discontinued

Documentation & Guides

  • [Documentation](https://erelajs-docs.netlify.app/docs/gettingstarted.html "Erela.js Documentation") - discontinued

  • [Guides](https://erelajs-docs.netlify.app/guides/introduction.html "Erela.js Guides") - discontinued

Prerequisites

  • Java - [Azul](https://www.azul.com/downloads/zulu-community/?architecture=x86-64-bit&package=jdk "Download Azul OpenJDK"), [Adopt](https://adoptopenjdk.net/ "Download Adopt OpenJDK") or [sdkman](https://sdkman.io/install "Download sdkman")

  • [Lavalink](https://ci.fredboat.com/viewLog.html?buildId=lastSuccessful&buildTypeId=Lavalink_Build&tab=artifacts&guest=1 "Download Lavalink")

Note: Java v11 or newer is required to run the Lavalink.jar. Java v13 is recommended. If you are using sdkman then its a manager, not Java, you have to install sdkman and use sdkman to install Java

Warning: Java v14 has issues with Lavalink.

Installation

NPM
npm install erela.js
Yarn
yarn add erela.js

Note: Node v16 is required!

Getting Started

  • Create an application.yml file in your working directory and copy the [example](https://github.com/freyacodes/Lavalink/blob/master/LavalinkServer/application.yml.example "application.yml file") into the created file and edit it with your configuration.

  • Run the jar file by running java -jar Lavalink.jar in a Terminal window.

Example usage

Please read the guides to start: <https://erelajs-docs.netlify.app/docs/gettingstarted.html#example-usage>

Plugins

You can use plugins below to extend Erela.js' features easily.

Note: These are the only ones shown before being published, check the GitHub repository for a complete list.

  • [erela.js-spotify](https://github.com/MenuDocs/erela.js-spotify) - Converts a Spotify URL into a UnresolvedTrack to play later.

Discordeno

Script for discordeno (sending data to shards)

bot.musicManager = new Manager({
    volumeDecrementer: 0.75,
    position_update_interval: 100,
    nodes: [
        {
            identifier: `Node_1`,
            host: "localhost",
            port: 2333,
            password: "youshallnotpass"
        }
    ],

    // A send method to send data to the Discord WebSocket using your library.
    // Getting the shard for the guild and sending the data to the WebSocket.
    send(id, payload) {
        const shardId = bot.utils.calculateShardId(bot.gateway, BigInt(id));
        // somehow get the shard
        const shard = bot.gateway.shards.get(shardId);
        shard.send(payload);
        // if your rest is hosted seperately then just do your typical shard request(s)

    },

});


// in raw event
import { VoiceState, VoicePacket, VoiceServer } from "erela.js";
// code...
switch (data.t) {
  case "VOICE_SERVER_UPDATE":
  case "VOICE_STATE_UPDATE":
    bot.musicManager.updateVoiceState(data.d as VoiceState | VoiceServer | VoicePacket)
  break;
}
// code ...

Example Manager + Usage

const { Manager } = require("erela.js"); // npm i Tomato6966/erela.js
const Deezer = require("better-erela.js-deezer"); // npm i Tomato6966/better-erela.js-deezer
const BandCampSearch = require("erela.js-bandcamp-search"); // npm i Tomato6966/erela.js-bandcamp-search"
  

// creation

client.musicManager = new Manager({
    defaultSearchPlatform: "ytsearch", // "ytmsearch" / "ytsearch" / "deezer" / "scsearch" // etc. etc. deezer only valid if you have MY better-erela.js-deezer plugin installed!
    handleError: false, // if true, you have to handle what happens when an Error happens, if false it auto skips!
    handleStuck: false, // if true, you have to handle what happens when an track gets stucked happens, if false it auto skips!
    volumeDecrementer: 0.75, // instead of sending 100% it sends 75%
    position_update_interval: 100, // update the player.position every 100ms
    nodes: [
        {
            identifier: `Use_Node_1`,
            port: 2333,  host: "localhost", // ip.address. e.g. 127.0.0.1
            regions: ["us-east", "us-central", "us-south", "us-west", "brazil"], // example regions
            password: "youshallnotpass",
            retryAmount: 10,
            retryDelay: 7500,
            version: "v3",
            useVersionPath: true,
        },
        {
            identifier: `GERMANY_Node_1`,
            port: 2333, host: "localhost", // ip.address. e.g. 127.0.0.1
            regions: ["rotterdam", "russia"],
            password: "milrato_pass_3569",
            retryAmount: 10, retryDelay: 7500,
            version: "v2",
            useVersionPath: false,
        }
    ],
    // every base-url provided in here, will be resolved once the track is beeing tryed to play, aka fetched by lavalink.
    validUnresolvedUris: [
        "spotify.com",  // only if your lavalink has spotify plugin
        "twitch.com",
        "twitch.tv",
        "vimeo.com",
        "bandcamp.com",
        "music.apple.com", // only if your lavalink has apple music plugin
    ],
    plugins: [
        new Deezer(), // not needed if your lavalink has the deezer plugin
        new BandCampSearch({
            querySource: ["bandcamp", "bc"],
        }),
    ],
    shards: client.ws.totalShards || 1,
    clientName: client.user?.username,
    clientId: client.user?.id || client.id,
    send(id, payload) {
        const guild = client.guilds.cache.get(id);
        if(!guild) return;
        guild.shard.send(payload);
    },
});

// listen to the manager events:
client.musicManager
    .on("nodeCreate", (node) => {
        console.log(`Created the Node: ${node.options.identifier} on host: ${node.options.host}`);
    })
    .on("nodeConnect", (node) => {
        console.log(`Connection to the Node: ${node.options.identifier} on host: ${node.options.host} was successful`);
    })
    .on("nodeReconnect", (node) => {
        console.log(`The Node: ${node.options.identifier} on host: ${node.options.host} is now attempting a reconnect`);
    })
    .on("nodeDisconnect", (node) => {
        console.error(`Connection of the Node: ${node.options.identifier} on host: ${node.options.host}, disconnected`);
    })
    .on("nodeError", (node, error) => {
        console.error(`Node: ${node.options.identifier} on host: ${node.options.host} errored:`, error);
    })
    .on("nodeRaw", (payload) => {
        // don't do much with it, the lib handles it
    })
    .on("playerCreate", (player) => {
        const textChannel = client.channels.cache.get(player.textChannel);
        console.log(`Player got Created in: ${player.guild}`);
        if(textChannel) textChannel.send({ content: `Player Created!` });
    })
    .on("playerDestroy", (player) => {
        const textChannel = client.channels.cache.get(player.textChannel);
        console.log(`Player got Destroyed in: ${player.guild}`);
        if(textChannel) textChannel.send({ content: `Player Destroyed!` });
    })
    .on("queueEnd", (player, track, payload) => {
        const textChannel = client.channels.cache.get(player.textChannel);
        console.log(`Queue Ended in: ${player.guild}`);
        if(textChannel) textChannel.send({ content: `Queue Ended` });
    })
    .on("playerMove", (player, initChannel, newChannel) => {
        if(player.voiceChannel === newChannel) return;
        // all of the code should not be necessary, but sometimes it fixes a bug
        player.voiceChannel = newChannel; // overwride the voiceChannel with the newChannel
        if (player.paused) return; // if it's paused keep it 
        setTimeout(() => {
            player.pause(true); // else pause and unpause to continue playing
            setTimeout(() => player.pause(false), 150);
        }, 150);
    })
    .on("playerDisconnect", (player, channel) => {
        console.log("The player disconnected");
        player.destroy();
    })
    .on("trackStart", (player, track, payload) => {
        const textChannel = client.channels.cache.get(player.textChannel);
        console.log(`Track Started: ${player.guild}`);
        if(textChannel) textChannel.send({ content: `Now Playing **${track.title}**: ${track.uri}` });
    })
    .on("trackEnd", (player, track, payload) => {
        console.log(`Track Ended: ${player.guild}`);
    })
    .on("trackStuck", (player, track, payload) => {
        const textChannel = client.channels.cache.get(player.textChannel);
        console.log(`Track Stucked: ${player.guild}`);
        if(textChannel) textChannel.send({ content: `Track Stucked **${track.title}**: ${track.uri}` });
    })
    .on("trackError", (player, track, payload) => {
        const textChannel = client.channels.cache.get(player.textChannel);
        console.log(`Track Errored: ${player.guild}`);
        if(textChannel) textChannel.send({ content: `Track Errored **${track.title}**: ${track.uri}` });
        player.stop();
    })

// init the manager
client.on("ready", () => {
  client.musicManager.init(client.user.id, {
    shards: client.ws.totalShards,
    clientName: client.user.username,
    clientId: client.user.id,
  });
})

// send voicestate updates
client.on("raw", (data) => {
    switch(data.t) {
        case "VOICE_SERVER_UPDATE":
        case "VOICE_STATE_UPDATE":
            client.musicManager.updateVoiceState(data.d)
        break;
    }
});


client.on("interactionCreate", await (interaction) => {
    if(!interaction.isCommand() && interaction.commandName !== "play") return; // if it's a wrong command
    
    const query = interaction.getString("query"); // `eminem without me`
    if(!interaction.member.voice.channel) return interaction.reply({ content: "Please join a VC" });
    // example how to search with rtcRegion
    const player = client.musicManager.create({
      region: interaction.member.voice.channel?.rtcRegion || undefined,
      guild: interaction.guildId,
      voiceChannel: interaction.member.voice.channel.id, // message.member.voice.channel.id,
      textChannel: interaction.channel.id,
      selfDeafen: true,
    });

    // join vc
    if(!player.connected) {
        await player.connect();
        await player.stop();
    }
    // don't provide a source --> it searches on defaultSearchPlatform
    // the source you provide will be used as a search platform!
    const result = await player.search({ query, source: "soundcloud" }, interaction.user);
    // wanna search via manager?       const result = await client.musicManager.search({ query, source: "soundcloud" }, interaction.user, player.node); // PROVIDE THE PLAYERNODE

    if(!result.tracks[0]) return interaction.reply({ content: "Nothing found" });
    
    // play track or add song to queue
    if(!player?.queue?.totalSize || (!player.paused && !player.playing)) { 
        // add multiple tracks: player.queue.add([...result.tracks]);
        player.queue.add(result.tracks[0]); // add track
        player.play({
            pause: false,
            volume: 100,
            startTime: 0,
        }); 
        if (!player.paused && !player.playing) player.pause(false);
        interaction.reply({ content: `Now playing: **${player.queue.current.title}**: ${player.queue.current.uri}` });
    } else {
        player.queue.add(result.tracks[0]); // add track
        interaction.reply({ content: `Added the song the queue: **${result.tracks[0].title}**: ${result.tracks[0].uri}` });
    }
});
// recommended events to listen:
client.on("channelDelete", channel => {
    const player = client.musicManager.players.get(channel.guild.id);
    if(!player) return;
    if(channel.id === player.voiceChannel) player.destroy();
    if(channel.id === player.textChannel) player.textChannel = null;
});
// recommended events to listen:
client.on("guildRemove", guild => {
    const player = client.musicManager.players.get(guild.id);
    if(!player) return; 
    player.destroy();
});

Contributors & Credits

  • Thanks to [menudocs](https://github.com/menudocs) and their Original version of [erela.js](https://github.com/menudocs/erela.js)
  • They discontinued the library, so I decided to continue coding it, keeping it up to date and adding new features!

👤 Tomato6966

  • Maintainer + Improver + Author of the New Version
  • Website: <https://tomato6966.xyz>
  • Github: [@Tomtao6966](https://github.com/Tomato6966)

👤 Solaris

  • Author - Old Version
  • Website: <https://solaris.codes/>
  • Github: [@Solaris9](https://github.com/Solaris9)

👤 Anish Shobith

  • Contributor - Old Version
  • Github: [@Anish-Shobith](https://github.com/Anish-Shobith)

👤 ayntee

  • Contributor - Old Version
  • Github: [@ayntee](https://github.com/ayntee)