lavalink-client 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  import { EventEmitter } from "events";
3
3
  import { NodeManager } from "./NodeManager";
4
- import { QueueChangesWatcher, QueueSaverOptions, StoreManager } from "./Queue";
4
+ import { QueueSaverOptions } from "./Queue";
5
5
  import { GuildShardPayload, LavalinkSearchPlatform, ManagerUitls, MiniMap, SearchPlatform, TrackEndEvent, TrackExceptionEvent, TrackStartEvent, TrackStuckEvent, VoicePacket, VoiceServer, VoiceState, WebSocketClosedEvent } from "./Utils";
6
6
  import { LavalinkNodeOptions } from "./Node";
7
7
  import { DestroyReasonsType, Player, PlayerOptions } from "./Player";
@@ -44,11 +44,11 @@ export interface LavalinkPlayerOptions {
44
44
  export interface ManagerOptions {
45
45
  nodes: LavalinkNodeOptions[];
46
46
  queueOptions?: QueueSaverOptions;
47
- queueStore?: StoreManager;
48
- queueChangesWatcher?: QueueChangesWatcher;
49
47
  client?: BotClientOptions;
50
48
  playerOptions?: LavalinkPlayerOptions;
51
49
  autoSkip?: boolean;
50
+ defaultLeastUsedNodeSortType?: "memory" | "calls" | "players";
51
+ defaultLeastLoadNodeSortType?: "cpu" | "memory";
52
52
  /** @async */
53
53
  sendToShard: (guildId: string, payload: GuildShardPayload) => void;
54
54
  }
@@ -13,36 +13,97 @@ class LavalinkManager extends events_1.EventEmitter {
13
13
  initiated = false;
14
14
  players = new Utils_1.MiniMap();
15
15
  applyDefaultOptions() {
16
+ if (!this.options.playerOptions)
17
+ this.options.playerOptions = {
18
+ applyVolumeAsFilter: false,
19
+ clientBasedPositionUpdateInterval: 100,
20
+ defaultSearchPlatform: "ytsearch",
21
+ onDisconnect: {
22
+ destroyPlayer: true,
23
+ autoReconnect: false
24
+ },
25
+ onEmptyQueue: {
26
+ autoPlayFunction: null,
27
+ destroyAfterMs: undefined
28
+ },
29
+ requesterTransformer: (requester) => {
30
+ // if it's already the transformed requester
31
+ if (typeof requester === "object" && "avatar" in requester && Object.keys(requester).length === 3)
32
+ return requester;
33
+ // if it's still a discord.js User
34
+ if (typeof requester === "object" && "displayAvatarURL" in requester) { // it's a user
35
+ return {
36
+ id: requester.id,
37
+ username: requester.username,
38
+ avatar: requester.displayAvatarURL(),
39
+ };
40
+ }
41
+ // if it's non of the above
42
+ return { id: requester.toString(), username: "unknown" }; // reteurn something that makes sense for you!
43
+ },
44
+ volumeDecrementer: 1
45
+ };
46
+ if (!this.options.autoSkip)
47
+ this.options.autoSkip = true;
48
+ if (!this.options.defaultLeastLoadNodeSortType)
49
+ this.options.defaultLeastLoadNodeSortType = "memory";
50
+ if (!this.options.defaultLeastUsedNodeSortType)
51
+ this.options.defaultLeastUsedNodeSortType = "players";
16
52
  if (!this.options.playerOptions.defaultSearchPlatform)
17
53
  this.options.playerOptions.defaultSearchPlatform = "ytsearch";
54
+ // default queue options
55
+ if (!this.options.queueOptions)
56
+ this.options.queueOptions = {
57
+ maxPreviousTracks: 25,
58
+ queueChangesWatcher: null,
59
+ queueStore: new Queue_1.DefaultQueueStore()
60
+ };
18
61
  if (typeof this.options?.queueOptions?.maxPreviousTracks !== "number" || this.options.queueOptions.maxPreviousTracks < 0)
19
62
  this.options.queueOptions.maxPreviousTracks = 25;
20
63
  return;
21
64
  }
22
65
  validateAndApply(options) {
66
+ if (typeof options.sendToShard !== "function")
67
+ throw new SyntaxError("ManagerOption.sendToShard was not provided, which is required!");
68
+ // only check in .init()
69
+ // if(typeof options.client !== "object" || typeof options.client.id !== "string") throw new SyntaxError("ManagerOption.client = { id: string, username?:string, shards?: 'auto'|number } was not provided, which is required");
70
+ if (options.autoSkip && typeof options.autoSkip !== "boolean")
71
+ throw new SyntaxError("ManagerOption.autoSkip must be either false | true aka boolean");
72
+ if (options.defaultLeastLoadNodeSortType && !["memory", "cpu"].includes(options.defaultLeastLoadNodeSortType))
73
+ throw new SyntaxError("ManagerOption.defaultLeastLoadNodeSortType must be 'memory' | 'cpu'");
74
+ if (options.defaultLeastUsedNodeSortType && !["memory", "players", "calls"].includes(options.defaultLeastUsedNodeSortType))
75
+ throw new SyntaxError("ManagerOption.defaultLeastLoadNodeSortType must be 'memory' | 'calls' | 'players'");
76
+ if (!options.nodes || !Array.isArray(options.nodes) || !options.nodes.every(node => this.utils.isNodeOptions(node)))
77
+ throw new SyntaxError("ManagerOption.nodes must be an Array of NodeOptions and is required of at least 1 Node");
23
78
  /* QUEUE STORE */
24
- if (options.queueStore) {
25
- const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(options.queueStore));
79
+ if (options.queueOptions?.queueStore) {
80
+ const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(options.queueOptions?.queueStore));
26
81
  const requiredKeys = ["get", "set", "stringify", "parse", "delete"];
27
- if (!requiredKeys.every(v => keys.includes(v)) || !requiredKeys.every(v => typeof options.queueStore[v] === "function"))
28
- throw new SyntaxError(`The provided QueueStore, does not have all required functions: ${requiredKeys.join(", ")}`);
82
+ if (!requiredKeys.every(v => keys.includes(v)) || !requiredKeys.every(v => typeof options.queueOptions?.queueStore[v] === "function"))
83
+ throw new SyntaxError(`The provided ManagerOption.QueueStore, does not have all required functions: ${requiredKeys.join(", ")}`);
29
84
  }
30
85
  else
31
- this.options.queueStore = new Queue_1.DefaultQueueStore();
86
+ this.options.queueOptions.queueStore = new Queue_1.DefaultQueueStore();
87
+ /* QUEUE WATCHER */
88
+ if (options.queueOptions?.queueChangesWatcher) {
89
+ const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(options.queueOptions?.queueChangesWatcher));
90
+ const requiredKeys = ["tracksAdd", "tracksRemoved", "shuffled"];
91
+ if (!requiredKeys.every(v => keys.includes(v)) || !requiredKeys.every(v => typeof options.queueOptions?.queueChangesWatcher[v] === "function"))
92
+ throw new SyntaxError(`The provided ManagerOption.QueueChangesWatcher, does not have all required functions: ${requiredKeys.join(", ")}`);
93
+ }
32
94
  }
33
95
  constructor(options) {
34
96
  super();
97
+ if (!options)
98
+ throw new SyntaxError("No Manager Options Provided");
35
99
  // create options
36
- this.options = {
37
- autoSkip: true,
38
- ...options
39
- };
100
+ this.options = options;
101
+ this.utils = new Utils_1.ManagerUitls(this);
40
102
  // use the validators
41
- this.applyDefaultOptions();
42
103
  this.validateAndApply(options);
104
+ this.applyDefaultOptions();
43
105
  // create classes
44
106
  this.nodeManager = new NodeManager_1.NodeManager(this);
45
- this.utils = new Utils_1.ManagerUitls(this);
46
107
  }
47
108
  createPlayer(options) {
48
109
  if (this.players.has(options.guildId))
@@ -153,7 +153,7 @@ class LavalinkNode {
153
153
  }
154
154
  /** Get the id of the node */
155
155
  get id() {
156
- return this.options.id || this.options.host;
156
+ return this.options.id || `${this.options.host}:${this.options.port}`;
157
157
  }
158
158
  /**
159
159
  * Destroys the Node-Connection (Websocket) and all player's of the node
@@ -531,14 +531,14 @@ class LavalinkNode {
531
531
  return this.NodeManager.LavalinkManager.emit("trackEnd", player, track, payload);
532
532
  // If a track had an error while starting
533
533
  if (["loadFailed", "cleanup"].includes(payload.reason)) {
534
- await (0, Utils_1.queueTrackEnd)(player.queue);
534
+ await (0, Utils_1.queueTrackEnd)(player.queue, false);
535
535
  // if no track available, end queue
536
536
  if (!player.queue.current)
537
537
  return this.queueEnd(player, track, payload);
538
538
  // fire event
539
539
  this.NodeManager.LavalinkManager.emit("trackEnd", player, track, payload);
540
540
  // play track if autoSkip is true
541
- return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ track: player.queue.current, noReplace: true });
541
+ return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ noReplace: true });
542
542
  }
543
543
  // remove tracks from the queue
544
544
  if (player.repeatMode !== "track")
@@ -549,7 +549,7 @@ class LavalinkNode {
549
549
  // fire event
550
550
  this.NodeManager.LavalinkManager.emit("trackEnd", player, track, payload);
551
551
  // play track if autoSkip is true
552
- return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ track: player.queue.current, noReplace: true });
552
+ return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ noReplace: true });
553
553
  }
554
554
  async queueEnd(player, track, payload) {
555
555
  player.queue.current = null;
@@ -559,7 +559,7 @@ class LavalinkNode {
559
559
  if (player.queue.tracks.length > 0)
560
560
  await (0, Utils_1.queueTrackEnd)(player.queue, player.repeatMode === "queue");
561
561
  if (player.queue.current)
562
- return player.play({ track: player.queue.current, noReplace: true, paused: false });
562
+ return player.play({ noReplace: true, paused: false });
563
563
  }
564
564
  if (payload?.reason !== "stopped") {
565
565
  await player.queue.utils.save();
@@ -590,7 +590,7 @@ class LavalinkNode {
590
590
  if (!player.queue.current)
591
591
  return this.queueEnd(player, track, payload);
592
592
  // play track if autoSkip is true
593
- return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ track: player.queue.current, noReplace: true });
593
+ return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
594
594
  }
595
595
  async trackError(player, track, payload) {
596
596
  this.NodeManager.LavalinkManager.emit("trackError", player, track, payload);
@@ -600,7 +600,7 @@ class LavalinkNode {
600
600
  if (!player.queue.current)
601
601
  return this.queueEnd(player, track, payload);
602
602
  // play track if autoSkip is true
603
- return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ track: player.queue.current, noReplace: true });
603
+ return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
604
604
  }
605
605
  socketClosed(player, payload) {
606
606
  return this.NodeManager.LavalinkManager.emit("playerSocketClosed", player, payload);
@@ -56,6 +56,17 @@ export declare class NodeManager extends EventEmitter {
56
56
  constructor(LavalinkManager: LavalinkManager);
57
57
  createNode(options: LavalinkNodeOptions): LavalinkNode;
58
58
  get leastUsedNodes(): LavalinkNode[];
59
+ /** Returns the least used Nodes sorted by amount of calls. */
60
+ private get leastUsedNodesCalls();
61
+ /** Returns the least used Nodes sorted by amount of players. */
62
+ private get leastUsedNodesPlayers();
63
+ /** Returns the least used Nodes sorted by amount of memory usage. */
64
+ private get leastUsedNodesMemory();
65
+ /** Returns the least system load Nodes. */
66
+ get leastLoadNodes(): LavalinkNode[];
67
+ get leastLoadNodesMemory(): LavalinkNode[];
68
+ /** Returns the least system load Nodes. */
69
+ get leastLoadNodesCpu(): LavalinkNode[];
59
70
  deleteNode(node: LavalinkNodeIdentifier | LavalinkNode): void;
60
71
  }
61
72
  export {};
@@ -14,14 +14,71 @@ class NodeManager extends stream_1.EventEmitter {
14
14
  this.LavalinkManager.options.nodes.forEach(node => this.createNode(node));
15
15
  }
16
16
  createNode(options) {
17
- if (this.nodes.has(options.id || options.host))
18
- return this.nodes.get(options.id || options.host);
17
+ if (this.nodes.has(options.id || `${options.host}:${options.port}`))
18
+ return this.nodes.get(options.id || `${options.host}:${options.port}`);
19
19
  const newNode = new Node_1.LavalinkNode(options, this);
20
20
  this.nodes.set(newNode.id, newNode);
21
21
  return newNode;
22
22
  }
23
23
  get leastUsedNodes() {
24
- return [...this.nodes.values()].filter(v => v);
24
+ if (this.LavalinkManager.options.defaultLeastUsedNodeSortType === "memory")
25
+ return this.leastUsedNodesMemory;
26
+ else if (this.LavalinkManager.options.defaultLeastUsedNodeSortType === "calls")
27
+ return this.leastUsedNodesCalls;
28
+ else
29
+ return this.leastUsedNodesPlayers; // this.options.defaultLeastUsedNodeSortType === "players"
30
+ }
31
+ /** Returns the least used Nodes sorted by amount of calls. */
32
+ get leastUsedNodesCalls() {
33
+ return [...this.nodes.values()]
34
+ .filter((node) => node.connected)
35
+ .sort((a, b) => b.calls - a.calls); // client sided sorting
36
+ }
37
+ /** Returns the least used Nodes sorted by amount of players. */
38
+ get leastUsedNodesPlayers() {
39
+ return [...this.nodes.values()]
40
+ .filter((node) => node.connected)
41
+ .sort((a, b) => (a.stats?.players || 0) - (b.stats?.players || 0));
42
+ }
43
+ /** Returns the least used Nodes sorted by amount of memory usage. */
44
+ get leastUsedNodesMemory() {
45
+ return [...this.nodes.values()]
46
+ .filter((node) => node.connected)
47
+ .sort((a, b) => (b.stats?.memory?.used || 0) - (a.stats?.memory?.used || 0)); // sort after memory
48
+ }
49
+ /** Returns the least system load Nodes. */
50
+ get leastLoadNodes() {
51
+ if (this.LavalinkManager.options.defaultLeastLoadNodeSortType === "cpu")
52
+ return this.leastLoadNodesCpu;
53
+ else
54
+ return this.leastLoadNodesMemory; // this.options.defaultLeastLoadNodeSortType === "memory"
55
+ }
56
+ get leastLoadNodesMemory() {
57
+ return [...this.nodes.values()]
58
+ .filter((node) => node.connected)
59
+ .sort((a, b) => {
60
+ const aload = a.stats.memory?.used
61
+ ? a.stats.memory.used
62
+ : 0;
63
+ const bload = b.stats.memory?.used
64
+ ? b.stats.memory.used
65
+ : 0;
66
+ return aload - bload;
67
+ });
68
+ }
69
+ /** Returns the least system load Nodes. */
70
+ get leastLoadNodesCpu() {
71
+ return [...this.nodes.values()]
72
+ .filter((node) => node.connected)
73
+ .sort((a, b) => {
74
+ const aload = a.stats.cpu
75
+ ? (a.stats.cpu.systemLoad / a.stats.cpu.cores) * 100
76
+ : 0;
77
+ const bload = b.stats.cpu
78
+ ? (b.stats.cpu.systemLoad / b.stats.cpu.cores) * 100
79
+ : 0;
80
+ return aload - bload;
81
+ });
25
82
  }
26
83
  deleteNode(node) {
27
84
  const decodeNode = typeof node === "string" ? this.nodes.get(node) : node || this.leastUsedNodes[0];
@@ -66,7 +66,9 @@ class Player {
66
66
  this.guildId = this.options.guildId;
67
67
  this.voiceChannelId = this.options.voiceChannelId;
68
68
  this.textChannelId = this.options.textChannelId || null;
69
- this.node = this.LavalinkManager.nodeManager.leastUsedNodes.filter(v => options.vcRegion ? v.options?.regions?.includes(options.vcRegion) : true)[0] || this.LavalinkManager.nodeManager.leastUsedNodes[0] || null;
69
+ this.node = typeof this.options.node === "string" ? this.LavalinkManager.nodeManager.nodes.get(this.options.node) : this.options.node;
70
+ if (!this.node || typeof this.node?.request !== "function")
71
+ this.node = this.LavalinkManager.nodeManager.leastUsedNodes.filter(v => options.vcRegion ? v.options?.regions?.includes(options.vcRegion) : true)[0] || this.LavalinkManager.nodeManager.leastUsedNodes[0] || null;
70
72
  if (!this.node)
71
73
  throw new Error("No available Node was found, please add a LavalinkNode to the Manager via Manager.NodeManager#createNode");
72
74
  if (this.LavalinkManager.options.playerOptions.volumeDecrementer)
@@ -74,7 +76,7 @@ class Player {
74
76
  this.LavalinkManager.emit("playerCreate", this);
75
77
  if (typeof options.volume === "number" && !isNaN(options.volume))
76
78
  this.setVolume(options.volume);
77
- this.queue = new Queue_1.Queue(this.guildId, {}, new Queue_1.QueueSaver(this.LavalinkManager.options.queueStore, this.LavalinkManager.options.queueOptions), this.LavalinkManager.options.queueChangesWatcher);
79
+ this.queue = new Queue_1.Queue(this.guildId, {}, new Queue_1.QueueSaver(this.LavalinkManager.options.queueOptions), this.LavalinkManager.options.queueOptions);
78
80
  }
79
81
  /**
80
82
  * Set custom data.
@@ -18,6 +18,8 @@ export interface StoreManager extends Record<any, any> {
18
18
  }
19
19
  export interface QueueSaverOptions {
20
20
  maxPreviousTracks: number;
21
+ queueStore?: StoreManager;
22
+ queueChangesWatcher?: QueueChangesWatcher;
21
23
  }
22
24
  export interface QueueSaver {
23
25
  /** @private */
@@ -26,7 +28,7 @@ export interface QueueSaver {
26
28
  options: QueueSaverOptions;
27
29
  }
28
30
  export declare class QueueSaver {
29
- constructor(storeManager: StoreManager, options: QueueSaverOptions);
31
+ constructor(options: QueueSaverOptions);
30
32
  get(guildId: string): Promise<Partial<StoredQueue>>;
31
33
  delete(guildId: string): Promise<any>;
32
34
  set(guildId: string, value: any): Promise<any>;
@@ -59,7 +61,7 @@ export declare class Queue {
59
61
  static readonly StaticSymbol: Symbol;
60
62
  private managerUtils;
61
63
  private queueChanges;
62
- constructor(guildId: string, data?: Partial<StoredQueue>, QueueSaver?: QueueSaver, queueChangesWatcher?: QueueChangesWatcher);
64
+ constructor(guildId: string, data?: Partial<StoredQueue>, QueueSaver?: QueueSaver, queueOptions?: QueueSaverOptions);
63
65
  /**
64
66
  * Utils for a Queue
65
67
  */
@@ -3,9 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Queue = exports.QueueChangesWatcher = exports.DefaultQueueStore = exports.QueueSaver = void 0;
4
4
  const Utils_1 = require("./Utils");
5
5
  class QueueSaver {
6
- constructor(storeManager, options) {
7
- this._ = storeManager;
8
- this.options = options;
6
+ constructor(options) {
7
+ this._ = options.queueStore || new DefaultQueueStore();
8
+ this.options = {
9
+ maxPreviousTracks: options.maxPreviousTracks
10
+ };
9
11
  }
10
12
  async get(guildId) {
11
13
  return await this._.parse(await this._.get(guildId));
@@ -66,8 +68,8 @@ class Queue {
66
68
  static StaticSymbol = Utils_1.QueueSymbol;
67
69
  managerUtils = new Utils_1.ManagerUitls();
68
70
  queueChanges;
69
- constructor(guildId, data = {}, QueueSaver, queueChangesWatcher) {
70
- this.queueChanges = queueChangesWatcher || null;
71
+ constructor(guildId, data = {}, QueueSaver, queueOptions) {
72
+ this.queueChanges = queueOptions.queueChangesWatcher || null;
71
73
  this.guildId = guildId;
72
74
  this.QueueSaver = QueueSaver;
73
75
  this.options.maxPreviousTracks = this.QueueSaver?.options?.maxPreviousTracks ?? this.options.maxPreviousTracks;
@@ -1,6 +1,6 @@
1
1
  import { LavalinkFilterData } from "./Filters";
2
2
  import { LavalinkManager } from "./LavalinkManager";
3
- import { LavalinkNode, NodeStats } from "./Node";
3
+ import { LavalinkNode, LavalinkNodeOptions, NodeStats } from "./Node";
4
4
  import { PlayOptions } from "./Player";
5
5
  import { Queue } from "./Queue";
6
6
  import { PluginInfo, Track } from "./Track";
@@ -40,10 +40,20 @@ export interface ManagerUitls {
40
40
  export declare class ManagerUitls {
41
41
  constructor(LavalinkManager?: LavalinkManager);
42
42
  buildTrack(data: any, requester: any): Track;
43
+ /**
44
+ * Validate if a data is equal to a node
45
+ * @param data
46
+ */
47
+ isNode(data: LavalinkNode): boolean;
48
+ /**
49
+ * Validate if a data is equal to node options
50
+ * @param data
51
+ */
52
+ isNodeOptions(data: LavalinkNodeOptions | any): boolean;
43
53
  /**
44
54
  * Validate if a data is euqal to a track
45
- * @param {Track|any} data the Track to validate
46
- * @returns {boolean}
55
+ * @param data the Track to validate
56
+ * @returns
47
57
  */
48
58
  isTrack(data: Track | any): boolean;
49
59
  validatedQuery(queryString: string, node: LavalinkNode): void;
@@ -85,6 +95,8 @@ export declare class MiniMap<K, V> extends Map<K, V> {
85
95
  filter<This, K2 extends K>(fn: (this: This, value: V, key: K, miniMap: this) => key is K2, thisArg: This): MiniMap<K2, V>;
86
96
  filter<This, V2 extends V>(fn: (this: This, value: V, key: K, miniMap: this) => value is V2, thisArg: This): MiniMap<K, V2>;
87
97
  filter<This>(fn: (this: This, value: V, key: K, miniMap: this) => boolean, thisArg: This): MiniMap<K, V>;
98
+ toJSON(): V[];
99
+ private static defaultSort;
88
100
  /**
89
101
  * Maps each item to another value into an array. Identical in behavior to
90
102
  * [Array.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).
@@ -41,10 +41,58 @@ class ManagerUitls {
41
41
  throw new RangeError(`Argument "data" is not a valid track: ${error.message}`);
42
42
  }
43
43
  }
44
+ /**
45
+ * Validate if a data is equal to a node
46
+ * @param data
47
+ */
48
+ isNode(data) {
49
+ if (!data)
50
+ return false;
51
+ const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(data));
52
+ if (!keys.includes("constructor"))
53
+ return false;
54
+ if (!keys.length)
55
+ return false;
56
+ // all required functions
57
+ if (!["connect", "destroy", "destroyPlayer", "fetchAllPlayers", "fetchInfo", "fetchPlayer", "fetchStats", "fetchVersion", "request", "updatePlayer", "updateSession"].every(v => keys.includes(v)))
58
+ return false;
59
+ return true;
60
+ }
61
+ /**
62
+ * Validate if a data is equal to node options
63
+ * @param data
64
+ */
65
+ isNodeOptions(data) {
66
+ if (!data || typeof data !== "object" || Array.isArray(data))
67
+ return false;
68
+ if (typeof data.host !== "string" || !data.host.length)
69
+ return false;
70
+ if (typeof data.port !== "number" || isNaN(data.port) || data.port < 0 || data.port > 65535)
71
+ return false;
72
+ if (typeof data.authorization !== "string" || !data.authorization.length)
73
+ return false;
74
+ if ("secure" in data && typeof data.secure !== "boolean")
75
+ return false;
76
+ if ("sessionId" in data && typeof data.sessionId !== "string")
77
+ return false;
78
+ if ("id" in data && typeof data.id !== "string")
79
+ return false;
80
+ if ("regions" in data && (!Array.isArray(data.regions) || !data.regions.every(v => typeof v === "string")))
81
+ return false;
82
+ if ("poolOptions" in data && typeof data.poolOptions !== "object")
83
+ return false;
84
+ if ("retryAmount" in data && (typeof data.retryAmount !== "number" || isNaN(data.retryAmount) || data.retryAmount <= 0))
85
+ return false;
86
+ if ("retryDelay" in data && (typeof data.retryDelay !== "number" || isNaN(data.retryDelay) || data.retryDelay <= 0))
87
+ return false;
88
+ if ("requestTimeout" in data && (typeof data.requestTimeout !== "number" || isNaN(data.requestTimeout) || data.requestTimeout <= 0))
89
+ return false;
90
+ return true;
91
+ }
44
92
  /**
45
93
  * Validate if a data is euqal to a track
46
- * @param {Track|any} data the Track to validate
47
- * @returns {boolean}
94
+ * @param data the Track to validate
95
+ * @returns
48
96
  */
49
97
  isTrack(data) {
50
98
  return typeof data?.encoded === "string" && typeof data?.info === "object";
@@ -152,6 +200,13 @@ class MiniMap extends Map {
152
200
  }
153
201
  return results;
154
202
  }
203
+ toJSON() {
204
+ // toJSON is called recursively by JSON.stringify.
205
+ return [...this.values()];
206
+ }
207
+ static defaultSort(firstValue, secondValue) {
208
+ return Number(firstValue > secondValue) || Number(firstValue === secondValue) - 1;
209
+ }
155
210
  map(fn, thisArg) {
156
211
  if (typeof thisArg !== 'undefined')
157
212
  fn = fn.bind(thisArg);
@@ -170,11 +225,11 @@ async function queueTrackEnd(queue, addBackToQueue = false) {
170
225
  if (queue.previous.length > queue.options.maxPreviousTracks)
171
226
  queue.previous.splice(queue.options.maxPreviousTracks, queue.previous.length);
172
227
  }
173
- // change the current Track to the next upcoming one
174
- queue.current = queue.tracks.shift() || null;
175
228
  // and if repeatMode == queue, add it back to the queue!
176
229
  if (addBackToQueue && queue.current)
177
230
  queue.tracks.push(queue.current);
231
+ // change the current Track to the next upcoming one
232
+ queue.current = queue.tracks.shift() || null;
178
233
  // save it in the DB
179
234
  await queue.utils.save();
180
235
  // return the new current Track
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  import { EventEmitter } from "events";
3
3
  import { NodeManager } from "./NodeManager";
4
- import { QueueChangesWatcher, QueueSaverOptions, StoreManager } from "./Queue";
4
+ import { QueueSaverOptions } from "./Queue";
5
5
  import { GuildShardPayload, LavalinkSearchPlatform, ManagerUitls, MiniMap, SearchPlatform, TrackEndEvent, TrackExceptionEvent, TrackStartEvent, TrackStuckEvent, VoicePacket, VoiceServer, VoiceState, WebSocketClosedEvent } from "./Utils";
6
6
  import { LavalinkNodeOptions } from "./Node";
7
7
  import { DestroyReasonsType, Player, PlayerOptions } from "./Player";
@@ -44,11 +44,11 @@ export interface LavalinkPlayerOptions {
44
44
  export interface ManagerOptions {
45
45
  nodes: LavalinkNodeOptions[];
46
46
  queueOptions?: QueueSaverOptions;
47
- queueStore?: StoreManager;
48
- queueChangesWatcher?: QueueChangesWatcher;
49
47
  client?: BotClientOptions;
50
48
  playerOptions?: LavalinkPlayerOptions;
51
49
  autoSkip?: boolean;
50
+ defaultLeastUsedNodeSortType?: "memory" | "calls" | "players";
51
+ defaultLeastLoadNodeSortType?: "cpu" | "memory";
52
52
  /** @async */
53
53
  sendToShard: (guildId: string, payload: GuildShardPayload) => void;
54
54
  }
@@ -10,36 +10,97 @@ export class LavalinkManager extends EventEmitter {
10
10
  initiated = false;
11
11
  players = new MiniMap();
12
12
  applyDefaultOptions() {
13
+ if (!this.options.playerOptions)
14
+ this.options.playerOptions = {
15
+ applyVolumeAsFilter: false,
16
+ clientBasedPositionUpdateInterval: 100,
17
+ defaultSearchPlatform: "ytsearch",
18
+ onDisconnect: {
19
+ destroyPlayer: true,
20
+ autoReconnect: false
21
+ },
22
+ onEmptyQueue: {
23
+ autoPlayFunction: null,
24
+ destroyAfterMs: undefined
25
+ },
26
+ requesterTransformer: (requester) => {
27
+ // if it's already the transformed requester
28
+ if (typeof requester === "object" && "avatar" in requester && Object.keys(requester).length === 3)
29
+ return requester;
30
+ // if it's still a discord.js User
31
+ if (typeof requester === "object" && "displayAvatarURL" in requester) { // it's a user
32
+ return {
33
+ id: requester.id,
34
+ username: requester.username,
35
+ avatar: requester.displayAvatarURL(),
36
+ };
37
+ }
38
+ // if it's non of the above
39
+ return { id: requester.toString(), username: "unknown" }; // reteurn something that makes sense for you!
40
+ },
41
+ volumeDecrementer: 1
42
+ };
43
+ if (!this.options.autoSkip)
44
+ this.options.autoSkip = true;
45
+ if (!this.options.defaultLeastLoadNodeSortType)
46
+ this.options.defaultLeastLoadNodeSortType = "memory";
47
+ if (!this.options.defaultLeastUsedNodeSortType)
48
+ this.options.defaultLeastUsedNodeSortType = "players";
13
49
  if (!this.options.playerOptions.defaultSearchPlatform)
14
50
  this.options.playerOptions.defaultSearchPlatform = "ytsearch";
51
+ // default queue options
52
+ if (!this.options.queueOptions)
53
+ this.options.queueOptions = {
54
+ maxPreviousTracks: 25,
55
+ queueChangesWatcher: null,
56
+ queueStore: new DefaultQueueStore()
57
+ };
15
58
  if (typeof this.options?.queueOptions?.maxPreviousTracks !== "number" || this.options.queueOptions.maxPreviousTracks < 0)
16
59
  this.options.queueOptions.maxPreviousTracks = 25;
17
60
  return;
18
61
  }
19
62
  validateAndApply(options) {
63
+ if (typeof options.sendToShard !== "function")
64
+ throw new SyntaxError("ManagerOption.sendToShard was not provided, which is required!");
65
+ // only check in .init()
66
+ // if(typeof options.client !== "object" || typeof options.client.id !== "string") throw new SyntaxError("ManagerOption.client = { id: string, username?:string, shards?: 'auto'|number } was not provided, which is required");
67
+ if (options.autoSkip && typeof options.autoSkip !== "boolean")
68
+ throw new SyntaxError("ManagerOption.autoSkip must be either false | true aka boolean");
69
+ if (options.defaultLeastLoadNodeSortType && !["memory", "cpu"].includes(options.defaultLeastLoadNodeSortType))
70
+ throw new SyntaxError("ManagerOption.defaultLeastLoadNodeSortType must be 'memory' | 'cpu'");
71
+ if (options.defaultLeastUsedNodeSortType && !["memory", "players", "calls"].includes(options.defaultLeastUsedNodeSortType))
72
+ throw new SyntaxError("ManagerOption.defaultLeastLoadNodeSortType must be 'memory' | 'calls' | 'players'");
73
+ if (!options.nodes || !Array.isArray(options.nodes) || !options.nodes.every(node => this.utils.isNodeOptions(node)))
74
+ throw new SyntaxError("ManagerOption.nodes must be an Array of NodeOptions and is required of at least 1 Node");
20
75
  /* QUEUE STORE */
21
- if (options.queueStore) {
22
- const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(options.queueStore));
76
+ if (options.queueOptions?.queueStore) {
77
+ const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(options.queueOptions?.queueStore));
23
78
  const requiredKeys = ["get", "set", "stringify", "parse", "delete"];
24
- if (!requiredKeys.every(v => keys.includes(v)) || !requiredKeys.every(v => typeof options.queueStore[v] === "function"))
25
- throw new SyntaxError(`The provided QueueStore, does not have all required functions: ${requiredKeys.join(", ")}`);
79
+ if (!requiredKeys.every(v => keys.includes(v)) || !requiredKeys.every(v => typeof options.queueOptions?.queueStore[v] === "function"))
80
+ throw new SyntaxError(`The provided ManagerOption.QueueStore, does not have all required functions: ${requiredKeys.join(", ")}`);
26
81
  }
27
82
  else
28
- this.options.queueStore = new DefaultQueueStore();
83
+ this.options.queueOptions.queueStore = new DefaultQueueStore();
84
+ /* QUEUE WATCHER */
85
+ if (options.queueOptions?.queueChangesWatcher) {
86
+ const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(options.queueOptions?.queueChangesWatcher));
87
+ const requiredKeys = ["tracksAdd", "tracksRemoved", "shuffled"];
88
+ if (!requiredKeys.every(v => keys.includes(v)) || !requiredKeys.every(v => typeof options.queueOptions?.queueChangesWatcher[v] === "function"))
89
+ throw new SyntaxError(`The provided ManagerOption.QueueChangesWatcher, does not have all required functions: ${requiredKeys.join(", ")}`);
90
+ }
29
91
  }
30
92
  constructor(options) {
31
93
  super();
94
+ if (!options)
95
+ throw new SyntaxError("No Manager Options Provided");
32
96
  // create options
33
- this.options = {
34
- autoSkip: true,
35
- ...options
36
- };
97
+ this.options = options;
98
+ this.utils = new ManagerUitls(this);
37
99
  // use the validators
38
- this.applyDefaultOptions();
39
100
  this.validateAndApply(options);
101
+ this.applyDefaultOptions();
40
102
  // create classes
41
103
  this.nodeManager = new NodeManager(this);
42
- this.utils = new ManagerUitls(this);
43
104
  }
44
105
  createPlayer(options) {
45
106
  if (this.players.has(options.guildId))
@@ -149,7 +149,7 @@ export class LavalinkNode {
149
149
  }
150
150
  /** Get the id of the node */
151
151
  get id() {
152
- return this.options.id || this.options.host;
152
+ return this.options.id || `${this.options.host}:${this.options.port}`;
153
153
  }
154
154
  /**
155
155
  * Destroys the Node-Connection (Websocket) and all player's of the node
@@ -527,14 +527,14 @@ export class LavalinkNode {
527
527
  return this.NodeManager.LavalinkManager.emit("trackEnd", player, track, payload);
528
528
  // If a track had an error while starting
529
529
  if (["loadFailed", "cleanup"].includes(payload.reason)) {
530
- await queueTrackEnd(player.queue);
530
+ await queueTrackEnd(player.queue, false);
531
531
  // if no track available, end queue
532
532
  if (!player.queue.current)
533
533
  return this.queueEnd(player, track, payload);
534
534
  // fire event
535
535
  this.NodeManager.LavalinkManager.emit("trackEnd", player, track, payload);
536
536
  // play track if autoSkip is true
537
- return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ track: player.queue.current, noReplace: true });
537
+ return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ noReplace: true });
538
538
  }
539
539
  // remove tracks from the queue
540
540
  if (player.repeatMode !== "track")
@@ -545,7 +545,7 @@ export class LavalinkNode {
545
545
  // fire event
546
546
  this.NodeManager.LavalinkManager.emit("trackEnd", player, track, payload);
547
547
  // play track if autoSkip is true
548
- return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ track: player.queue.current, noReplace: true });
548
+ return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ noReplace: true });
549
549
  }
550
550
  async queueEnd(player, track, payload) {
551
551
  player.queue.current = null;
@@ -555,7 +555,7 @@ export class LavalinkNode {
555
555
  if (player.queue.tracks.length > 0)
556
556
  await queueTrackEnd(player.queue, player.repeatMode === "queue");
557
557
  if (player.queue.current)
558
- return player.play({ track: player.queue.current, noReplace: true, paused: false });
558
+ return player.play({ noReplace: true, paused: false });
559
559
  }
560
560
  if (payload?.reason !== "stopped") {
561
561
  await player.queue.utils.save();
@@ -586,7 +586,7 @@ export class LavalinkNode {
586
586
  if (!player.queue.current)
587
587
  return this.queueEnd(player, track, payload);
588
588
  // play track if autoSkip is true
589
- return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ track: player.queue.current, noReplace: true });
589
+ return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
590
590
  }
591
591
  async trackError(player, track, payload) {
592
592
  this.NodeManager.LavalinkManager.emit("trackError", player, track, payload);
@@ -596,7 +596,7 @@ export class LavalinkNode {
596
596
  if (!player.queue.current)
597
597
  return this.queueEnd(player, track, payload);
598
598
  // play track if autoSkip is true
599
- return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ track: player.queue.current, noReplace: true });
599
+ return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
600
600
  }
601
601
  socketClosed(player, payload) {
602
602
  return this.NodeManager.LavalinkManager.emit("playerSocketClosed", player, payload);
@@ -56,6 +56,17 @@ export declare class NodeManager extends EventEmitter {
56
56
  constructor(LavalinkManager: LavalinkManager);
57
57
  createNode(options: LavalinkNodeOptions): LavalinkNode;
58
58
  get leastUsedNodes(): LavalinkNode[];
59
+ /** Returns the least used Nodes sorted by amount of calls. */
60
+ private get leastUsedNodesCalls();
61
+ /** Returns the least used Nodes sorted by amount of players. */
62
+ private get leastUsedNodesPlayers();
63
+ /** Returns the least used Nodes sorted by amount of memory usage. */
64
+ private get leastUsedNodesMemory();
65
+ /** Returns the least system load Nodes. */
66
+ get leastLoadNodes(): LavalinkNode[];
67
+ get leastLoadNodesMemory(): LavalinkNode[];
68
+ /** Returns the least system load Nodes. */
69
+ get leastLoadNodesCpu(): LavalinkNode[];
59
70
  deleteNode(node: LavalinkNodeIdentifier | LavalinkNode): void;
60
71
  }
61
72
  export {};
@@ -11,14 +11,71 @@ export class NodeManager extends EventEmitter {
11
11
  this.LavalinkManager.options.nodes.forEach(node => this.createNode(node));
12
12
  }
13
13
  createNode(options) {
14
- if (this.nodes.has(options.id || options.host))
15
- return this.nodes.get(options.id || options.host);
14
+ if (this.nodes.has(options.id || `${options.host}:${options.port}`))
15
+ return this.nodes.get(options.id || `${options.host}:${options.port}`);
16
16
  const newNode = new LavalinkNode(options, this);
17
17
  this.nodes.set(newNode.id, newNode);
18
18
  return newNode;
19
19
  }
20
20
  get leastUsedNodes() {
21
- return [...this.nodes.values()].filter(v => v);
21
+ if (this.LavalinkManager.options.defaultLeastUsedNodeSortType === "memory")
22
+ return this.leastUsedNodesMemory;
23
+ else if (this.LavalinkManager.options.defaultLeastUsedNodeSortType === "calls")
24
+ return this.leastUsedNodesCalls;
25
+ else
26
+ return this.leastUsedNodesPlayers; // this.options.defaultLeastUsedNodeSortType === "players"
27
+ }
28
+ /** Returns the least used Nodes sorted by amount of calls. */
29
+ get leastUsedNodesCalls() {
30
+ return [...this.nodes.values()]
31
+ .filter((node) => node.connected)
32
+ .sort((a, b) => b.calls - a.calls); // client sided sorting
33
+ }
34
+ /** Returns the least used Nodes sorted by amount of players. */
35
+ get leastUsedNodesPlayers() {
36
+ return [...this.nodes.values()]
37
+ .filter((node) => node.connected)
38
+ .sort((a, b) => (a.stats?.players || 0) - (b.stats?.players || 0));
39
+ }
40
+ /** Returns the least used Nodes sorted by amount of memory usage. */
41
+ get leastUsedNodesMemory() {
42
+ return [...this.nodes.values()]
43
+ .filter((node) => node.connected)
44
+ .sort((a, b) => (b.stats?.memory?.used || 0) - (a.stats?.memory?.used || 0)); // sort after memory
45
+ }
46
+ /** Returns the least system load Nodes. */
47
+ get leastLoadNodes() {
48
+ if (this.LavalinkManager.options.defaultLeastLoadNodeSortType === "cpu")
49
+ return this.leastLoadNodesCpu;
50
+ else
51
+ return this.leastLoadNodesMemory; // this.options.defaultLeastLoadNodeSortType === "memory"
52
+ }
53
+ get leastLoadNodesMemory() {
54
+ return [...this.nodes.values()]
55
+ .filter((node) => node.connected)
56
+ .sort((a, b) => {
57
+ const aload = a.stats.memory?.used
58
+ ? a.stats.memory.used
59
+ : 0;
60
+ const bload = b.stats.memory?.used
61
+ ? b.stats.memory.used
62
+ : 0;
63
+ return aload - bload;
64
+ });
65
+ }
66
+ /** Returns the least system load Nodes. */
67
+ get leastLoadNodesCpu() {
68
+ return [...this.nodes.values()]
69
+ .filter((node) => node.connected)
70
+ .sort((a, b) => {
71
+ const aload = a.stats.cpu
72
+ ? (a.stats.cpu.systemLoad / a.stats.cpu.cores) * 100
73
+ : 0;
74
+ const bload = b.stats.cpu
75
+ ? (b.stats.cpu.systemLoad / b.stats.cpu.cores) * 100
76
+ : 0;
77
+ return aload - bload;
78
+ });
22
79
  }
23
80
  deleteNode(node) {
24
81
  const decodeNode = typeof node === "string" ? this.nodes.get(node) : node || this.leastUsedNodes[0];
@@ -63,7 +63,9 @@ export class Player {
63
63
  this.guildId = this.options.guildId;
64
64
  this.voiceChannelId = this.options.voiceChannelId;
65
65
  this.textChannelId = this.options.textChannelId || null;
66
- this.node = this.LavalinkManager.nodeManager.leastUsedNodes.filter(v => options.vcRegion ? v.options?.regions?.includes(options.vcRegion) : true)[0] || this.LavalinkManager.nodeManager.leastUsedNodes[0] || null;
66
+ this.node = typeof this.options.node === "string" ? this.LavalinkManager.nodeManager.nodes.get(this.options.node) : this.options.node;
67
+ if (!this.node || typeof this.node?.request !== "function")
68
+ this.node = this.LavalinkManager.nodeManager.leastUsedNodes.filter(v => options.vcRegion ? v.options?.regions?.includes(options.vcRegion) : true)[0] || this.LavalinkManager.nodeManager.leastUsedNodes[0] || null;
67
69
  if (!this.node)
68
70
  throw new Error("No available Node was found, please add a LavalinkNode to the Manager via Manager.NodeManager#createNode");
69
71
  if (this.LavalinkManager.options.playerOptions.volumeDecrementer)
@@ -71,7 +73,7 @@ export class Player {
71
73
  this.LavalinkManager.emit("playerCreate", this);
72
74
  if (typeof options.volume === "number" && !isNaN(options.volume))
73
75
  this.setVolume(options.volume);
74
- this.queue = new Queue(this.guildId, {}, new QueueSaver(this.LavalinkManager.options.queueStore, this.LavalinkManager.options.queueOptions), this.LavalinkManager.options.queueChangesWatcher);
76
+ this.queue = new Queue(this.guildId, {}, new QueueSaver(this.LavalinkManager.options.queueOptions), this.LavalinkManager.options.queueOptions);
75
77
  }
76
78
  /**
77
79
  * Set custom data.
@@ -18,6 +18,8 @@ export interface StoreManager extends Record<any, any> {
18
18
  }
19
19
  export interface QueueSaverOptions {
20
20
  maxPreviousTracks: number;
21
+ queueStore?: StoreManager;
22
+ queueChangesWatcher?: QueueChangesWatcher;
21
23
  }
22
24
  export interface QueueSaver {
23
25
  /** @private */
@@ -26,7 +28,7 @@ export interface QueueSaver {
26
28
  options: QueueSaverOptions;
27
29
  }
28
30
  export declare class QueueSaver {
29
- constructor(storeManager: StoreManager, options: QueueSaverOptions);
31
+ constructor(options: QueueSaverOptions);
30
32
  get(guildId: string): Promise<Partial<StoredQueue>>;
31
33
  delete(guildId: string): Promise<any>;
32
34
  set(guildId: string, value: any): Promise<any>;
@@ -59,7 +61,7 @@ export declare class Queue {
59
61
  static readonly StaticSymbol: Symbol;
60
62
  private managerUtils;
61
63
  private queueChanges;
62
- constructor(guildId: string, data?: Partial<StoredQueue>, QueueSaver?: QueueSaver, queueChangesWatcher?: QueueChangesWatcher);
64
+ constructor(guildId: string, data?: Partial<StoredQueue>, QueueSaver?: QueueSaver, queueOptions?: QueueSaverOptions);
63
65
  /**
64
66
  * Utils for a Queue
65
67
  */
@@ -1,8 +1,10 @@
1
1
  import { ManagerUitls, QueueSymbol } from "./Utils";
2
2
  export class QueueSaver {
3
- constructor(storeManager, options) {
4
- this._ = storeManager;
5
- this.options = options;
3
+ constructor(options) {
4
+ this._ = options.queueStore || new DefaultQueueStore();
5
+ this.options = {
6
+ maxPreviousTracks: options.maxPreviousTracks
7
+ };
6
8
  }
7
9
  async get(guildId) {
8
10
  return await this._.parse(await this._.get(guildId));
@@ -60,8 +62,8 @@ export class Queue {
60
62
  static StaticSymbol = QueueSymbol;
61
63
  managerUtils = new ManagerUitls();
62
64
  queueChanges;
63
- constructor(guildId, data = {}, QueueSaver, queueChangesWatcher) {
64
- this.queueChanges = queueChangesWatcher || null;
65
+ constructor(guildId, data = {}, QueueSaver, queueOptions) {
66
+ this.queueChanges = queueOptions.queueChangesWatcher || null;
65
67
  this.guildId = guildId;
66
68
  this.QueueSaver = QueueSaver;
67
69
  this.options.maxPreviousTracks = this.QueueSaver?.options?.maxPreviousTracks ?? this.options.maxPreviousTracks;
@@ -1,6 +1,6 @@
1
1
  import { LavalinkFilterData } from "./Filters";
2
2
  import { LavalinkManager } from "./LavalinkManager";
3
- import { LavalinkNode, NodeStats } from "./Node";
3
+ import { LavalinkNode, LavalinkNodeOptions, NodeStats } from "./Node";
4
4
  import { PlayOptions } from "./Player";
5
5
  import { Queue } from "./Queue";
6
6
  import { PluginInfo, Track } from "./Track";
@@ -40,10 +40,20 @@ export interface ManagerUitls {
40
40
  export declare class ManagerUitls {
41
41
  constructor(LavalinkManager?: LavalinkManager);
42
42
  buildTrack(data: any, requester: any): Track;
43
+ /**
44
+ * Validate if a data is equal to a node
45
+ * @param data
46
+ */
47
+ isNode(data: LavalinkNode): boolean;
48
+ /**
49
+ * Validate if a data is equal to node options
50
+ * @param data
51
+ */
52
+ isNodeOptions(data: LavalinkNodeOptions | any): boolean;
43
53
  /**
44
54
  * Validate if a data is euqal to a track
45
- * @param {Track|any} data the Track to validate
46
- * @returns {boolean}
55
+ * @param data the Track to validate
56
+ * @returns
47
57
  */
48
58
  isTrack(data: Track | any): boolean;
49
59
  validatedQuery(queryString: string, node: LavalinkNode): void;
@@ -85,6 +95,8 @@ export declare class MiniMap<K, V> extends Map<K, V> {
85
95
  filter<This, K2 extends K>(fn: (this: This, value: V, key: K, miniMap: this) => key is K2, thisArg: This): MiniMap<K2, V>;
86
96
  filter<This, V2 extends V>(fn: (this: This, value: V, key: K, miniMap: this) => value is V2, thisArg: This): MiniMap<K, V2>;
87
97
  filter<This>(fn: (this: This, value: V, key: K, miniMap: this) => boolean, thisArg: This): MiniMap<K, V>;
98
+ toJSON(): V[];
99
+ private static defaultSort;
88
100
  /**
89
101
  * Maps each item to another value into an array. Identical in behavior to
90
102
  * [Array.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).
@@ -38,10 +38,58 @@ export class ManagerUitls {
38
38
  throw new RangeError(`Argument "data" is not a valid track: ${error.message}`);
39
39
  }
40
40
  }
41
+ /**
42
+ * Validate if a data is equal to a node
43
+ * @param data
44
+ */
45
+ isNode(data) {
46
+ if (!data)
47
+ return false;
48
+ const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(data));
49
+ if (!keys.includes("constructor"))
50
+ return false;
51
+ if (!keys.length)
52
+ return false;
53
+ // all required functions
54
+ if (!["connect", "destroy", "destroyPlayer", "fetchAllPlayers", "fetchInfo", "fetchPlayer", "fetchStats", "fetchVersion", "request", "updatePlayer", "updateSession"].every(v => keys.includes(v)))
55
+ return false;
56
+ return true;
57
+ }
58
+ /**
59
+ * Validate if a data is equal to node options
60
+ * @param data
61
+ */
62
+ isNodeOptions(data) {
63
+ if (!data || typeof data !== "object" || Array.isArray(data))
64
+ return false;
65
+ if (typeof data.host !== "string" || !data.host.length)
66
+ return false;
67
+ if (typeof data.port !== "number" || isNaN(data.port) || data.port < 0 || data.port > 65535)
68
+ return false;
69
+ if (typeof data.authorization !== "string" || !data.authorization.length)
70
+ return false;
71
+ if ("secure" in data && typeof data.secure !== "boolean")
72
+ return false;
73
+ if ("sessionId" in data && typeof data.sessionId !== "string")
74
+ return false;
75
+ if ("id" in data && typeof data.id !== "string")
76
+ return false;
77
+ if ("regions" in data && (!Array.isArray(data.regions) || !data.regions.every(v => typeof v === "string")))
78
+ return false;
79
+ if ("poolOptions" in data && typeof data.poolOptions !== "object")
80
+ return false;
81
+ if ("retryAmount" in data && (typeof data.retryAmount !== "number" || isNaN(data.retryAmount) || data.retryAmount <= 0))
82
+ return false;
83
+ if ("retryDelay" in data && (typeof data.retryDelay !== "number" || isNaN(data.retryDelay) || data.retryDelay <= 0))
84
+ return false;
85
+ if ("requestTimeout" in data && (typeof data.requestTimeout !== "number" || isNaN(data.requestTimeout) || data.requestTimeout <= 0))
86
+ return false;
87
+ return true;
88
+ }
41
89
  /**
42
90
  * Validate if a data is euqal to a track
43
- * @param {Track|any} data the Track to validate
44
- * @returns {boolean}
91
+ * @param data the Track to validate
92
+ * @returns
45
93
  */
46
94
  isTrack(data) {
47
95
  return typeof data?.encoded === "string" && typeof data?.info === "object";
@@ -148,6 +196,13 @@ export class MiniMap extends Map {
148
196
  }
149
197
  return results;
150
198
  }
199
+ toJSON() {
200
+ // toJSON is called recursively by JSON.stringify.
201
+ return [...this.values()];
202
+ }
203
+ static defaultSort(firstValue, secondValue) {
204
+ return Number(firstValue > secondValue) || Number(firstValue === secondValue) - 1;
205
+ }
151
206
  map(fn, thisArg) {
152
207
  if (typeof thisArg !== 'undefined')
153
208
  fn = fn.bind(thisArg);
@@ -165,11 +220,11 @@ export async function queueTrackEnd(queue, addBackToQueue = false) {
165
220
  if (queue.previous.length > queue.options.maxPreviousTracks)
166
221
  queue.previous.splice(queue.options.maxPreviousTracks, queue.previous.length);
167
222
  }
168
- // change the current Track to the next upcoming one
169
- queue.current = queue.tracks.shift() || null;
170
223
  // and if repeatMode == queue, add it back to the queue!
171
224
  if (addBackToQueue && queue.current)
172
225
  queue.tracks.push(queue.current);
226
+ // change the current Track to the next upcoming one
227
+ queue.current = queue.tracks.shift() || null;
173
228
  // save it in the DB
174
229
  await queue.utils.save();
175
230
  // return the new current Track
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  import { EventEmitter } from "events";
3
3
  import { NodeManager } from "./NodeManager";
4
- import { QueueChangesWatcher, QueueSaverOptions, StoreManager } from "./Queue";
4
+ import { QueueSaverOptions } from "./Queue";
5
5
  import { GuildShardPayload, LavalinkSearchPlatform, ManagerUitls, MiniMap, SearchPlatform, TrackEndEvent, TrackExceptionEvent, TrackStartEvent, TrackStuckEvent, VoicePacket, VoiceServer, VoiceState, WebSocketClosedEvent } from "./Utils";
6
6
  import { LavalinkNodeOptions } from "./Node";
7
7
  import { DestroyReasonsType, Player, PlayerOptions } from "./Player";
@@ -44,11 +44,11 @@ export interface LavalinkPlayerOptions {
44
44
  export interface ManagerOptions {
45
45
  nodes: LavalinkNodeOptions[];
46
46
  queueOptions?: QueueSaverOptions;
47
- queueStore?: StoreManager;
48
- queueChangesWatcher?: QueueChangesWatcher;
49
47
  client?: BotClientOptions;
50
48
  playerOptions?: LavalinkPlayerOptions;
51
49
  autoSkip?: boolean;
50
+ defaultLeastUsedNodeSortType?: "memory" | "calls" | "players";
51
+ defaultLeastLoadNodeSortType?: "cpu" | "memory";
52
52
  /** @async */
53
53
  sendToShard: (guildId: string, payload: GuildShardPayload) => void;
54
54
  }
@@ -56,6 +56,17 @@ export declare class NodeManager extends EventEmitter {
56
56
  constructor(LavalinkManager: LavalinkManager);
57
57
  createNode(options: LavalinkNodeOptions): LavalinkNode;
58
58
  get leastUsedNodes(): LavalinkNode[];
59
+ /** Returns the least used Nodes sorted by amount of calls. */
60
+ private get leastUsedNodesCalls();
61
+ /** Returns the least used Nodes sorted by amount of players. */
62
+ private get leastUsedNodesPlayers();
63
+ /** Returns the least used Nodes sorted by amount of memory usage. */
64
+ private get leastUsedNodesMemory();
65
+ /** Returns the least system load Nodes. */
66
+ get leastLoadNodes(): LavalinkNode[];
67
+ get leastLoadNodesMemory(): LavalinkNode[];
68
+ /** Returns the least system load Nodes. */
69
+ get leastLoadNodesCpu(): LavalinkNode[];
59
70
  deleteNode(node: LavalinkNodeIdentifier | LavalinkNode): void;
60
71
  }
61
72
  export {};
@@ -18,6 +18,8 @@ export interface StoreManager extends Record<any, any> {
18
18
  }
19
19
  export interface QueueSaverOptions {
20
20
  maxPreviousTracks: number;
21
+ queueStore?: StoreManager;
22
+ queueChangesWatcher?: QueueChangesWatcher;
21
23
  }
22
24
  export interface QueueSaver {
23
25
  /** @private */
@@ -26,7 +28,7 @@ export interface QueueSaver {
26
28
  options: QueueSaverOptions;
27
29
  }
28
30
  export declare class QueueSaver {
29
- constructor(storeManager: StoreManager, options: QueueSaverOptions);
31
+ constructor(options: QueueSaverOptions);
30
32
  get(guildId: string): Promise<Partial<StoredQueue>>;
31
33
  delete(guildId: string): Promise<any>;
32
34
  set(guildId: string, value: any): Promise<any>;
@@ -59,7 +61,7 @@ export declare class Queue {
59
61
  static readonly StaticSymbol: Symbol;
60
62
  private managerUtils;
61
63
  private queueChanges;
62
- constructor(guildId: string, data?: Partial<StoredQueue>, QueueSaver?: QueueSaver, queueChangesWatcher?: QueueChangesWatcher);
64
+ constructor(guildId: string, data?: Partial<StoredQueue>, QueueSaver?: QueueSaver, queueOptions?: QueueSaverOptions);
63
65
  /**
64
66
  * Utils for a Queue
65
67
  */
@@ -1,6 +1,6 @@
1
1
  import { LavalinkFilterData } from "./Filters";
2
2
  import { LavalinkManager } from "./LavalinkManager";
3
- import { LavalinkNode, NodeStats } from "./Node";
3
+ import { LavalinkNode, LavalinkNodeOptions, NodeStats } from "./Node";
4
4
  import { PlayOptions } from "./Player";
5
5
  import { Queue } from "./Queue";
6
6
  import { PluginInfo, Track } from "./Track";
@@ -40,10 +40,20 @@ export interface ManagerUitls {
40
40
  export declare class ManagerUitls {
41
41
  constructor(LavalinkManager?: LavalinkManager);
42
42
  buildTrack(data: any, requester: any): Track;
43
+ /**
44
+ * Validate if a data is equal to a node
45
+ * @param data
46
+ */
47
+ isNode(data: LavalinkNode): boolean;
48
+ /**
49
+ * Validate if a data is equal to node options
50
+ * @param data
51
+ */
52
+ isNodeOptions(data: LavalinkNodeOptions | any): boolean;
43
53
  /**
44
54
  * Validate if a data is euqal to a track
45
- * @param {Track|any} data the Track to validate
46
- * @returns {boolean}
55
+ * @param data the Track to validate
56
+ * @returns
47
57
  */
48
58
  isTrack(data: Track | any): boolean;
49
59
  validatedQuery(queryString: string, node: LavalinkNode): void;
@@ -85,6 +95,8 @@ export declare class MiniMap<K, V> extends Map<K, V> {
85
95
  filter<This, K2 extends K>(fn: (this: This, value: V, key: K, miniMap: this) => key is K2, thisArg: This): MiniMap<K2, V>;
86
96
  filter<This, V2 extends V>(fn: (this: This, value: V, key: K, miniMap: this) => value is V2, thisArg: This): MiniMap<K, V2>;
87
97
  filter<This>(fn: (this: This, value: V, key: K, miniMap: this) => boolean, thisArg: This): MiniMap<K, V>;
98
+ toJSON(): V[];
99
+ private static defaultSort;
88
100
  /**
89
101
  * Maps each item to another value into an array. Identical in behavior to
90
102
  * [Array.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lavalink-client",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Easy and advanced lavalink client. Use it with lavalink plugins as well as latest lavalink versions",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",