npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

lavalink-handler

v1.0.0

Published

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)](#discordeno), eris, oceanic and many other libraries too!

Downloads

1

Readme

erela.js - Updated & Improved by BENx2115

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

You need a Lavalink Server!

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 Name | Value | Default | Description | | ---------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | nodes | NodeOptions[] | DefaultNode | What Nodes to use for this Manager | | clientId | string | - | the Bot-Client Id (Snowflake) / any | | clientName | string | "erela.js" | Value for the Lavalink Client-Name header | | shards | number | 1 | The Shard count | | plugins | Plugin[] | - | Array of erela.js Plugins | | forceLoadPlugin | boolean | true | If Plugins should be loaded, no matter which class they extend | | autoPlay | boolean | true | Wheter it should skip to the next song in the queue on Errors, songFinish, etc. | | trackPartial | string[] | - | an array of track Properties to keep | | defaultSearchPlatform | SearchPlatform | "youtube" | What Searchplatform to use, when searching without a source | | volumeDecrementer | number | 1 | how 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_interval | number | 250 | in how many ms interval steps, the player-position should be incremented client sided ( Lavalink sends it every N seconds [defualt configuration is 5s] ) | | validUnresolvedUris | string[] | ["www.youtu", "music.youtu", "soundcloud.com"] | What Urls are allowed to be directly used from lavalink, when resolving unresolved tracks | | allowedLinks | string[] | - | Array of URLS to allow which you are too lazy to write REGEXes of (must be 1:1) | | allowedLinksRegexes | RegExp[] | Manager#regex | all regexes which allow URLS (if 1 regex matches a given url, the url will be used) | | onlyAllowAllowedLinks | boolean | true | if 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) | | forceSearchLinkQueries | boolean | true | If it should force-load links via Manager#searchLink (which should happen automatically, but this forces it) | | useUnresolvedData | boolean | true | If it should use unresolved Tracks Data uppon their resolved ones | | userAgent | string | "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 | | restTimeout | number | - | Amount of time to wait until the Rest Methods are rejected | | send | Function(id: string, payload:Payload) | - | The function to send payload to discord... | | applyVolumeAsFilter | Boolean | false | If 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-Name | returnData | Description | | | -------------------------------------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | --- | | regex | Record<SourcesRegex, RegExp> | An Object of all default Source Regexes, which you may use too! | | | DEFAULT_SOURCES | Record<SearchPlatform, string> | An Object of all SearchPlatform strings you may use, to convert to lavalink sources | | | players | Collection<guildId:string, Player> | A Collection of all Players | | | nodes | Collection<nodeIdentifier:string, node> | A Collection of all Nodes | | | options | ManagerOptions | all Options given to the Manager | | | leastUsedNodes | Collection<nodeIdentifier:string, Node> | A Collection of all Nodes, but sorted by their Calls/Players/Memory Usages (Manager#defaultLeastUsedNodeSortType) | | | leastUsedNodesCalls | Collection<nodeIdentifier:string, Node> | A Collection of all Nodes, but sorted by their Calls Usages | | | leastUsedNodesPlayers | Collection<nodeIdentifier:string, Node> | A Collection of all Nodes, but sorted by their Players Usages | | | leastUsedNodesMemory | Collection<nodeIdentifier:string, Node> | A Collection of all Nodes, but sorted by their Memory Usages | | | leastLoadNodes | Collection<nodeIdentifier:string, Node> | A Collection of all Nodes, but sorted by their Cpu/Memory Usages (Manager#defaultLeastLoadNodeSortType) | | | leastLoadNodesMemory | Collection<nodeIdentifier:string, Node> | A Collection of all Nodes, but sorted by their Memory Usages | | | leastLoadNodesCpu | Collection<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<SearchResult> | 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<SearchResult> | Search for a Link on Lavalink | | | decodeTracks(tracks:string[]) | Promise<TrackData[]> | Decode Tracks from Lavalink | | | decodeTrack(track:string) | Promise<TrackData> | Decode a single Track | | | create(PlayerOptions) | Player | Create a Player | | | get(guildId:string) | Player | get a Player from a guildId | | | destroy(guildId:string) | void | Destroy a Player of a GuildId | | | createNode(NodeOptions) | Node | Creates a Node | | | destroyNode(NodeIdentifier:string) | void | Destroy a Node | | | updateVoiceState(data) | Promise<void> | update the Voice State to Lavalink | |

Manager-Events

| Event-Names | Parameters | Description | |------------------|-------------------------------------------------|------------------------------------------------------| | nodeCreate | node | Emitted once a node gets Created | | nodeConnect | node | Emits when a node Connects | | nodeReconnect | node | Emits when a node attempts a reconnect | | nodeDisconnect | node, reason: { code?: number, reason?: string} | Emits when a node disconnects | | nodeError | node, error | Emits when a node throws errors | | nodeRaw | payload | Emits every payload from a Node | | playerCreate | player | Emits when a player gets created | | playerDestroy | player | Emits when a player get's destroyed | | queueEnd | player, track, payload | Emits when the queue End | | playerMove | player, initChannel, newChannel | Emits when the player moves from a VC to another one | | playerDisconnect | player, oldChannel | Emits when the player Leaves the VC | | trackStart | player, track, payload | Emits when a track starts to play | | trackEnd | player, track, payload | Emits when a track ends playing | | trackStuck | player, track, payload | Emits when a track gets stucked and skips the track | | trackError | player, track, payload | Emits when a track errors and skips it | | socketClosed | player, payload | Emits 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-Name | Value | Default | Description | |-----------------|---------------------|-------------------|---------------------------------------------------------------------------------------------------| | host | string | - | The Hostname / Ipaddress of your Lavalink-Node | | port | number | 2333 | The Port of the Node | | password | string | "youshallnotpass" | The Authentication Creditentals aka Password | | secure | boolean | false | If it shall use Port 443 and https | | identifier | string | - | The identifier of the Node (for your manager#nodes Collection key) | | retryAmount | number | 5 | How often it shall retry to connect on fails | | retryDelay | number | 30e3 | How long to wait between each retry | | requestTimeout | number | 10e3 | How long to wait until a REST-Request gets rejected | | poolOptions | undici.Pool.Options | - | The Undici Pool Options | | regions | string[] | - | Discord Voice Regions array (for that node) to support regioning | | version | "v2" | "v3" | - | Lavalink Version (use v3 for REST API) | | useVersionPath | boolean | - | 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-Name | Value | Default | Description | |-----------------------|---------|---------|---------------------------------------------------------| | guild | string | - | The Guild-ID of the Player | | voiceChannel | string | - | The VOICE-CHANNEl-ID of the Player | | textChannel | string | - | The TEXT-CHANNEl-ID of the Player | | selfMute | boolean | false | If the Player shall join self-muted | | selfDeafen | boolean | true | If the Player shall join self-Deafend | | region | string | - | In What VC-Region this Player is in (regioning support) | | instaUpdateFiltersFix | boolean | true | If 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"]
})

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. ~ @Solaris9 + now discontinued

Documentation & Guides

Prerequisites

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 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.

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");
  

// 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 and their Original version of erela.js
  • They discontinued the library, so I decided to continue coding it, keeping it up to date and adding new features!

👤 Solaris

👤 Anish Shobith

👤 ayntee

  • Contributor - Old Version

  • Github: @ayntee