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
  }
@@ -10,36 +10,97 @@ export class LavalinkManager extends EventEmitter {
10
10
  initiated = false;
11
11
  players = new MiniMap();
12
12
  applyDefaultOptions() {
13
+ if (!this.options.playerOptions)
14
+ this.options.playerOptions = {
15
+ applyVolumeAsFilter: false,
16
+ clientBasedPositionUpdateInterval: 100,
17
+ defaultSearchPlatform: "ytsearch",
18
+ onDisconnect: {
19
+ destroyPlayer: true,
20
+ autoReconnect: false
21
+ },
22
+ onEmptyQueue: {
23
+ autoPlayFunction: null,
24
+ destroyAfterMs: undefined
25
+ },
26
+ requesterTransformer: (requester) => {
27
+ // if it's already the transformed requester
28
+ if (typeof requester === "object" && "avatar" in requester && Object.keys(requester).length === 3)
29
+ return requester;
30
+ // if it's still a discord.js User
31
+ if (typeof requester === "object" && "displayAvatarURL" in requester) { // it's a user
32
+ return {
33
+ id: requester.id,
34
+ username: requester.username,
35
+ avatar: requester.displayAvatarURL(),
36
+ };
37
+ }
38
+ // if it's non of the above
39
+ return { id: requester.toString(), username: "unknown" }; // reteurn something that makes sense for you!
40
+ },
41
+ volumeDecrementer: 1
42
+ };
43
+ if (!this.options.autoSkip)
44
+ this.options.autoSkip = true;
45
+ if (!this.options.defaultLeastLoadNodeSortType)
46
+ this.options.defaultLeastLoadNodeSortType = "memory";
47
+ if (!this.options.defaultLeastUsedNodeSortType)
48
+ this.options.defaultLeastUsedNodeSortType = "players";
13
49
  if (!this.options.playerOptions.defaultSearchPlatform)
14
50
  this.options.playerOptions.defaultSearchPlatform = "ytsearch";
51
+ // default queue options
52
+ if (!this.options.queueOptions)
53
+ this.options.queueOptions = {
54
+ maxPreviousTracks: 25,
55
+ queueChangesWatcher: null,
56
+ queueStore: new DefaultQueueStore()
57
+ };
15
58
  if (typeof this.options?.queueOptions?.maxPreviousTracks !== "number" || this.options.queueOptions.maxPreviousTracks < 0)
16
59
  this.options.queueOptions.maxPreviousTracks = 25;
17
60
  return;
18
61
  }
19
62
  validateAndApply(options) {
63
+ if (typeof options.sendToShard !== "function")
64
+ throw new SyntaxError("ManagerOption.sendToShard was not provided, which is required!");
65
+ // only check in .init()
66
+ // if(typeof options.client !== "object" || typeof options.client.id !== "string") throw new SyntaxError("ManagerOption.client = { id: string, username?:string, shards?: 'auto'|number } was not provided, which is required");
67
+ if (options.autoSkip && typeof options.autoSkip !== "boolean")
68
+ throw new SyntaxError("ManagerOption.autoSkip must be either false | true aka boolean");
69
+ if (options.defaultLeastLoadNodeSortType && !["memory", "cpu"].includes(options.defaultLeastLoadNodeSortType))
70
+ throw new SyntaxError("ManagerOption.defaultLeastLoadNodeSortType must be 'memory' | 'cpu'");
71
+ if (options.defaultLeastUsedNodeSortType && !["memory", "players", "calls"].includes(options.defaultLeastUsedNodeSortType))
72
+ throw new SyntaxError("ManagerOption.defaultLeastLoadNodeSortType must be 'memory' | 'calls' | 'players'");
73
+ if (!options.nodes || !Array.isArray(options.nodes) || !options.nodes.every(node => this.utils.isNodeOptions(node)))
74
+ throw new SyntaxError("ManagerOption.nodes must be an Array of NodeOptions and is required of at least 1 Node");
20
75
  /* QUEUE STORE */
21
- if (options.queueStore) {
22
- const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(options.queueStore));
76
+ if (options.queueOptions?.queueStore) {
77
+ const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(options.queueOptions?.queueStore));
23
78
  const requiredKeys = ["get", "set", "stringify", "parse", "delete"];
24
- if (!requiredKeys.every(v => keys.includes(v)) || !requiredKeys.every(v => typeof options.queueStore[v] === "function"))
25
- throw new SyntaxError(`The provided QueueStore, does not have all required functions: ${requiredKeys.join(", ")}`);
79
+ if (!requiredKeys.every(v => keys.includes(v)) || !requiredKeys.every(v => typeof options.queueOptions?.queueStore[v] === "function"))
80
+ throw new SyntaxError(`The provided ManagerOption.QueueStore, does not have all required functions: ${requiredKeys.join(", ")}`);
26
81
  }
27
82
  else
28
- this.options.queueStore = new DefaultQueueStore();
83
+ this.options.queueOptions.queueStore = new DefaultQueueStore();
84
+ /* QUEUE WATCHER */
85
+ if (options.queueOptions?.queueChangesWatcher) {
86
+ const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(options.queueOptions?.queueChangesWatcher));
87
+ const requiredKeys = ["tracksAdd", "tracksRemoved", "shuffled"];
88
+ if (!requiredKeys.every(v => keys.includes(v)) || !requiredKeys.every(v => typeof options.queueOptions?.queueChangesWatcher[v] === "function"))
89
+ throw new SyntaxError(`The provided ManagerOption.QueueChangesWatcher, does not have all required functions: ${requiredKeys.join(", ")}`);
90
+ }
29
91
  }
30
92
  constructor(options) {
31
93
  super();
94
+ if (!options)
95
+ throw new SyntaxError("No Manager Options Provided");
32
96
  // create options
33
- this.options = {
34
- autoSkip: true,
35
- ...options
36
- };
97
+ this.options = options;
98
+ this.utils = new ManagerUitls(this);
37
99
  // use the validators
38
- this.applyDefaultOptions();
39
100
  this.validateAndApply(options);
101
+ this.applyDefaultOptions();
40
102
  // create classes
41
103
  this.nodeManager = new NodeManager(this);
42
- this.utils = new ManagerUitls(this);
43
104
  }
44
105
  createPlayer(options) {
45
106
  if (this.players.has(options.guildId))
@@ -37,6 +37,7 @@ export const DefaultSources = {
37
37
  // speak PLUGIN
38
38
  "speak": "speak",
39
39
  "tts": "tts",
40
+ "ftts": "ftts"
40
41
  };
41
42
  export const SourceLinksRegexes = {
42
43
  /** DEFAULT SUPPORTED BY LAVALINK */
@@ -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.clientBasedUpdateInterval === "number" && this.NodeManager.LavalinkManager.options.playerOptions.clientBasedUpdateInterval >= 10) {
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.clientBasedUpdateInterval || 250;
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.clientBasedUpdateInterval || 250) > 400 ? 2 : 3)) {
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.clientBasedUpdateInterval || 250));
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({ track: player.queue.current, noReplace: true });
537
+ return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ noReplace: true });
538
538
  }
539
539
  // remove tracks from the queue
540
540
  if (player.repeatMode !== "track")
@@ -545,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({ track: player.queue.current, noReplace: true });
548
+ return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ noReplace: true });
549
549
  }
550
550
  async queueEnd(player, track, payload) {
551
551
  player.queue.current = null;
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({ track: player.queue.current, noReplace: true });
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({ track: player.queue.current, noReplace: true });
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
- return [...this.nodes.values()].filter(v => v);
21
+ if (this.LavalinkManager.options.defaultLeastUsedNodeSortType === "memory")
22
+ return this.leastUsedNodesMemory;
23
+ else if (this.LavalinkManager.options.defaultLeastUsedNodeSortType === "calls")
24
+ return this.leastUsedNodesCalls;
25
+ else
26
+ return this.leastUsedNodesPlayers; // this.options.defaultLeastUsedNodeSortType === "players"
27
+ }
28
+ /** Returns the least used Nodes sorted by amount of calls. */
29
+ get leastUsedNodesCalls() {
30
+ return [...this.nodes.values()]
31
+ .filter((node) => node.connected)
32
+ .sort((a, b) => b.calls - a.calls); // client sided sorting
33
+ }
34
+ /** Returns the least used Nodes sorted by amount of players. */
35
+ get leastUsedNodesPlayers() {
36
+ return [...this.nodes.values()]
37
+ .filter((node) => node.connected)
38
+ .sort((a, b) => (a.stats?.players || 0) - (b.stats?.players || 0));
39
+ }
40
+ /** Returns the least used Nodes sorted by amount of memory usage. */
41
+ get leastUsedNodesMemory() {
42
+ return [...this.nodes.values()]
43
+ .filter((node) => node.connected)
44
+ .sort((a, b) => (b.stats?.memory?.used || 0) - (a.stats?.memory?.used || 0)); // sort after memory
45
+ }
46
+ /** Returns the least system load Nodes. */
47
+ get leastLoadNodes() {
48
+ if (this.LavalinkManager.options.defaultLeastLoadNodeSortType === "cpu")
49
+ return this.leastLoadNodesCpu;
50
+ else
51
+ return this.leastLoadNodesMemory; // this.options.defaultLeastLoadNodeSortType === "memory"
52
+ }
53
+ get leastLoadNodesMemory() {
54
+ return [...this.nodes.values()]
55
+ .filter((node) => node.connected)
56
+ .sort((a, b) => {
57
+ const aload = a.stats.memory?.used
58
+ ? a.stats.memory.used
59
+ : 0;
60
+ const bload = b.stats.memory?.used
61
+ ? b.stats.memory.used
62
+ : 0;
63
+ return aload - bload;
64
+ });
65
+ }
66
+ /** Returns the least system load Nodes. */
67
+ get leastLoadNodesCpu() {
68
+ return [...this.nodes.values()]
69
+ .filter((node) => node.connected)
70
+ .sort((a, b) => {
71
+ const aload = a.stats.cpu
72
+ ? (a.stats.cpu.systemLoad / a.stats.cpu.cores) * 100
73
+ : 0;
74
+ const bload = b.stats.cpu
75
+ ? (b.stats.cpu.systemLoad / b.stats.cpu.cores) * 100
76
+ : 0;
77
+ return aload - bload;
78
+ });
22
79
  }
23
80
  deleteNode(node) {
24
81
  const decodeNode = typeof node === "string" ? this.nodes.get(node) : node || this.leastUsedNodes[0];
@@ -63,7 +63,9 @@ export class Player {
63
63
  this.guildId = this.options.guildId;
64
64
  this.voiceChannelId = this.options.voiceChannelId;
65
65
  this.textChannelId = this.options.textChannelId || null;
66
- this.node = this.LavalinkManager.nodeManager.leastUsedNodes.filter(v => options.vcRegion ? v.options?.regions?.includes(options.vcRegion) : true)[0] || this.LavalinkManager.nodeManager.leastUsedNodes[0] || null;
66
+ this.node = typeof this.options.node === "string" ? this.LavalinkManager.nodeManager.nodes.get(this.options.node) : this.options.node;
67
+ if (!this.node || typeof this.node?.request !== "function")
68
+ this.node = this.LavalinkManager.nodeManager.leastUsedNodes.filter(v => options.vcRegion ? v.options?.regions?.includes(options.vcRegion) : true)[0] || this.LavalinkManager.nodeManager.leastUsedNodes[0] || null;
67
69
  if (!this.node)
68
70
  throw new Error("No available Node was found, please add a LavalinkNode to the Manager via Manager.NodeManager#createNode");
69
71
  if (this.LavalinkManager.options.playerOptions.volumeDecrementer)
@@ -71,7 +73,7 @@ export class Player {
71
73
  this.LavalinkManager.emit("playerCreate", this);
72
74
  if (typeof options.volume === "number" && !isNaN(options.volume))
73
75
  this.setVolume(options.volume);
74
- this.queue = new Queue(this.guildId, {}, new QueueSaver(this.LavalinkManager.options.queueStore, this.LavalinkManager.options.queueOptions), this.LavalinkManager.options.queueChangesWatcher);
76
+ this.queue = new Queue(this.guildId, {}, new QueueSaver(this.LavalinkManager.options.queueOptions), this.LavalinkManager.options.queueOptions);
75
77
  }
76
78
  /**
77
79
  * Set custom data.
@@ -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
- // request the data
204
- const res = await this.node.request(`/loadtracks?identifier=${!/^https?:\/\//.test(Query.query) ? `${Query.source}:` : ""}${encodeURIComponent(Query.query)}`);
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(storeManager: StoreManager, options: QueueSaverOptions);
31
+ constructor(options: QueueSaverOptions);
30
32
  get(guildId: string): Promise<Partial<StoredQueue>>;
31
33
  delete(guildId: string): Promise<any>;
32
34
  set(guildId: string, value: any): Promise<any>;
@@ -59,7 +61,7 @@ export declare class Queue {
59
61
  static readonly StaticSymbol: Symbol;
60
62
  private managerUtils;
61
63
  private queueChanges;
62
- constructor(guildId: string, data?: Partial<StoredQueue>, QueueSaver?: QueueSaver, queueChangesWatcher?: QueueChangesWatcher);
64
+ constructor(guildId: string, data?: Partial<StoredQueue>, QueueSaver?: QueueSaver, queueOptions?: QueueSaverOptions);
63
65
  /**
64
66
  * Utils for a Queue
65
67
  */
@@ -1,8 +1,10 @@
1
1
  import { ManagerUitls, QueueSymbol } from "./Utils";
2
2
  export class QueueSaver {
3
- constructor(storeManager, options) {
4
- this._ = storeManager;
5
- this.options = options;
3
+ constructor(options) {
4
+ this._ = options.queueStore || new DefaultQueueStore();
5
+ this.options = {
6
+ maxPreviousTracks: options.maxPreviousTracks
7
+ };
6
8
  }
7
9
  async get(guildId) {
8
10
  return await this._.parse(await this._.get(guildId));
@@ -60,15 +62,14 @@ export class Queue {
60
62
  static StaticSymbol = QueueSymbol;
61
63
  managerUtils = new ManagerUitls();
62
64
  queueChanges;
63
- constructor(guildId, data = {}, QueueSaver, queueChangesWatcher) {
64
- this.queueChanges = queueChangesWatcher || null;
65
+ constructor(guildId, data = {}, QueueSaver, queueOptions) {
66
+ this.queueChanges = queueOptions.queueChangesWatcher || null;
65
67
  this.guildId = guildId;
66
68
  this.QueueSaver = QueueSaver;
67
69
  this.options.maxPreviousTracks = this.QueueSaver?.options?.maxPreviousTracks ?? this.options.maxPreviousTracks;
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: 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).
@@ -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 {Track|any} data the Track to validate
44
- * @returns {boolean}
91
+ * @param data the Track to validate
92
+ * @returns
45
93
  */
46
94
  isTrack(data) {
47
95
  return typeof data?.encoded === "string" && typeof data?.info === "object";
@@ -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
- clientBasedUpdateInterval?: number;
20
+ clientBasedPositionUpdateInterval?: number;
21
21
  defaultSearchPlatform?: SearchPlatform;
22
22
  applyVolumeAsFilter?: boolean;
23
23
  }