lavalink-client 1.0.2 → 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.
- package/dist/cjs/structures/LavalinkManager.d.ts +15 -4
- package/dist/cjs/structures/LavalinkManager.js +72 -11
- package/dist/cjs/structures/LavalinkManagerStatics.js +1 -0
- package/dist/cjs/structures/Node.js +17 -10
- package/dist/cjs/structures/NodeManager.d.ts +11 -0
- package/dist/cjs/structures/NodeManager.js +60 -3
- package/dist/cjs/structures/Player.js +7 -4
- package/dist/cjs/structures/Queue.d.ts +4 -2
- package/dist/cjs/structures/Queue.js +7 -6
- package/dist/cjs/structures/Track.d.ts +9 -1
- package/dist/cjs/structures/Utils.d.ts +16 -4
- package/dist/cjs/structures/Utils.js +64 -5
- package/dist/esm/structures/LavalinkManager.d.ts +15 -4
- package/dist/esm/structures/LavalinkManager.js +72 -11
- package/dist/esm/structures/LavalinkManagerStatics.js +1 -0
- package/dist/esm/structures/Node.js +17 -10
- package/dist/esm/structures/NodeManager.d.ts +11 -0
- package/dist/esm/structures/NodeManager.js +60 -3
- package/dist/esm/structures/Player.js +7 -4
- package/dist/esm/structures/Queue.d.ts +4 -2
- package/dist/esm/structures/Queue.js +7 -6
- package/dist/esm/structures/Track.d.ts +9 -1
- package/dist/esm/structures/Utils.d.ts +16 -4
- package/dist/esm/structures/Utils.js +64 -5
- package/dist/structures/LavalinkManager.d.ts +1 -1
- package/dist/types/structures/LavalinkManager.d.ts +15 -4
- package/dist/types/structures/NodeManager.d.ts +11 -0
- package/dist/types/structures/Queue.d.ts +4 -2
- package/dist/types/structures/Track.d.ts +9 -1
- package/dist/types/structures/Utils.d.ts +16 -4
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { EventEmitter } from "events";
|
|
3
3
|
import { NodeManager } from "./NodeManager";
|
|
4
|
-
import {
|
|
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";
|
|
@@ -18,26 +18,37 @@ export interface BotClientOptions {
|
|
|
18
18
|
[x: string | number | symbol | undefined]: any;
|
|
19
19
|
}
|
|
20
20
|
export interface LavalinkPlayerOptions {
|
|
21
|
+
/** If the Lavalink Volume should be decremented by x number */
|
|
21
22
|
volumeDecrementer?: number;
|
|
22
|
-
|
|
23
|
+
/** How often it should update the the player Position */
|
|
24
|
+
clientBasedPositionUpdateInterval?: number;
|
|
25
|
+
/** What should be used as a searchPlatform, if no source was provided during the query */
|
|
23
26
|
defaultSearchPlatform?: SearchPlatform;
|
|
27
|
+
/** Applies the volume via a filter, not via the lavalink volume transformer */
|
|
24
28
|
applyVolumeAsFilter?: boolean;
|
|
29
|
+
/** Transforms the saved data of a requested user */
|
|
30
|
+
requesterTransformer?: (requester: unknown) => unknown;
|
|
31
|
+
/** What lavalink-client should do when the player reconnects */
|
|
25
32
|
onDisconnect?: {
|
|
33
|
+
/** Try to reconnect? -> If fails -> Destroy */
|
|
26
34
|
autoReconnect?: boolean;
|
|
35
|
+
/** Instantly destroy player (overrides autoReconnect) */
|
|
27
36
|
destroyPlayer?: boolean;
|
|
28
37
|
};
|
|
29
38
|
onEmptyQueue?: {
|
|
39
|
+
/** Get's executed onEmptyQueue -> You can do any track queue previous transformations, if you add a track to the queue -> it will play it, if not queueEnd will execute! */
|
|
40
|
+
autoPlayFunction?: (player: Player, lastPlayedTrack: Track) => Promise<void>;
|
|
30
41
|
destroyAfterMs?: number;
|
|
31
42
|
};
|
|
32
43
|
}
|
|
33
44
|
export interface ManagerOptions {
|
|
34
45
|
nodes: LavalinkNodeOptions[];
|
|
35
46
|
queueOptions?: QueueSaverOptions;
|
|
36
|
-
queueStore?: StoreManager;
|
|
37
|
-
queueChangesWatcher?: QueueChangesWatcher;
|
|
38
47
|
client?: BotClientOptions;
|
|
39
48
|
playerOptions?: LavalinkPlayerOptions;
|
|
40
49
|
autoSkip?: boolean;
|
|
50
|
+
defaultLeastUsedNodeSortType?: "memory" | "calls" | "players";
|
|
51
|
+
defaultLeastLoadNodeSortType?: "cpu" | "memory";
|
|
41
52
|
/** @async */
|
|
42
53
|
sendToShard: (guildId: string, payload: GuildShardPayload) => void;
|
|
43
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
|
-
|
|
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
|
|
@@ -449,15 +449,15 @@ class LavalinkNode {
|
|
|
449
449
|
player.ping.ws = payload.state.ping >= 0 ? payload.state.ping : player.ping.ws <= 0 && player.connected ? null : player.ping.ws || 0;
|
|
450
450
|
if (!player.createdTimeStamp && payload.state.time)
|
|
451
451
|
player.createdTimeStamp = payload.state.time;
|
|
452
|
-
if (typeof this.NodeManager.LavalinkManager.options.playerOptions.
|
|
452
|
+
if (typeof this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval === "number" && this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval >= 10) {
|
|
453
453
|
player.set("internal_updateInterval", setInterval(() => {
|
|
454
|
-
player.position += this.NodeManager.LavalinkManager.options.playerOptions.
|
|
454
|
+
player.position += this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval || 250;
|
|
455
455
|
if (player.filterManager.filterUpdatedState >= 1) {
|
|
456
456
|
player.filterManager.filterUpdatedState++;
|
|
457
457
|
const maxMins = 8;
|
|
458
458
|
const currentDuration = player.queue.current?.info?.duration || 0;
|
|
459
459
|
if (currentDuration <= maxMins * 6e4 || (0, path_1.isAbsolute)(player.queue.current?.info?.uri)) {
|
|
460
|
-
if (player.filterManager.filterUpdatedState >= ((this.NodeManager.LavalinkManager.options.playerOptions.
|
|
460
|
+
if (player.filterManager.filterUpdatedState >= ((this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval || 250) > 400 ? 2 : 3)) {
|
|
461
461
|
player.filterManager.filterUpdatedState = 0;
|
|
462
462
|
player.seek(player.position);
|
|
463
463
|
}
|
|
@@ -466,7 +466,7 @@ class LavalinkNode {
|
|
|
466
466
|
player.filterManager.filterUpdatedState = 0;
|
|
467
467
|
}
|
|
468
468
|
}
|
|
469
|
-
}, this.NodeManager.LavalinkManager.options.playerOptions.
|
|
469
|
+
}, this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval || 250));
|
|
470
470
|
}
|
|
471
471
|
else {
|
|
472
472
|
if (player.filterManager.filterUpdatedState >= 1) { // if no interval but instafix available, findable via the "filterUpdatedState" property
|
|
@@ -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({
|
|
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,11 +549,18 @@ 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({
|
|
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;
|
|
556
556
|
player.playing = false;
|
|
557
|
+
if (typeof this.NodeManager.LavalinkManager.options?.playerOptions?.onEmptyQueue?.autoPlayFunction === "function") {
|
|
558
|
+
await this.NodeManager.LavalinkManager.options?.playerOptions?.onEmptyQueue?.autoPlayFunction(player, track);
|
|
559
|
+
if (player.queue.tracks.length > 0)
|
|
560
|
+
await (0, Utils_1.queueTrackEnd)(player.queue, player.repeatMode === "queue");
|
|
561
|
+
if (player.queue.current)
|
|
562
|
+
return player.play({ noReplace: true, paused: false });
|
|
563
|
+
}
|
|
557
564
|
if (payload?.reason !== "stopped") {
|
|
558
565
|
await player.queue.utils.save();
|
|
559
566
|
}
|
|
@@ -583,7 +590,7 @@ class LavalinkNode {
|
|
|
583
590
|
if (!player.queue.current)
|
|
584
591
|
return this.queueEnd(player, track, payload);
|
|
585
592
|
// play track if autoSkip is true
|
|
586
|
-
return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({
|
|
593
|
+
return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
|
|
587
594
|
}
|
|
588
595
|
async trackError(player, track, payload) {
|
|
589
596
|
this.NodeManager.LavalinkManager.emit("trackError", player, track, payload);
|
|
@@ -593,7 +600,7 @@ class LavalinkNode {
|
|
|
593
600
|
if (!player.queue.current)
|
|
594
601
|
return this.queueEnd(player, track, payload);
|
|
595
602
|
// play track if autoSkip is true
|
|
596
|
-
return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({
|
|
603
|
+
return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
|
|
597
604
|
}
|
|
598
605
|
socketClosed(player, payload) {
|
|
599
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
@@ -203,8 +205,9 @@ class Player {
|
|
|
203
205
|
Query.source = LavalinkManagerStatics_1.DefaultSources[foundSource]; // set the source to ytsearch:
|
|
204
206
|
Query.query = Query.query.replace(`${foundSource}:`, ""); // remove ytsearch: from the query
|
|
205
207
|
}
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
+
// ftts query parameters: ?voice=Olivia&audio_format=ogg_opus&translate=False&silence=1000&speed=1.0 | example raw get query: https://api.flowery.pw/v1/tts?voice=Olivia&audio_format=ogg_opus&translate=False&silence=0&speed=1.0&text=Hello%20World
|
|
209
|
+
// request the data
|
|
210
|
+
const res = await this.node.request(`/loadtracks?identifier=${!/^https?:\/\//.test(Query.query) ? `${Query.source}:${Query.source === "ftts" ? "//" : ""}` : ""}${encodeURIComponent(Query.query)}`);
|
|
208
211
|
// transform the data which can be Error, Track or Track[] to enfore [Track]
|
|
209
212
|
const resTracks = res.loadType === "playlist" ? res.data?.tracks : res.loadType === "track" ? [res.data] : res.loadType === "search" ? Array.isArray(res.data) ? res.data : [res.data] : [];
|
|
210
213
|
return {
|
|
@@ -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(
|
|
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,
|
|
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(
|
|
7
|
-
this._ =
|
|
8
|
-
this.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,15 +68,14 @@ class Queue {
|
|
|
66
68
|
static StaticSymbol = Utils_1.QueueSymbol;
|
|
67
69
|
managerUtils = new Utils_1.ManagerUitls();
|
|
68
70
|
queueChanges;
|
|
69
|
-
constructor(guildId, data = {}, QueueSaver,
|
|
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;
|
|
74
76
|
this.current = this.managerUtils.isTrack(data.current) ? data.current : null;
|
|
75
77
|
this.previous = Array.isArray(data.previous) && data.previous.some(track => this.managerUtils.isTrack(track)) ? data.previous.filter(track => this.managerUtils.isTrack(track)) : [];
|
|
76
78
|
this.tracks = Array.isArray(data.tracks) && data.tracks.some(track => this.managerUtils.isTrack(track)) ? data.tracks.filter(track => this.managerUtils.isTrack(track)) : [];
|
|
77
|
-
this.utils.sync(false, true);
|
|
78
79
|
}
|
|
79
80
|
/**
|
|
80
81
|
* Utils for a Queue
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { Base64 } from "./Utils";
|
|
2
|
+
type LavalinkSourceNames = "youtube" | "youtubemusic" | "soundcloud" | "bandcamp" | "twitch";
|
|
3
|
+
type LavalinkPlugin_LavaSrc_SourceNames = "deezer" | "spotify" | "applemusic" | "yandexmusic" | "flowery-tts";
|
|
4
|
+
type SourceNames = LavalinkSourceNames | LavalinkPlugin_LavaSrc_SourceNames;
|
|
2
5
|
export interface TrackInfo {
|
|
3
6
|
identifier: string;
|
|
4
7
|
title: string;
|
|
@@ -6,7 +9,7 @@ export interface TrackInfo {
|
|
|
6
9
|
duration: number;
|
|
7
10
|
artworkUrl: string | null;
|
|
8
11
|
uri: string;
|
|
9
|
-
sourceName:
|
|
12
|
+
sourceName: SourceNames;
|
|
10
13
|
isSeekable: boolean;
|
|
11
14
|
isStream: boolean;
|
|
12
15
|
isrc: string | null;
|
|
@@ -24,6 +27,10 @@ export interface PluginInfo {
|
|
|
24
27
|
url?: string;
|
|
25
28
|
/** The Url provided by a Plugin */
|
|
26
29
|
uri?: string;
|
|
30
|
+
/** You can put specific track information here, to transform the tracks... */
|
|
31
|
+
clientData?: {
|
|
32
|
+
[key: string]: any;
|
|
33
|
+
};
|
|
27
34
|
}
|
|
28
35
|
export interface LavalinkTrack {
|
|
29
36
|
/** The Base 64 encoded String */
|
|
@@ -45,3 +52,4 @@ export interface UnresolvedQuery {
|
|
|
45
52
|
/** The Track's Requester */
|
|
46
53
|
requester?: unknown;
|
|
47
54
|
}
|
|
55
|
+
export {};
|
|
@@ -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";
|
|
@@ -8,7 +8,7 @@ export declare const TrackSymbol: unique symbol;
|
|
|
8
8
|
export declare const UnresolvedTrackSymbol: unique symbol;
|
|
9
9
|
export declare const QueueSymbol: unique symbol;
|
|
10
10
|
export declare const NodeSymbol: unique symbol;
|
|
11
|
-
export type LavalinkSearchPlatform = "ytsearch" | "ytmsearch" | "scsearch" | "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "sprec" | "ymsearch" | "speak" | "tts";
|
|
11
|
+
export type LavalinkSearchPlatform = "ytsearch" | "ytmsearch" | "scsearch" | "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "sprec" | "ymsearch" | "speak" | "tts" | "ftts";
|
|
12
12
|
export type ClientSearchPlatform = "youtube" | "yt" | "yt" | "youtube music" | "youtubemusic" | "ytm" | "soundcloud" | "sc" | "am" | "apple music" | "applemusic" | "apple" | "yandex music" | "sp" | "sprec" | "spsuggestion" | "spotify" | "dz" | "deezer" | "yandex" | "yandexmusic";
|
|
13
13
|
export type SearchPlatform = LavalinkSearchPlatform | ClientSearchPlatform;
|
|
14
14
|
export type SourcesRegex = "YoutubeRegex" | "YoutubeMusicRegex" | "SoundCloudRegex" | "SoundCloudMobileRegex" | "DeezerTrackRegex" | "DeezerArtistRegex" | "DeezerEpisodeRegex" | "DeezerMixesRegex" | "DeezerPageLinkRegex" | "DeezerPlaylistRegex" | "DeezerAlbumRegex" | "AllDeezerRegex" | "AllDeezerRegexWithoutPageLink" | "SpotifySongRegex" | "SpotifyPlaylistRegex" | "SpotifyArtistRegex" | "SpotifyEpisodeRegex" | "SpotifyShowRegex" | "SpotifyAlbumRegex" | "AllSpotifyRegex" | "mp3Url" | "m3uUrl" | "m3u8Url" | "mp4Url" | "m4aUrl" | "wavUrl" | "aacpUrl" | "tiktok" | "mixcloud" | "musicYandex" | "radiohost" | "bandcamp" | "appleMusic" | "TwitchTv" | "vimeo";
|
|
@@ -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
|
|
46
|
-
* @returns
|
|
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).
|
|
@@ -32,7 +32,7 @@ class ManagerUitls {
|
|
|
32
32
|
isrc: data.info?.isrc,
|
|
33
33
|
},
|
|
34
34
|
pluginInfo: data.pluginInfo || data.plugin || {},
|
|
35
|
-
requester: data?.requester || requester,
|
|
35
|
+
requester: typeof this.manager.options?.playerOptions?.requesterTransformer === "function" ? this.manager.options?.playerOptions?.requesterTransformer(data?.requester || requester) : requester,
|
|
36
36
|
};
|
|
37
37
|
Object.defineProperty(r, exports.TrackSymbol, { configurable: true, value: true });
|
|
38
38
|
return r;
|
|
@@ -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
|
|
47
|
-
* @returns
|
|
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";
|
|
@@ -121,6 +169,10 @@ class ManagerUitls {
|
|
|
121
169
|
if (source === "tts" && !node.info.sourceManagers.includes("tts")) {
|
|
122
170
|
throw new Error("Lavalink Node has not 'tts' enabled, which is required to have 'tts' work");
|
|
123
171
|
}
|
|
172
|
+
if (source === "ftts" && !node.info.sourceManagers.includes("ftts")) {
|
|
173
|
+
console.log(node.info.sourceManagers);
|
|
174
|
+
throw new Error("Lavalink Node has not 'ftts' enabled, which is required to have 'ftts' work");
|
|
175
|
+
}
|
|
124
176
|
if (source === "ymsearch" && !node.info.sourceManagers.includes("yandexmusic")) {
|
|
125
177
|
throw new Error("Lavalink Node has not 'yandexmusic' enabled, which is required to have 'ymsearch' work");
|
|
126
178
|
}
|
|
@@ -148,6 +200,13 @@ class MiniMap extends Map {
|
|
|
148
200
|
}
|
|
149
201
|
return results;
|
|
150
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
|
+
}
|
|
151
210
|
map(fn, thisArg) {
|
|
152
211
|
if (typeof thisArg !== 'undefined')
|
|
153
212
|
fn = fn.bind(thisArg);
|
|
@@ -166,11 +225,11 @@ async function queueTrackEnd(queue, addBackToQueue = false) {
|
|
|
166
225
|
if (queue.previous.length > queue.options.maxPreviousTracks)
|
|
167
226
|
queue.previous.splice(queue.options.maxPreviousTracks, queue.previous.length);
|
|
168
227
|
}
|
|
169
|
-
// change the current Track to the next upcoming one
|
|
170
|
-
queue.current = queue.tracks.shift() || null;
|
|
171
228
|
// and if repeatMode == queue, add it back to the queue!
|
|
172
229
|
if (addBackToQueue && queue.current)
|
|
173
230
|
queue.tracks.push(queue.current);
|
|
231
|
+
// change the current Track to the next upcoming one
|
|
232
|
+
queue.current = queue.tracks.shift() || null;
|
|
174
233
|
// save it in the DB
|
|
175
234
|
await queue.utils.save();
|
|
176
235
|
// return the new current Track
|