Comparing version 1.0.0 to 1.1.0
@@ -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; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
65479
1500
145
Updatedws@^7.1.2