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.
Files changed (31) hide show
  1. package/dist/cjs/structures/LavalinkManager.d.ts +15 -4
  2. package/dist/cjs/structures/LavalinkManager.js +72 -11
  3. package/dist/cjs/structures/LavalinkManagerStatics.js +1 -0
  4. package/dist/cjs/structures/Node.js +17 -10
  5. package/dist/cjs/structures/NodeManager.d.ts +11 -0
  6. package/dist/cjs/structures/NodeManager.js +60 -3
  7. package/dist/cjs/structures/Player.js +7 -4
  8. package/dist/cjs/structures/Queue.d.ts +4 -2
  9. package/dist/cjs/structures/Queue.js +7 -6
  10. package/dist/cjs/structures/Track.d.ts +9 -1
  11. package/dist/cjs/structures/Utils.d.ts +16 -4
  12. package/dist/cjs/structures/Utils.js +64 -5
  13. package/dist/esm/structures/LavalinkManager.d.ts +15 -4
  14. package/dist/esm/structures/LavalinkManager.js +72 -11
  15. package/dist/esm/structures/LavalinkManagerStatics.js +1 -0
  16. package/dist/esm/structures/Node.js +17 -10
  17. package/dist/esm/structures/NodeManager.d.ts +11 -0
  18. package/dist/esm/structures/NodeManager.js +60 -3
  19. package/dist/esm/structures/Player.js +7 -4
  20. package/dist/esm/structures/Queue.d.ts +4 -2
  21. package/dist/esm/structures/Queue.js +7 -6
  22. package/dist/esm/structures/Track.d.ts +9 -1
  23. package/dist/esm/structures/Utils.d.ts +16 -4
  24. package/dist/esm/structures/Utils.js +64 -5
  25. package/dist/structures/LavalinkManager.d.ts +1 -1
  26. package/dist/types/structures/LavalinkManager.d.ts +15 -4
  27. package/dist/types/structures/NodeManager.d.ts +11 -0
  28. package/dist/types/structures/Queue.d.ts +4 -2
  29. package/dist/types/structures/Track.d.ts +9 -1
  30. package/dist/types/structures/Utils.d.ts +16 -4
  31. 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 { 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";
@@ -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
- clientBasedUpdateInterval?: number;
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
- 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))
@@ -40,6 +40,7 @@ exports.DefaultSources = {
40
40
  // speak PLUGIN
41
41
  "speak": "speak",
42
42
  "tts": "tts",
43
+ "ftts": "ftts"
43
44
  };
44
45
  exports.SourceLinksRegexes = {
45
46
  /** DEFAULT SUPPORTED BY LAVALINK */
@@ -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.clientBasedUpdateInterval === "number" && this.NodeManager.LavalinkManager.options.playerOptions.clientBasedUpdateInterval >= 10) {
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.clientBasedUpdateInterval || 250;
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.clientBasedUpdateInterval || 250) > 400 ? 2 : 3)) {
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.clientBasedUpdateInterval || 250));
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({ 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,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({ 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;
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({ track: player.queue.current, noReplace: true });
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({ track: player.queue.current, noReplace: true });
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
- 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.
@@ -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
- // request the data
207
- const res = await this.node.request(`/loadtracks?identifier=${!/^https?:\/\//.test(Query.query) ? `${Query.source}:` : ""}${encodeURIComponent(Query.query)}`);
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(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,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, 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;
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: string;
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 {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).
@@ -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 {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";
@@ -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