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
|
}
|
|
@@ -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
|
-
|
|
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
|
|
@@ -445,15 +445,15 @@ export class LavalinkNode {
|
|
|
445
445
|
player.ping.ws = payload.state.ping >= 0 ? payload.state.ping : player.ping.ws <= 0 && player.connected ? null : player.ping.ws || 0;
|
|
446
446
|
if (!player.createdTimeStamp && payload.state.time)
|
|
447
447
|
player.createdTimeStamp = payload.state.time;
|
|
448
|
-
if (typeof this.NodeManager.LavalinkManager.options.playerOptions.
|
|
448
|
+
if (typeof this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval === "number" && this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval >= 10) {
|
|
449
449
|
player.set("internal_updateInterval", setInterval(() => {
|
|
450
|
-
player.position += this.NodeManager.LavalinkManager.options.playerOptions.
|
|
450
|
+
player.position += this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval || 250;
|
|
451
451
|
if (player.filterManager.filterUpdatedState >= 1) {
|
|
452
452
|
player.filterManager.filterUpdatedState++;
|
|
453
453
|
const maxMins = 8;
|
|
454
454
|
const currentDuration = player.queue.current?.info?.duration || 0;
|
|
455
455
|
if (currentDuration <= maxMins * 6e4 || isAbsolute(player.queue.current?.info?.uri)) {
|
|
456
|
-
if (player.filterManager.filterUpdatedState >= ((this.NodeManager.LavalinkManager.options.playerOptions.
|
|
456
|
+
if (player.filterManager.filterUpdatedState >= ((this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval || 250) > 400 ? 2 : 3)) {
|
|
457
457
|
player.filterManager.filterUpdatedState = 0;
|
|
458
458
|
player.seek(player.position);
|
|
459
459
|
}
|
|
@@ -462,7 +462,7 @@ export class LavalinkNode {
|
|
|
462
462
|
player.filterManager.filterUpdatedState = 0;
|
|
463
463
|
}
|
|
464
464
|
}
|
|
465
|
-
}, this.NodeManager.LavalinkManager.options.playerOptions.
|
|
465
|
+
}, this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval || 250));
|
|
466
466
|
}
|
|
467
467
|
else {
|
|
468
468
|
if (player.filterManager.filterUpdatedState >= 1) { // if no interval but instafix available, findable via the "filterUpdatedState" property
|
|
@@ -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({
|
|
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,11 +545,18 @@ 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({
|
|
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;
|
|
552
552
|
player.playing = false;
|
|
553
|
+
if (typeof this.NodeManager.LavalinkManager.options?.playerOptions?.onEmptyQueue?.autoPlayFunction === "function") {
|
|
554
|
+
await this.NodeManager.LavalinkManager.options?.playerOptions?.onEmptyQueue?.autoPlayFunction(player, track);
|
|
555
|
+
if (player.queue.tracks.length > 0)
|
|
556
|
+
await queueTrackEnd(player.queue, player.repeatMode === "queue");
|
|
557
|
+
if (player.queue.current)
|
|
558
|
+
return player.play({ noReplace: true, paused: false });
|
|
559
|
+
}
|
|
553
560
|
if (payload?.reason !== "stopped") {
|
|
554
561
|
await player.queue.utils.save();
|
|
555
562
|
}
|
|
@@ -579,7 +586,7 @@ export class LavalinkNode {
|
|
|
579
586
|
if (!player.queue.current)
|
|
580
587
|
return this.queueEnd(player, track, payload);
|
|
581
588
|
// play track if autoSkip is true
|
|
582
|
-
return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({
|
|
589
|
+
return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
|
|
583
590
|
}
|
|
584
591
|
async trackError(player, track, payload) {
|
|
585
592
|
this.NodeManager.LavalinkManager.emit("trackError", player, track, payload);
|
|
@@ -589,7 +596,7 @@ export class LavalinkNode {
|
|
|
589
596
|
if (!player.queue.current)
|
|
590
597
|
return this.queueEnd(player, track, payload);
|
|
591
598
|
// play track if autoSkip is true
|
|
592
|
-
return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({
|
|
599
|
+
return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
|
|
593
600
|
}
|
|
594
601
|
socketClosed(player, payload) {
|
|
595
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
@@ -200,8 +202,9 @@ export class Player {
|
|
|
200
202
|
Query.source = DefaultSources[foundSource]; // set the source to ytsearch:
|
|
201
203
|
Query.query = Query.query.replace(`${foundSource}:`, ""); // remove ytsearch: from the query
|
|
202
204
|
}
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
+
// 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
|
|
206
|
+
// request the data
|
|
207
|
+
const res = await this.node.request(`/loadtracks?identifier=${!/^https?:\/\//.test(Query.query) ? `${Query.source}:${Query.source === "ftts" ? "//" : ""}` : ""}${encodeURIComponent(Query.query)}`);
|
|
205
208
|
// transform the data which can be Error, Track or Track[] to enfore [Track]
|
|
206
209
|
const resTracks = res.loadType === "playlist" ? res.data?.tracks : res.loadType === "track" ? [res.data] : res.loadType === "search" ? Array.isArray(res.data) ? res.data : [res.data] : [];
|
|
207
210
|
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
|
*/
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { ManagerUitls, QueueSymbol } from "./Utils";
|
|
2
2
|
export class QueueSaver {
|
|
3
|
-
constructor(
|
|
4
|
-
this._ =
|
|
5
|
-
this.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,15 +62,14 @@ export class Queue {
|
|
|
60
62
|
static StaticSymbol = QueueSymbol;
|
|
61
63
|
managerUtils = new ManagerUitls();
|
|
62
64
|
queueChanges;
|
|
63
|
-
constructor(guildId, data = {}, QueueSaver,
|
|
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;
|
|
68
70
|
this.current = this.managerUtils.isTrack(data.current) ? data.current : null;
|
|
69
71
|
this.previous = Array.isArray(data.previous) && data.previous.some(track => this.managerUtils.isTrack(track)) ? data.previous.filter(track => this.managerUtils.isTrack(track)) : [];
|
|
70
72
|
this.tracks = Array.isArray(data.tracks) && data.tracks.some(track => this.managerUtils.isTrack(track)) ? data.tracks.filter(track => this.managerUtils.isTrack(track)) : [];
|
|
71
|
-
this.utils.sync(false, true);
|
|
72
73
|
}
|
|
73
74
|
/**
|
|
74
75
|
* 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).
|
|
@@ -29,7 +29,7 @@ export class ManagerUitls {
|
|
|
29
29
|
isrc: data.info?.isrc,
|
|
30
30
|
},
|
|
31
31
|
pluginInfo: data.pluginInfo || data.plugin || {},
|
|
32
|
-
requester: data?.requester || requester,
|
|
32
|
+
requester: typeof this.manager.options?.playerOptions?.requesterTransformer === "function" ? this.manager.options?.playerOptions?.requesterTransformer(data?.requester || requester) : requester,
|
|
33
33
|
};
|
|
34
34
|
Object.defineProperty(r, TrackSymbol, { configurable: true, value: true });
|
|
35
35
|
return r;
|
|
@@ -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
|
|
44
|
-
* @returns
|
|
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";
|
|
@@ -118,6 +166,10 @@ export class ManagerUitls {
|
|
|
118
166
|
if (source === "tts" && !node.info.sourceManagers.includes("tts")) {
|
|
119
167
|
throw new Error("Lavalink Node has not 'tts' enabled, which is required to have 'tts' work");
|
|
120
168
|
}
|
|
169
|
+
if (source === "ftts" && !node.info.sourceManagers.includes("ftts")) {
|
|
170
|
+
console.log(node.info.sourceManagers);
|
|
171
|
+
throw new Error("Lavalink Node has not 'ftts' enabled, which is required to have 'ftts' work");
|
|
172
|
+
}
|
|
121
173
|
if (source === "ymsearch" && !node.info.sourceManagers.includes("yandexmusic")) {
|
|
122
174
|
throw new Error("Lavalink Node has not 'yandexmusic' enabled, which is required to have 'ymsearch' work");
|
|
123
175
|
}
|
|
@@ -144,6 +196,13 @@ export class MiniMap extends Map {
|
|
|
144
196
|
}
|
|
145
197
|
return results;
|
|
146
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
|
+
}
|
|
147
206
|
map(fn, thisArg) {
|
|
148
207
|
if (typeof thisArg !== 'undefined')
|
|
149
208
|
fn = fn.bind(thisArg);
|
|
@@ -161,11 +220,11 @@ export async function queueTrackEnd(queue, addBackToQueue = false) {
|
|
|
161
220
|
if (queue.previous.length > queue.options.maxPreviousTracks)
|
|
162
221
|
queue.previous.splice(queue.options.maxPreviousTracks, queue.previous.length);
|
|
163
222
|
}
|
|
164
|
-
// change the current Track to the next upcoming one
|
|
165
|
-
queue.current = queue.tracks.shift() || null;
|
|
166
223
|
// and if repeatMode == queue, add it back to the queue!
|
|
167
224
|
if (addBackToQueue && queue.current)
|
|
168
225
|
queue.tracks.push(queue.current);
|
|
226
|
+
// change the current Track to the next upcoming one
|
|
227
|
+
queue.current = queue.tracks.shift() || null;
|
|
169
228
|
// save it in the DB
|
|
170
229
|
await queue.utils.save();
|
|
171
230
|
// return the new current Track
|
|
@@ -17,7 +17,7 @@ export interface BotClientOptions {
|
|
|
17
17
|
}
|
|
18
18
|
export interface LavalinkPlayerOptions {
|
|
19
19
|
volumeDecrementer?: number;
|
|
20
|
-
|
|
20
|
+
clientBasedPositionUpdateInterval?: number;
|
|
21
21
|
defaultSearchPlatform?: SearchPlatform;
|
|
22
22
|
applyVolumeAsFilter?: boolean;
|
|
23
23
|
}
|