Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

shoukaku

Package Overview
Dependencies
Maintainers
1
Versions
71
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

shoukaku - npm Package Compare versions

Comparing version 1.0.0 to 1.1.0

81

index.d.ts

@@ -5,4 +5,15 @@ declare module 'shoukaku' {

export const version: string;
export const version: string;
export class ShoukakuError extends Error {
constructor(message: string);
public name: string;
}
export class ShoukakuTimeout extends Error {
constructor(message: string);
public name: string;
}
export interface Track {

@@ -27,3 +38,3 @@ track: string;

export type LoadTrackType =
export type LoadTrackType =
'TRACK_LOADER' | 'PLAYLIST_LOADED' | 'SEARCH_RESULT' |

@@ -95,2 +106,3 @@ 'NO_MATCHES' | 'LOAD_FAILED';

reconnectTries?: number;
moveOnDisconnect?: boolean;
restTimeout?: number;

@@ -154,3 +166,3 @@ }

on(event: 'end', listener: (reason: Reason) => void): this;
on(event: 'error', listener: (err: Error) => void): this;
on(event: 'error', listener: (err: ShoukakuError | Error) => void): this;
on(event: 'nodeDisconnect', listener: (name: string) => void): this;

@@ -161,3 +173,3 @@ on(event: 'resumed', listener: () => void): this;

once(event: 'end', listener: (reason: Reason) => void): this;
once(event: 'error', listener: (err: Error) => void): this;
once(event: 'error', listener: (err: ShoukakuError | Error) => void): this;
once(event: 'nodeDisconnect', listener: (name: string) => void): this;

@@ -168,3 +180,3 @@ once(event: 'resumed', listener: () => void): this;

off(event: 'end', listener: (reason: Reason) => void): this;
off(event: 'error', listener: (err: Error) => void): this;
off(event: 'error', listener: (err: ShoukakuError | Error) => void): this;
off(event: 'nodeDisconnect', listener: (name: string) => void): this;

@@ -177,3 +189,3 @@ off(event: 'resumed', listener: () => void): this;

export class ShoukakuPlayer extends EventEmitter {
constructor(link: ShoukakuLink);
constructor(node: ShoukakuSocket, guild: Guild);
public voiceConnection: ShoukakuLink;

@@ -186,4 +198,6 @@ public track: string | null;

public connect(options: unknown, callback:(error: Error, link: ShoukakuLink) => void): void;
private connect(options: unknown, callback:(error: ShoukakuError | Error | null, player: ShoukakuPlayer) => void): void;
public disconnect(): void;
public moveToNode(name: string): Promise<void>;

@@ -199,3 +213,3 @@ public playTrack(track: string, options?: ShoukakuPlayOptions): Promise<boolean>;

private _clearTrack(): void;
private _clearPlayer(): void;
private _clearBands(): void;
private _resume(): Promise<void>;

@@ -205,3 +219,3 @@ }

export class ShoukakuLink {
constructor(node: ShoukakuSocket, guild: Guild);
constructor(node: ShoukakuSocket, player: ShoukakuPlayer, guild: Guild);
public node: ShoukakuSocket;

@@ -220,21 +234,15 @@ public player: ShoukakuPlayer;

private lastServerUpdate: unknown | null;
private _callback: (err: Error | null, player: ShoukakuPlayer) => void | null;
private _callback: (err: ShoukakuError | Error | null, player: ShoukakuPlayer) => void | null;
private _timeout: number | null;
public build: {
self_deaf: boolean;
self_mute: boolean;
channel_id: string;
session_id: string;
};
private stateUpdate(data: unknown);
private serverUpdate(data: unknown);
private serverUpdate: unknown;
private _connect(d: unknown, callback: (err: Error | null, player: ShoukakuPlayer) => void);
private _connect(d: unknown, callback: (err: ShoukakuError | Error | null, player: ShoukakuPlayer) => void);
private _disconnect(): void;
private _send(d: unknown): void;
private _move(): Promise<void>;
private _sendDiscordWS(d: unknown): void;
private _clearVoice(): void;
private _destroy(): void;
private _voiceUpdate(event: unknown): void;
private _voiceDisconnect(): void;
private _destroy(): Promise<boolean>;
private _voiceUpdate(): Promise<boolean>;
private _nodeDisconnected(): void;

@@ -264,7 +272,7 @@ }

public joinVoiceChannel(options: ShoukakuJoinOptions): Promise<ShoukakuPlayer>;
public leaveVoiceChannel(guildID: string): void;
private send(data: unknown): Promise<boolean>;
private _configureResuming(): Promise<boolean>;
private _configureCleaner(state: boolean): void;
private _executeCleaner(): void;
private _executeCleaner(): Promise<void>;
private _upgrade(response: unknown): void;

@@ -279,16 +287,16 @@ private _open(): void;

on(event: 'debug', listener: (name: string, data: unknown) => void): this;
on(event: 'error', listener: (name: string, error: Error) => void): this;
on(event: 'error', listener: (name: string, error: ShoukakuError | Error) => void): this;
on(event: 'ready', listener: (name: string, reconnect: boolean) => void): this;
on(event: 'closed', listener: (name: string, code: number, reason: string) => void): this;
on(event: 'disconnected', listener: (name: string, reason: string) => void): this;
on(event: 'closed', listener: (name: string, code: number, reason: string | null) => void): this;
on(event: 'disconnected', listener: (name: string, reason: string | null) => void): this;
once(event: 'debug', listener: (name: string, data: unknown) => void): this;
once(event: 'error', listener: (name: string, error: Error) => void): this;
once(event: 'error', listener: (name: string, error: ShoukakuError | Error) => void): this;
once(event: 'ready', listener: (name: string, reconnect: boolean) => void): this;
once(event: 'closed', listener: (name: string, code: number, reason: string) => void): this;
once(event: 'disconnected', listener: (name: string, reason: string) => void): this;
once(event: 'closed', listener: (name: string, code: number, reason: string | null) => void): this;
once(event: 'disconnected', listener: (name: string, reason: string | null) => void): this;
off(event: 'debug', listener: (name: string, data: unknown) => void): this;
off(event: 'error', listener: (name: string, error: Error) => void): this;
off(event: 'error', listener: (name: string, error: ShoukakuError | Error) => void): this;
off(event: 'ready', listener: (name: string, reconnect: boolean) => void): this;
off(event: 'closed', listener: (name: string, code: number, reason: string) => void): this;
off(event: 'disconnected', listener: (name: string, reason: string) => void): this;
off(event: 'closed', listener: (name: string, code: number, reason: string | null) => void): this;
off(event: 'disconnected', listener: (name: string, reason: string | null) => void): this;
}

@@ -310,7 +318,6 @@

public addNode(nodeOptions: ShoukakuNodeOptions): void;
public removeNode(name: string, libraryInvoked?: boolean): void;
public getNode(name?: boolean | string): ShoukakuSocket;
public removeNode(name: string, reason?: string): void;
public getNode(name?: string): ShoukakuSocket;
public getLink(guildId: string): ShoukakuLink | null;
private send(payload: unknown): void;
private _ready(name: string, resumed: boolean): void;

@@ -317,0 +324,0 @@ private _reconnect(name: string, code: number, reason: string): void;

{
"name": "shoukaku",
"version": "1.0.0",
"version": "1.1.0",
"description": "A lavalink client for Discord.js v12 only",
"main": "index.js",
"scripts": {
"doc": "docma"
"doc": "docma"
},

@@ -30,3 +30,3 @@ "types": "index.d.ts",

"node-fetch": "^2.6.0",
"ws": "^7.0.0"
"ws": "^7.1.2"
},

@@ -33,0 +33,0 @@ "devDependencies": {

@@ -1,2 +0,8 @@

# Shoukaku
## Shoukaku
[![Discord](https://img.shields.io/discord/423116740810244097?style=flat-square)](https://discordapp.com/invite/FVqbtGu)
[![npm](https://img.shields.io/npm/v/shoukaku?style=flat-square)](https://www.npmjs.com/package/shoukaku)
![Github Stars](https://img.shields.io/github/stars/Deivu/Shoukaku?style=flat-square)
![GitHub issues](https://img.shields.io/github/issues-raw/Deivu/Shoukaku?style=flat-square)
![Snyk Vulnerabilities for npm package](https://img.shields.io/snyk/vulnerabilities/npm/shoukaku?style=flat-square)
![NPM](https://img.shields.io/npm/l/shoukaku?style=flat-square)
<p align="center">

@@ -15,14 +21,12 @@ <img src="https://vignette.wikia.nocookie.net/kancolle/images/c/c8/Shoukaku_Full.png/revision/latest">

### Why Shoukaku?
✅ Straightforward
✅ Designed to used in Discord.JS v12
✅ Scalable
✅ Straightforward, Maintained, and Reliable.
✅ Reliable
✅ Stable for long term usage.
✅ Maintained
✅ Offers features that other libraries don't have.
✅ Very Cute and Charming Shipgirl ❤
✅ Very cute and reliable Shipgirl ❤ (Important)
✅ And will make your library weeb 😂
### Documentation

@@ -41,53 +45,5 @@ https://deivu.github.io/Shoukaku/?api

### 0.2.x -> 1.0.x Migration
### Changelogs
You can view it on [CHANGELOGS.MD](https://github.com/Deivu/Shoukaku/blob/master/CHANGELOGS.MD) file in this repository.
> 0.2.x and earlier
```js
<Shoukaku>.build({ /* buildOptions here */ });
```
> 1.0.0 and newer
```js
<Shoukaku>.start({ /* buildOptions here */ });
```
### 0.1.x -> 0.2.x Migration
> ShoukakuLink is now a property of ShoukakuPlayer, meaning all link related getters are changed to player getters.
> You can access ShoukakuLink via .voiceConnection property of ShoukakuPlayer
> ShoukakuPlayer events are "GREATLY CHANGED". Those marked as optional can be left out.
The events is as follows
- end
- closed
- error
- nodeDisconnect
- trackException "optional"
- resumed "optional"
- playerUpdate "optional"
> Shoukaku class have renamed methods and properties
- getLink() -> getPlayer()
- .links -> .players
- .totalLinks -> .totalPlayers
> ShoukakuSocket also have a renamed property
- .links -> .players
> You don't disconnect / clean the player on ShoukakuLink but on ShoukakuPlayer now making your code more clean and better
> 0.1.1 and earlier
```js
<ShoukakuLink>.player.playTrack();
<ShoukakuLink>.disconnect();
```
> 0.2.x and newer
```js
<ShoukakuPlayer>.playTrack();
<ShoukakuPlayer>.disconnect();
```
You can see more of the changes on updated example below.
### Support Server

@@ -110,14 +66,12 @@ If you need help on using this, Join Here [ShipGirls Community](https://discordapp.com/invite/FVqbtGu) and `ask at #support`.

### More simple implementation w/o queue.
### Really simple example of using this.
```js
const { Client } = require('discord.js');
const { Shoukaku } = require('shoukaku');
const MyLavalinkServer = [
{
name: 'my_lavalink_server',
const MyLavalinkServer = [{
name: 'Lewd Server',
host: 'localhost',
port: 6969,
auth: 'owo_your_password'
}
];
auth: 'do not guess my password'
}];
const client = new Client();

@@ -127,2 +81,3 @@

const Carrier = new Shoukaku(client, {
moveOnDisconnect: false,
resumable: false,

@@ -144,3 +99,3 @@ resumableTimeout: 30,

client.on('ready', () => {
// You need to build shoukaku on your client's ready event for her to work like how its done in this example.
// Connecting Shoukaku to Lavalink Nodes.
Carrier.start(MyLavalinkServer, { id: client.user.id });

@@ -147,0 +102,0 @@ console.log('Bot Initialized');

@@ -85,2 +85,3 @@ /**

* @property {number} [reconnectTries=2] Amount of tries to connect to the lavalink Node before it decides that the node is unreconnectable.
* @property {number} [moveOnDisconnect=false] Specifies if the library will attempt to reconnect players on a disconnected node to another node.
* @property {number} [restTimeout=10000] Timeout on rest requests to your lavalink node.

@@ -94,2 +95,3 @@ * @memberof ShoukakuConstants#

reconnectTries: 2,
moveOnDisconnect: false,
restTimeout: 10000

@@ -96,0 +98,0 @@ };

@@ -0,2 +1,10 @@

/**
* ShoukakuError. Extended Error class.
* @class ShoukakuError
* @extends {Error}
*/
class ShoukakuError extends Error {
/**
* @param message The Error Message
*/
constructor(message) {

@@ -7,2 +15,2 @@ super(message);

}
module.exports = ShoukakuError;
module.exports = ShoukakuError;

@@ -0,2 +1,10 @@

/**
* ShoukakuTimeout, Timeout Error class of Shoukaku.
* @class ShoukakuTimeout
* @extends {Error}
*/
class ShoukakuTimeout extends Error {
/**
* @param message The Error Message
*/
constructor(message) {

@@ -7,2 +15,2 @@ super(message);

}
module.exports = ShoukakuTimeout;
module.exports = ShoukakuTimeout;

@@ -5,13 +5,12 @@ const { ShoukakuStatus } = require('../constants/ShoukakuConstants.js');

/**
* ShoukakuLink, the voice connection manager of a guild. Contains the Player Class that can be used to play tracks.
* @class
* ShoukakuLink, contains data about the voice connection on the guild.
* @class ShoukakuLink
*/
class ShoukakuLink {
/**
* Constructor for ShoukakuLink
* @param {ShoukakuSocket} node The node that governs this link.
* @param {ShoukakuPlayer} player The player of this link.
* @param {ShoukakuSocket} node The node that governs this link.
* @param {external:Guild} guild A Discord.js Guild Object.
*/
constructor(player, node, guild) {
constructor(node, player, guild) {
/**

@@ -41,3 +40,3 @@ * The node that governs this Link

*/
this.userID = node.shoukaku.id;
this.userID = this.node.shoukaku.id;
/**

@@ -59,3 +58,3 @@ * The sessionID of this Link

/**
* TIf the client user is self defeaned.
* If the client user is self defeaned.
* @type {boolean}

@@ -75,3 +74,3 @@ */

set build(data) {
stateUpdate(data) {
this.selfDeaf = data.self_deaf;

@@ -83,11 +82,27 @@ this.selfMute = data.self_mute;

set serverUpdate(data) {
serverUpdate(data) {
this.lastServerUpdate = data;
this._voiceUpdate(data);
this._voiceUpdate()
.then(() => {
if (this._timeout) clearTimeout(this._timeout);
if (this.state === ShoukakuStatus.CONNECTING) this.state = ShoukakuStatus.CONNECTED;
if (this._callback) this._callback(null, this.player);
})
.catch((error) => {
if (this._timeout) clearTimeout(this._timeout);
if (this.state !== ShoukakuStatus.CONNECTING)
return this.player._listen('error', error);
this.state = ShoukakuStatus.DISCONNECTED;
if (this._callback) this._callback(error);
})
.finally(() => {
this._callback = null;
this._timeout = null;
});
}
_connect(d, callback) {
if (!d || !callback)
_connect(options, callback) {
if (!options || !callback)
throw new ShoukakuError('No Options or Callback supplied.');
this._callback = callback;

@@ -106,3 +121,5 @@

this.state = ShoukakuStatus.CONNECTING;
this._send(d);
const { guildID, voiceChannelID, deaf, mute } = options;
this._sendDiscordWS({ guild_id: guildID, channel_id: voiceChannelID, self_deaf: deaf, self_mute: mute });
}

@@ -113,71 +130,48 @@

this.node.players.delete(this.guildID);
this._clearVoice();
this.player.removeAllListeners();
this._clearVoice();
this.player._clearTrack();
this.player._clearPlayer();
this.player._clearBands();
if (this.state !== ShoukakuStatus.DISCONNECTED) {
this._destroy();
this._send({
guild_id: this.guildID,
channel_id: null,
self_mute: false,
self_deaf: false
});
this._destroy()
.catch(() => null);
this._sendDiscordWS({ guild_id: this.guildID, channel_id: null, self_mute: false, self_deaf: false });
this.state = ShoukakuStatus.DISCONNECTED;
}
}
_send(d) {
this.node.shoukaku.send({ op: 4, d });
async _move(node) {
await this._destroy();
this.node.players.delete(this.guildID);
this.node = node;
await this._voiceUpdate();
this.node.players.set(this.guildID, this.player);
await this.player._resume();
}
_clearVoice() {
this.lastServerUpdate = null;
this.sessionID = null;
this.voiceChannelID = null;
_destroy() {
return this.node.send({ op: 'destroy', guildId: this.guildID });
}
_destroy() {
this.node.send({ op: 'destroy', guildId: this.guildID })
.catch(() => null);
_voiceUpdate() {
return this.node.send({ op: 'voiceUpdate', guildId: this.guildID, sessionId: this.sessionID, event: this.lastServerUpdate });
}
_voiceUpdate(event) {
this.node.send({ op: 'voiceUpdate', guildId: this.guildID, sessionId: this.sessionID, event })
.then(() => {
if (this._timeout) clearTimeout(this._timeout);
if (this.state !== ShoukakuStatus.CONNECTING) return;
this.state = ShoukakuStatus.CONNECTED;
this._callback(null, this.player);
})
.catch((error) => {
if (this._timeout) clearTimeout(this._timeout);
if (this.state !== ShoukakuStatus.CONNECTING) {
this.player._listen('error', error);
return;
}
this.state = ShoukakuStatus.DISCONNECTED;
this._callback(error);
})
.finally(() => {
this._callback = null;
this._timeout = null;
});
_sendDiscordWS(d) {
const guild = this.node.shoukaku.client.guilds.get(this.guildID);
if (!guild) return;
guild.shard.send({ op: 4, d });
}
_voiceDisconnect() {
this.state = ShoukakuStatus.DISCONNECTED;
this._destroy();
_clearVoice() {
this.lastServerUpdate = null;
this.sessionID = null;
this.voiceChannelID = null;
}
_nodeDisconnected() {
this._clearVoice();
this._send({
guild_id: this.guildID,
channel_id: null,
self_mute: false,
self_deaf: false
});
this.player._listen('nodeDisconnect', this.node.name);
this.player._listen('nodeDisconnect', new ShoukakuError(`Node: ${this.node.name} disconnected. Either there is no more nodes available to migrate to, or moveOnDisconnect is disabled.`));
this._disconnect();
}
}
module.exports = ShoukakuLink;
const EventEmitter = require('events');
const { ShoukakuPlayOptions } = require('../constants/ShoukakuConstants.js');
const { ShoukakuPlayOptions, ShoukakuStatus } = require('../constants/ShoukakuConstants.js');
const ShoukakuLink = require('./ShoukakuLink.js');

@@ -8,4 +8,4 @@ const ShoukakuError = require('../constants/ShoukakuError.js');

/**
* ShoukakuPlayer, Governs the playing stuff on your guild
* @class
* ShoukakuPlayer, used to control the player on the guildused to control the player on the guild.
* @class ShoukakuPlayer
* @extends {external:EventEmitter}

@@ -15,3 +15,2 @@ */

/**
* Constructs a player.
* @param {ShoukakuSocket} node The node that governs this player.

@@ -23,6 +22,6 @@ * @param {external:Guild} guild A Discord.JS Guild Object.

/**
* The Link where this connected to.
* The Voice Connection of this Player.
* @type {ShoukakuLink}
*/
this.voiceConnection = new ShoukakuLink(this, node, guild);
this.voiceConnection = new ShoukakuLink(node, this, guild);
/**

@@ -55,3 +54,2 @@ * The Track that is currently being played by this player.

// Events
/**

@@ -61,2 +59,3 @@ * Emitted when the Lavalink Player emits a TrackEnd or TrackStuck event.

* @param {Object} reason
* @memberOf ShoukakuPlayer
*/

@@ -67,2 +66,3 @@ /**

* @param {Object} reason
* @memberOf ShoukakuPlayer
* @example

@@ -78,2 +78,3 @@ * // <Player> is your ShoukakuPlayer instance

* @param {Error} error The error encountered.
* @memberOf ShoukakuPlayer
* @example

@@ -90,2 +91,3 @@ * // <Player> is your ShoukakuPlayer instance

* @param {string} name The name of the node that disconnected.
* @memberOf ShoukakuPlayer
* @example

@@ -102,2 +104,3 @@ * // <Player> is your ShoukakuPlayer instance

* @param {Object} reason
* @memberOf ShoukakuPlayer
*/

@@ -107,2 +110,3 @@ /**

* @event ShoukakuPlayer#resumed
* @memberOf ShoukakuPlayer
*/

@@ -113,5 +117,10 @@ /**

* @param {Object} data
* @memberOf ShoukakuPlayer
*/
// Events End
/**
* Eventually Connects the Bot to the voice channel in the guild. This is used internally and must not be used to connect players. Use `<ShoukakuSocket>.joinVoiceChannel()` instead.
* @memberOf ShoukakuPlayer
* @returns {void}
*/
connect(options, callback) {

@@ -121,3 +130,4 @@ this.voiceConnection._connect(options, callback);

/**
* Eventually Disconnects the VoiceConnection from a Guild. Could be also used to clean up player remnants from unexpected events.
* Eventually Disconnects the VoiceConnection & Removes the player from a Guild. Could be also used to clean up player remnants from unexpected events.
* @memberOf ShoukakuPlayer
* @returns {void}

@@ -128,7 +138,20 @@ */

}
/**
* Moves this Player & VoiceConnection to another lavalink node you specified.
* @param {string} name Name of the Node you want to move to.
* @memberOf ShoukakuPlayer
* @returns {Promise<void>}
*/
async moveToNode(name) {
const node = this.voiceConnection.node.shoukaku.nodes.get(name);
if (!node || node.name === this.voiceConnection.node.name) return;
if (node.state !== ShoukakuStatus.CONNECTED)
throw new Error('The node you specified is not ready.');
await this.voiceConnection._move(node);
}
/**
* Plays the track you specifed. Warning: If the player is playing anything, calling this will just ignore your call. Call `ShoukakuPlayer.StopTrack()` first.
* @param {string} track The Base64 encoded track you got from lavalink API.
* @param {ShoukakuConstants#ShoukakuPlayOptions} [options=ShoukakuPlayOptions] Used if you want to put a custom track start or end time.
* @memberOf ShoukakuPlayer
* @returns {Promise<boolean>} true if successful false if not.

@@ -151,2 +174,3 @@ */

* Stops the player from playing.
* @memberOf ShoukakuPlayer
* @returns {Promise<boolean>} true if successful false if not.

@@ -166,2 +190,3 @@ */

* @param {boolean} [pause=true] true to pause, false to unpause
* @memberOf ShoukakuPlayer
* @returns {Promise<boolean>} true if successful false if not.

@@ -182,2 +207,3 @@ */

* @param {Array} bands An array of Lavalink bands.
* @memberOf ShoukakuPlayer
* @returns {Promise<boolean>} true if successful false if not.

@@ -198,2 +224,3 @@ */

* @param {number} volume The new volume you want to set on the player.
* @memberOf ShoukakuPlayer
* @returns {Promise<boolean>} true if successful false if not.

@@ -215,2 +242,3 @@ */

* @param {number} position position in MS you want to seek to.
* @memberOf ShoukakuPlayer
* @returns {Promise<boolean>} true if successful false if not.

@@ -228,11 +256,2 @@ */

_listen(event, data) {
if (endEvents.includes(event)) {
event === 'nodeDisconnect' ? this._clearTrack() && this._clearPlayer() : this._clearTrack();
this.emit(event, data);
return;
}
if (data) this.position = data.position;
this.emit(event, data);
}

@@ -244,15 +263,12 @@ _clearTrack() {

_clearPlayer() {
this.bands = null;
_clearBands() {
this.bands.length = 0;
}
async _resume() {
if (!this.track) {
this._listen('error', new ShoukakuError('No Track Found upon trying to resume.'));
return;
}
try {
await this.playTrack(this.track.repeat(1), { startTime: this.position });
if (this.bands.length) await this.setEqualizer(this.bands.slice(0));
if (this.volume !== 100) await this.setVolume(Number(this.volume));
if (!this.track) return this._listen('error', new ShoukakuError('No Track Found upon trying to resume.'));
await this.playTrack(this.track, { startTime: this.position });
if (this.bands.length) await this.setEqualizer(this.bands);
if (this.volume !== 100) await this.setVolume(this.volume);
this._listen('resumed', null);

@@ -263,3 +279,15 @@ } catch (error) {

}
_listen(event, data) {
if (endEvents.includes(event)) {
if (event === 'nodeDisconnect') this._clearTrack() && this._clearBands();
else this._clearTrack();
this.emit(event, data);
return;
}
if (data) this.position = data.position;
this.emit(event, data);
}
}
module.exports = ShoukakuPlayer;

@@ -0,1 +1,3 @@

const Websocket = require('ws');
const EventEmitter = require('events');
const { ShoukakuStatus, ShoukakuNodeStats, ShoukakuJoinOptions } = require('../constants/ShoukakuConstants.js');

@@ -6,12 +8,9 @@ const { PacketRouter, EventRouter } = require('../router/ShoukakuRouter.js');

const ShoukakuPlayer = require('../guild/ShoukakuPlayer.js');
const Websocket = require('ws');
const EventEmitter = require('events');
/**
* ShoukakuSocket, governs the Lavalink Connection and Lavalink Voice Connections.
* @class
* ShoukakuSocket, manages a single Lavalink WS connection.
* @class ShoukakuSocket
*/
class ShoukakuSocket extends EventEmitter {
/**
* Constructs a socket.
* @extends {external:EventEmitter}

@@ -34,3 +33,3 @@ * @param {Shoukaku} shoukaku Your Shoukaku Instance

/**
* The REST server of this Socket, mostly to load balance your REST requests instead of relying on a single node.
* The REST API of this Socket, mostly to load balance your REST requests instead of relying on a single node.
* @type {ShoukakuResolver}

@@ -79,5 +78,11 @@ */

}
get moveOnDisconnect() {
return this.shoukaku.options.moveOnDisconnect;
}
/**
* Penalties of this Socket. The higher the return number, the more loaded the server is.
* @type {number}
* @memberof ShoukakuSocket
*/

@@ -99,5 +104,7 @@ get penalties() {

* @param {boolean|string} resumable Determines if we should try to resume the connection.
* @memberof ShoukakuSocket
* @returns {void}
*/
connect(id, shardCount, resumable) {
this.state = ShoukakuStatus.CONNECTING;
const headers = {};

@@ -108,13 +115,9 @@ Object.defineProperty(headers, 'Authorization', { value: this.auth, enumerable: true });

if (resumable) Object.defineProperty(headers, 'Resume-Key', { value: resumable, enumerable: true });
const upgrade = this._upgrade.bind(this);
const open = this._open.bind(this);
this.ws = new Websocket(this.url, { headers });
this.ws.once('upgrade', this._upgrade.bind(this));
this.ws.once('open', this._open.bind(this));
this.ws.once('error', this._error.bind(this));
this.ws.once('close', this._close.bind(this));
const message = this._message.bind(this);
const error = this._error.bind(this);
const close = this._close.bind(this);
this.ws = new Websocket(this.url, { headers });
this.ws.on('upgrade', upgrade);
this.ws.on('open', open);
this.ws.on('message', message);
this.ws.on('error', error);
this.ws.on('close', close);
this.shoukaku.on('packetUpdate', this.packetRouter);

@@ -125,2 +128,3 @@ }

* @param {ShoukakuConstants#ShoukakuJoinOptions} options Join data to send.
* @memberof ShoukakuSocket
* @returns {Promise<ShoukakuPlayer>}

@@ -138,2 +142,5 @@ * @example

if (this.state !== ShoukakuStatus.CONNECTED)
return reject(new ShoukakuError('This node is not yet ready.'));
if (this.players.has(options.guildID))

@@ -149,10 +156,3 @@ return reject(new ShoukakuError('A Player is already established in this channel'));

const joinOptions = {
guild_id: options.guildID,
channel_id: options.voiceChannelID,
self_deaf: options.deaf,
self_mute: options.mute
};
player.connect(joinOptions, (error, value) => {
player.connect(options, (error, value) => {
if (!error) return resolve(value);

@@ -164,3 +164,15 @@ this.players.delete(guild.id);

}
/**
* Eventually Disconnects the VoiceConnection & Removes the Player from a Guild.
* @param {string} guildID The guild id of the player you want to remove.
* @memberOf ShoukakuSocket
* @returns {void}
*/
leaveVoiceChannel(guildID) {
const player = this.players.get(guildID);
if (!player) return;
player.disconnect();
}
send(data) {

@@ -180,3 +192,3 @@ return new Promise((resolve, reject) => {

}
_configureResuming() {

@@ -190,10 +202,16 @@ return this.send({

_configureCleaner(state) {
this.cleaner = state;
async _executeCleaner() {
if (!this.cleaner) return this.cleaner = true;
const nodes = [...this.shoukaku.nodes.values()].filter(node => node.state === ShoukakuStatus.CONNECTED);
if (this.moveOnDisconnect && nodes.length > 0) {
const ideal = nodes.sort((a, b) => a.penalties - b.penalties).shift();
for (const player of this.players.values()) {
await player.voiceConnection._move(ideal)
.catch(() => player.voiceConnection._nodeDisconnected());
}
} else {
for (const player of this.players.values()) player.voiceConnection._nodeDisconnected();
}
}
_executeCleaner() {
if (!this.cleaner) return this._configureCleaner(true);
for (const player of this.players.values()) player.voiceConnection._nodeDisconnected();
}

@@ -207,3 +225,3 @@ _upgrade(response) {

this._configureResuming()
.catch((error) => this.emit('error', this.name, error));
.catch(() => this.ws.close(4011, 'Failed to send resuming packet. Reconnecting.'));
this.reconnectAttempts = 0;

@@ -231,2 +249,3 @@ this.state = ShoukakuStatus.CONNECTED;

_close(code, reason) {
this.state = ShoukakuStatus.DISCONNECTED;
this.ws.removeAllListeners();

@@ -233,0 +252,0 @@ this.shoukaku.removeListener('packetUpdate', this.packetRouter);

@@ -11,8 +11,7 @@ const Fetch = require('node-fetch');

/**
* ShoukakuResolver, the REST part of the wrapper
* @class
* ShoukakuResolver, provides access to Lavalink REST API.
* @class ShoukakuResolver
*/
class ShoukakuResolver {
/**
* Constructs ShoukakuResolver
* @param {string} host Your node host / ip address of where the lavalink is hosted.

@@ -41,2 +40,3 @@ * @param {string} port The Port Number of your lavalink instance.

* @param {string} search Either `youtube` or `soundcloud`. If specified, resolve will return search results.
* @memberof ShoukakuResolver
* @returns {Promise<Object>} The Lavalink Track Object.

@@ -71,2 +71,3 @@ */

* @param {base64} track Base64 Encoded Track you got from the Lavalink API.
* @memberof ShoukakuResolver
* @returns {Promise<Object>} The Lavalink Track details.

@@ -73,0 +74,0 @@ */

const { ShoukakuStatus } = require('../constants/ShoukakuConstants.js');
const ShoukakuError = require('../constants/ShoukakuError.js');
class ShoukakuRouter {

@@ -19,3 +20,3 @@ static ReconnectRouter(id) {

return player._resume()
.catch(() => null);
.catch((error) => player._listen('error', new ShoukakuError(error.message)));
player._listen('error', error);

@@ -39,8 +40,8 @@ });

switch (packet.t) {
case 'VOICE_STATE_UPDATE':
player.voiceConnection.build = packet.d;
case 'VOICE_STATE_UPDATE':
player.voiceConnection.stateUpdate(packet.d);
if (!packet.d.channel_id) player._listen('error', new ShoukakuError('Voice connection is closed unexpectedly.'));
break;
case 'VOICE_SERVER_UPDATE':
player.voiceConnection.serverUpdate = packet.d;
player.voiceConnection.serverUpdate(packet.d);
}

@@ -47,0 +48,0 @@ }

@@ -27,3 +27,3 @@ const { RawRouter, ReconnectRouter } = require('./router/ShoukakuRouter.js');

* Shoukaku, governs the client's node connections.
* @class
* @class Shoukaku
* @extends {external:EventEmitter}

@@ -33,3 +33,2 @@ */

/**
* Initialize the base class
* @param {external:Client} client Your Discord.js client

@@ -52,5 +51,5 @@ * @param {ShoukakuConstants#ShoukakuOptions} [options=ShoukakuOptions] Options to initialize Shoukaku with

* The shard count of the bot that is being governed by Shoukaku.
* @type {?number}
* @type {number}
*/
this.shardCount = null;
this.shardCount = 1;
/**

@@ -67,4 +66,5 @@ * The current nodes that is being handled by Shoukaku.

/**
* Gets all the Players governed by the Nodes / Sockets in this instance.
* Gets all the Players that is currently active on all nodes in this instance.
* @type {external:Map}
* @memberof Shoukaku
*/

@@ -81,2 +81,3 @@ get players() {

* @type {number}
* @memberof Shoukaku
*/

@@ -89,3 +90,2 @@ get totalPlayers() {

// Events
/**

@@ -96,2 +96,3 @@ * Emitted when a Lavalink Node sends a debug event.

* @param {Object} data The actual debug data
* @memberof Shoukaku
*/

@@ -103,2 +104,3 @@ /**

* @param {Error} error The error encountered.
* @memberof Shoukaku
* @example

@@ -113,2 +115,3 @@ * // <Shoukaku> is your own instance of Shoukaku

* @param {boolean} reconnect True if the session reconnected, otherwise false.
* @memberof Shoukaku
*/

@@ -121,2 +124,3 @@ /**

* @param {reason} reason The reason for this close event.
* @memberof Shoukaku
*/

@@ -128,4 +132,4 @@ /**

* @param {string} reason The reason for the disconnect.
* @memberof Shoukaku
*/
// Events End

@@ -136,10 +140,11 @@ /**

* @param {ShoukakuConstants#ShoukakuBuildOptions} options Options that is need by Shoukaku to build herself.
* @memberof Shoukaku
* @returns {void}
*/
start(nodes, options) {
if (this.id)
if (this.id)
throw new ShoukakuError('You already started Shoukaku, you don\'t need to start her again.');
options = this._mergeDefault(constants.ShoukakuBuildOptions, options);
this.id = options.id;
this.shardCount = options.shardCount;
if (options.shardCount) this.shardCount = options.shardCount;
for (let node of nodes) {

@@ -155,13 +160,14 @@ node = this._mergeDefault(constants.ShoukakuNodeOptions, node);

* @param {ShoukakuConstants#ShoukakuNodeOptions} nodeOptions The Node Options to be used to connect to.
* @memberof Shoukaku
* @returns {void}
*/
addNode(nodeOptions) {
if (!this.id)
if (!this.id)
throw new ShoukakuError('You didn\'t start Shoukaku once. Please call .start() method once before using this.');
const node = new ShoukakuSocket(this, nodeOptions);
node.connect(this.id, this.shardCount, false);
const _close = this._reconnect.bind(this);
const _ready = this._ready.bind(this);
node.on('debug', (name, data) => this.emit('debug', name, data));
node.on('error', (name, error) => this.emit('error', name, error));
const _close = this._close.bind(this);
const _ready = this._ready.bind(this);
node.on('ready', _ready);

@@ -171,22 +177,30 @@ node.on('close', _close);

}
// noinspection JSCommentMatchesSignature
/**
* Function to remove a Lavalink Node
* @param {string} name The Lavalink Node to remove
* @returns {boolean} true if the node was removed with no problems. Otherwise false.
* @param {string} reason Optional reason for this disconnect.
* @memberof Shoukaku
* @returns {void}
*/
removeNode(name, libraryInvoked = false) {
if (!this.id)
removeNode(name, reason) {
if (!this.id)
throw new ShoukakuError('You didn\'t start Shoukaku once. Please call .start() method once before using this.');
const node = this.nodes.get(name);
if (!node) return false;
node.removeAllListeners();
node._executeCleaner();
this.nodes.delete(name);
if (!libraryInvoked) this.emit('disconnected', name, 'User invoked disconnection');
return true;
if (!node) return;
node.state = constants.ShoukakuStatus.DISCONNECTING;
node._executeCleaner()
.catch((error) => this.emit('error', name, error))
.finally(() => {
node.state = constants.ShoukakuStatus.DISCONNECTED;
this.nodes.delete(name);
this.removeListener('packetUpdate', node.packetRouter);
node.removeAllListeners();
node.ws.close(4011, 'Remove node executed.');
this.emit('disconnected', name, reason);
});
}
/**
* Shortcut to get the Ideal Node or a manually specified Node from the current nodes that Shoukaku governs.
* @param {boolean|string} [name] If blank, Shoukaku will automatically return the Ideal Node for you to connect to. If name is specifed, she will try to return the node you specified.
* @param {string} [name] If blank, Shoukaku will automatically return the Ideal Node for you to connect to. If name is specifed, she will try to return the node you specified.
* @memberof Shoukaku
* @returns {ShoukakuSocket}

@@ -204,3 +218,3 @@ * @example

getNode(name) {
if (!this.id)
if (!this.id)
throw new ShoukakuError('You didn\'t start Shoukaku once. Please call .start() method once before using this.');

@@ -211,6 +225,13 @@ if (!this.nodes.size)

const node = this.nodes.get(name);
if (node) return node;
if (node) {
if (node.state !== constants.ShoukakuStatus.CONNECTED)
throw new ShoukakuError('This node is not yet ready');
return node;
}
throw new ShoukakuError('The node name you specified is not one of my nodes');
}
return [...this.nodes.values()].sort((a, b) => a.penalties - b.penalties).shift();
const nodes = [...this.nodes.values()].filter(node => node.state === constants.ShoukakuStatus.CONNECTED);
if (!nodes.length)
throw new ShoukakuError('No nodes are ready for communication.');
return nodes.sort((a, b) => a.penalties - b.penalties).shift();
}

@@ -220,6 +241,7 @@ /**

* @param {string} guildID The guildID of the guild we are trying to get.
* @memberof Shoukaku
* @returns {?ShoukakuPlayer}
*/
getPlayer(guildID) {
if (!this.id)
if (!this.id)
throw new ShoukakuError('You didn\'t start Shoukaku once. Please call .start() method once before using this.');

@@ -231,8 +253,20 @@ if (!guildID) return null;

send(payload) {
const guild = this.client.guilds.get(payload.d.guild_id);
if (!guild) return;
guild.shard.send(payload);
_mergeDefault(def, given) {
if (!given) return def;
const defaultKeys = Object.keys(def);
for (const key of defaultKeys) {
if (def[key] === null) {
if (!given[key]) throw new ShoukakuError(`${key} was not found from the given options.`);
}
if (!given[key]) given[key] = def[key];
}
for (const key in defaultKeys) {
if (defaultKeys.includes(key)) continue;
delete given[key];
}
return given;
}
_ready(name, resumed) {

@@ -244,3 +278,4 @@ const node = this.nodes.get(name);

_reconnect(name, code, reason) {
_close(name, code, reason) {
this.emit('close', name, code, reason);
const node = this.nodes.get(name);

@@ -252,31 +287,10 @@ if (node.reconnectAttempts < this.options.reconnectTries) {

} catch (error) {
this.emit('error', 'Shoukaku', error);
this.emit('error', name, error);
setTimeout(() => this._reconnect(name, code, reason), 2500);
return;
}
} else {
this.removeNode(name, true);
this.emit('disconnected', name, `Failed to reconnect in ${this.options.reconnectTries} attempts`);
return;
this.removeNode(name, `Failed to reconnect in ${this.options.reconnectTries} attempts`);
}
this.emit('close', name, code, reason);
}
// noinspection JSMethodCanBeStatic
_mergeDefault(def, given) {
if (!given) return def;
const defaultKeys = Object.keys(def);
for (const key of defaultKeys) {
if (def[key] === null) {
if (!given[key]) throw new ShoukakuError(`${key} was not found from the given options.`);
}
if (!given[key]) given[key] = def[key];
}
for (const key in defaultKeys) {
if (defaultKeys.includes(key)) continue;
delete given[key];
}
return given;
}
}
module.exports = Shoukaku;
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc