lavalink-client 2.5.7 → 2.5.8

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 (98) hide show
  1. package/README.md +9 -2
  2. package/dist/index.d.mts +3036 -0
  3. package/dist/index.d.ts +3036 -0
  4. package/dist/index.js +4965 -0
  5. package/dist/index.mjs +4904 -0
  6. package/package.json +21 -24
  7. package/dist/cjs/index.d.ts +0 -16
  8. package/dist/cjs/index.js +0 -19
  9. package/dist/cjs/package.json +0 -3
  10. package/dist/cjs/structures/Constants.d.ts +0 -90
  11. package/dist/cjs/structures/Constants.js +0 -296
  12. package/dist/cjs/structures/CustomSearches/BandCampSearch.d.ts +0 -3
  13. package/dist/cjs/structures/CustomSearches/BandCampSearch.js +0 -39
  14. package/dist/cjs/structures/Filters.d.ts +0 -169
  15. package/dist/cjs/structures/Filters.js +0 -700
  16. package/dist/cjs/structures/LavalinkManager.d.ts +0 -232
  17. package/dist/cjs/structures/LavalinkManager.js +0 -621
  18. package/dist/cjs/structures/LavalinkManagerStatics.d.ts +0 -15
  19. package/dist/cjs/structures/LavalinkManagerStatics.js +0 -149
  20. package/dist/cjs/structures/Node.d.ts +0 -523
  21. package/dist/cjs/structures/Node.js +0 -1605
  22. package/dist/cjs/structures/NodeManager.d.ts +0 -100
  23. package/dist/cjs/structures/NodeManager.js +0 -224
  24. package/dist/cjs/structures/Player.d.ts +0 -223
  25. package/dist/cjs/structures/Player.js +0 -807
  26. package/dist/cjs/structures/Queue.d.ts +0 -186
  27. package/dist/cjs/structures/Queue.js +0 -390
  28. package/dist/cjs/structures/Types/Filters.d.ts +0 -190
  29. package/dist/cjs/structures/Types/Filters.js +0 -2
  30. package/dist/cjs/structures/Types/Manager.d.ts +0 -271
  31. package/dist/cjs/structures/Types/Manager.js +0 -2
  32. package/dist/cjs/structures/Types/Node.d.ts +0 -238
  33. package/dist/cjs/structures/Types/Node.js +0 -2
  34. package/dist/cjs/structures/Types/Player.d.ts +0 -114
  35. package/dist/cjs/structures/Types/Player.js +0 -2
  36. package/dist/cjs/structures/Types/Queue.d.ts +0 -35
  37. package/dist/cjs/structures/Types/Queue.js +0 -2
  38. package/dist/cjs/structures/Types/Track.d.ts +0 -134
  39. package/dist/cjs/structures/Types/Track.js +0 -2
  40. package/dist/cjs/structures/Types/Utils.d.ts +0 -443
  41. package/dist/cjs/structures/Types/Utils.js +0 -2
  42. package/dist/cjs/structures/Utils.d.ts +0 -116
  43. package/dist/cjs/structures/Utils.js +0 -567
  44. package/dist/esm/index.d.ts +0 -16
  45. package/dist/esm/index.js +0 -16
  46. package/dist/esm/package.json +0 -3
  47. package/dist/esm/structures/Constants.d.ts +0 -90
  48. package/dist/esm/structures/Constants.js +0 -293
  49. package/dist/esm/structures/CustomSearches/BandCampSearch.d.ts +0 -3
  50. package/dist/esm/structures/CustomSearches/BandCampSearch.js +0 -35
  51. package/dist/esm/structures/Filters.d.ts +0 -169
  52. package/dist/esm/structures/Filters.js +0 -696
  53. package/dist/esm/structures/LavalinkManager.d.ts +0 -232
  54. package/dist/esm/structures/LavalinkManager.js +0 -617
  55. package/dist/esm/structures/LavalinkManagerStatics.d.ts +0 -15
  56. package/dist/esm/structures/LavalinkManagerStatics.js +0 -146
  57. package/dist/esm/structures/Node.d.ts +0 -523
  58. package/dist/esm/structures/Node.js +0 -1600
  59. package/dist/esm/structures/NodeManager.d.ts +0 -100
  60. package/dist/esm/structures/NodeManager.js +0 -220
  61. package/dist/esm/structures/Player.d.ts +0 -223
  62. package/dist/esm/structures/Player.js +0 -803
  63. package/dist/esm/structures/Queue.d.ts +0 -186
  64. package/dist/esm/structures/Queue.js +0 -384
  65. package/dist/esm/structures/Types/Filters.d.ts +0 -190
  66. package/dist/esm/structures/Types/Filters.js +0 -1
  67. package/dist/esm/structures/Types/Manager.d.ts +0 -271
  68. package/dist/esm/structures/Types/Manager.js +0 -1
  69. package/dist/esm/structures/Types/Node.d.ts +0 -238
  70. package/dist/esm/structures/Types/Node.js +0 -1
  71. package/dist/esm/structures/Types/Player.d.ts +0 -114
  72. package/dist/esm/structures/Types/Player.js +0 -1
  73. package/dist/esm/structures/Types/Queue.d.ts +0 -35
  74. package/dist/esm/structures/Types/Queue.js +0 -1
  75. package/dist/esm/structures/Types/Track.d.ts +0 -134
  76. package/dist/esm/structures/Types/Track.js +0 -1
  77. package/dist/esm/structures/Types/Utils.d.ts +0 -443
  78. package/dist/esm/structures/Types/Utils.js +0 -1
  79. package/dist/esm/structures/Utils.d.ts +0 -116
  80. package/dist/esm/structures/Utils.js +0 -559
  81. package/dist/types/index.d.ts +0 -16
  82. package/dist/types/structures/Constants.d.ts +0 -90
  83. package/dist/types/structures/CustomSearches/BandCampSearch.d.ts +0 -3
  84. package/dist/types/structures/Filters.d.ts +0 -169
  85. package/dist/types/structures/LavalinkManager.d.ts +0 -232
  86. package/dist/types/structures/LavalinkManagerStatics.d.ts +0 -15
  87. package/dist/types/structures/Node.d.ts +0 -523
  88. package/dist/types/structures/NodeManager.d.ts +0 -100
  89. package/dist/types/structures/Player.d.ts +0 -223
  90. package/dist/types/structures/Queue.d.ts +0 -186
  91. package/dist/types/structures/Types/Filters.d.ts +0 -190
  92. package/dist/types/structures/Types/Manager.d.ts +0 -271
  93. package/dist/types/structures/Types/Node.d.ts +0 -238
  94. package/dist/types/structures/Types/Player.d.ts +0 -114
  95. package/dist/types/structures/Types/Queue.d.ts +0 -35
  96. package/dist/types/structures/Types/Track.d.ts +0 -134
  97. package/dist/types/structures/Types/Utils.d.ts +0 -443
  98. package/dist/types/structures/Utils.d.ts +0 -116
@@ -1,1600 +0,0 @@
1
- import { isAbsolute } from "path";
2
- import WebSocket from "ws";
3
- import { DebugEvents, DestroyReasons, validSponsorBlocks } from "./Constants.js";
4
- import { NodeSymbol, queueTrackEnd, safeStringify } from "./Utils.js";
5
- /**
6
- * Lavalink Node creator class
7
- */
8
- export class LavalinkNode {
9
- heartBeatPingTimestamp = 0;
10
- heartBeatPongTimestamp = 0;
11
- get heartBeatPing() {
12
- return this.heartBeatPongTimestamp - this.heartBeatPingTimestamp;
13
- }
14
- heartBeatInterval;
15
- pingTimeout;
16
- isAlive = false;
17
- /** The provided Options of the Node */
18
- options;
19
- /** The amount of rest calls the node has made. */
20
- calls = 0;
21
- /** Stats from lavalink, will be updated via an interval by lavalink. */
22
- stats = {
23
- players: 0,
24
- playingPlayers: 0,
25
- cpu: {
26
- cores: 0,
27
- lavalinkLoad: 0,
28
- systemLoad: 0
29
- },
30
- memory: {
31
- allocated: 0,
32
- free: 0,
33
- reservable: 0,
34
- used: 0,
35
- },
36
- uptime: 0,
37
- frameStats: {
38
- deficit: 0,
39
- nulled: 0,
40
- sent: 0,
41
- }
42
- };
43
- /** The current sessionId, only present when connected */
44
- sessionId = null;
45
- /** Wether the node resuming is enabled or not */
46
- resuming = { enabled: true, timeout: null };
47
- /** Actual Lavalink Information of the Node */
48
- info = null;
49
- /** The Node Manager of this Node */
50
- NodeManager = null;
51
- /** The Reconnection Timeout */
52
- reconnectTimeout = undefined;
53
- /** The Reconnection Attempt counter */
54
- reconnectAttempts = 1;
55
- /** The Socket of the Lavalink */
56
- socket = null;
57
- /** Version of what the Lavalink Server should be */
58
- version = "v4";
59
- /**
60
- * Create a new Node
61
- * @param options Lavalink Node Options
62
- * @param manager Node Manager
63
- *
64
- *
65
- * @example
66
- * ```ts
67
- * // don't create a node manually, instead use:
68
- *
69
- * client.lavalink.nodeManager.createNode(options)
70
- * ```
71
- */
72
- constructor(options, manager) {
73
- this.options = {
74
- secure: false,
75
- retryAmount: 5,
76
- retryDelay: 10e3,
77
- requestSignalTimeoutMS: 10000,
78
- heartBeatInterval: 30_000,
79
- closeOnError: true,
80
- enablePingOnStatsCheck: true,
81
- ...options
82
- };
83
- this.NodeManager = manager;
84
- this.validate();
85
- if (this.options.secure && this.options.port !== 443)
86
- throw new SyntaxError("If secure is true, then the port must be 443");
87
- this.options.regions = (this.options.regions || []).map(a => a.toLowerCase());
88
- Object.defineProperty(this, NodeSymbol, { configurable: true, value: true });
89
- }
90
- /**
91
- * Raw Request util function
92
- * @param endpoint endpoint string
93
- * @param modify modify the request
94
- * @param extraQueryUrlParams UrlSearchParams to use in a encodedURI, useful for example for flowertts
95
- * @returns object containing request and option information
96
- *
97
- * @example
98
- * ```ts
99
- * player.node.rawRequest(`/loadtracks?identifier=Never gonna give you up`, (options) => options.method = "GET");
100
- * ```
101
- */
102
- async rawRequest(endpoint, modify) {
103
- const options = {
104
- path: `/${this.version}/${endpoint.startsWith("/") ? endpoint.slice(1) : endpoint}`,
105
- method: "GET",
106
- headers: {
107
- "Authorization": this.options.authorization
108
- },
109
- signal: this.options.requestSignalTimeoutMS && this.options.requestSignalTimeoutMS > 0 ? AbortSignal.timeout(this.options.requestSignalTimeoutMS) : undefined,
110
- };
111
- modify?.(options);
112
- const url = new URL(`${this.restAddress}${options.path}`);
113
- url.searchParams.append("trace", "true");
114
- if (options.extraQueryUrlParams && options.extraQueryUrlParams?.size > 0) {
115
- for (const [paramKey, paramValue] of options.extraQueryUrlParams.entries()) {
116
- url.searchParams.append(paramKey, paramValue);
117
- }
118
- }
119
- const urlToUse = url.toString();
120
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
121
- const { path, extraQueryUrlParams, ...fetchOptions } = options; // destructure fetch only options
122
- const response = await fetch(urlToUse, fetchOptions);
123
- this.calls++;
124
- return { response, options: options };
125
- }
126
- async request(endpoint, modify, parseAsText) {
127
- if (!this.connected)
128
- throw new Error("The node is not connected to the Lavalink Server!, Please call node.connect() first!");
129
- const { response, options } = await this.rawRequest(endpoint, modify);
130
- if (["DELETE", "PUT"].includes(options.method))
131
- return;
132
- if (response.status === 204)
133
- return; // no content
134
- if (response.status === 404)
135
- throw new Error(`Node Request resulted into an error, request-PATH: ${options.path} | headers: ${safeStringify(response.headers)}`);
136
- return parseAsText ? await response.text() : await response.json();
137
- }
138
- /**
139
- * Search something raw on the node, please note only add tracks to players of that node
140
- * @param query SearchQuery Object
141
- * @param requestUser Request User for creating the player(s)
142
- * @param throwOnEmpty Wether to throw on an empty result or not
143
- * @returns Searchresult
144
- *
145
- * @example
146
- * ```ts
147
- * // use player.search() instead
148
- * player.node.search({ query: "Never gonna give you up by Rick Astley", source: "soundcloud" }, interaction.user);
149
- * player.node.search({ query: "https://deezer.com/track/123456789" }, interaction.user);
150
- * ```
151
- */
152
- async search(query, requestUser, throwOnEmpty = false) {
153
- const Query = this.NodeManager.LavalinkManager.utils.transformQuery(query);
154
- this.NodeManager.LavalinkManager.utils.validateQueryString(this, Query.query, Query.source);
155
- if (Query.source)
156
- this.NodeManager.LavalinkManager.utils.validateSourceString(this, Query.source);
157
- if (["bcsearch", "bandcamp"].includes(Query.source) && !this.info.sourceManagers.includes("bandcamp")) {
158
- throw new Error("Bandcamp Search only works on the player (lavaplayer version < 2.2.0!");
159
- }
160
- const requestUrl = new URL(`${this.restAddress}/loadtracks`);
161
- if (/^https?:\/\//.test(Query.query) || ["http", "https", "link", "uri"].includes(Query.source)) { // if it's a link simply encode it
162
- requestUrl.searchParams.append("identifier", Query.query);
163
- }
164
- else { // if not make a query out of it
165
- const fttsPrefix = Query.source === "ftts" ? "//" : "";
166
- const prefix = Query.source !== "local" ? `${Query.source}:${fttsPrefix}` : "";
167
- requestUrl.searchParams.append("identifier", `${prefix}${Query.query}`);
168
- }
169
- const requestPathAndSearch = requestUrl.pathname + requestUrl.search;
170
- const res = await this.request(requestPathAndSearch, (options) => {
171
- if (typeof query === "object" && typeof query.extraQueryUrlParams?.size === "number" && query.extraQueryUrlParams?.size > 0) {
172
- options.extraQueryUrlParams = query.extraQueryUrlParams;
173
- }
174
- });
175
- // transform the data which can be Error, Track or Track[] to enfore [Track]
176
- const resTracks = res.loadType === "playlist" ? res.data?.tracks : res.loadType === "track" ? [res.data] : res.loadType === "search" ? Array.isArray(res.data) ? res.data : [res.data] : [];
177
- if (throwOnEmpty === true && (res.loadType === "empty" || !resTracks.length)) {
178
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
179
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.SearchNothingFound, {
180
- state: "warn",
181
- message: `Search found nothing for Request: "${Query.source ? `${Query.source}:` : ""}${Query.query}"`,
182
- functionLayer: "(LavalinkNode > node | player) > search()",
183
- });
184
- }
185
- throw new Error("Nothing found");
186
- }
187
- return {
188
- loadType: res.loadType,
189
- exception: res.loadType === "error" ? res.data : null,
190
- pluginInfo: res.pluginInfo || {},
191
- playlist: res.loadType === "playlist" ? {
192
- name: res.data.info?.name || res.data.pluginInfo?.name || null,
193
- title: res.data.info?.name || res.data.pluginInfo?.name || null,
194
- author: res.data.info?.author || res.data.pluginInfo?.author || null,
195
- thumbnail: (res.data.info?.artworkUrl) || (res.data.pluginInfo?.artworkUrl) || ((typeof res.data?.info?.selectedTrack !== "number" || res.data?.info?.selectedTrack === -1) ? null : resTracks[res.data?.info?.selectedTrack] ? (resTracks[res.data?.info?.selectedTrack]?.info?.artworkUrl || resTracks[res.data?.info?.selectedTrack]?.info?.pluginInfo?.artworkUrl) : null) || null,
196
- uri: res.data.info?.url || res.data.info?.uri || res.data.info?.link || res.data.pluginInfo?.url || res.data.pluginInfo?.uri || res.data.pluginInfo?.link || null,
197
- selectedTrack: typeof res.data?.info?.selectedTrack !== "number" || res.data?.info?.selectedTrack === -1 ? null : resTracks[res.data?.info?.selectedTrack] ? this.NodeManager.LavalinkManager.utils.buildTrack(resTracks[res.data?.info?.selectedTrack], requestUser) : null,
198
- duration: resTracks.length ? resTracks.reduce((acc, cur) => acc + (cur?.info?.duration || cur?.info?.length || 0), 0) : 0,
199
- } : null,
200
- tracks: (resTracks.length ? resTracks.map(t => this.NodeManager.LavalinkManager.utils.buildTrack(t, requestUser)) : [])
201
- };
202
- }
203
- /**
204
- * Search something using the lavaSearchPlugin (filtered searches by types)
205
- * @param query LavaSearchQuery Object
206
- * @param requestUser Request User for creating the player(s)
207
- * @param throwOnEmpty Wether to throw on an empty result or not
208
- * @returns LavaSearchresult (SearchResult if link is provided)
209
- *
210
- * @example
211
- * ```ts
212
- * // use player.search() instead
213
- * player.node.lavaSearch({ types: ["playlist", "album"], query: "Rick Astley", source: "spotify" }, interaction.user);
214
- * ```
215
- */
216
- async lavaSearch(query, requestUser, throwOnEmpty = false) {
217
- const Query = this.NodeManager.LavalinkManager.utils.transformLavaSearchQuery(query);
218
- if (Query.source)
219
- this.NodeManager.LavalinkManager.utils.validateSourceString(this, Query.source);
220
- if (/^https?:\/\//.test(Query.query))
221
- return this.search({ query: Query.query, source: Query.source }, requestUser);
222
- if (!["spsearch", "sprec", "amsearch", "dzsearch", "dzisrc", "ytmsearch", "ytsearch"].includes(Query.source))
223
- throw new SyntaxError(`Query.source must be a source from LavaSrc: "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "ytmsearch" | "ytsearch"`);
224
- if (!this.info.plugins.find(v => v.name === "lavasearch-plugin"))
225
- throw new RangeError(`there is no lavasearch-plugin available in the lavalink node: ${this.id}`);
226
- if (!this.info.plugins.find(v => v.name === "lavasrc-plugin"))
227
- throw new RangeError(`there is no lavasrc-plugin available in the lavalink node: ${this.id}`);
228
- const { response } = await this.rawRequest(`/loadsearch?query=${Query.source ? `${Query.source}:` : ""}${encodeURIComponent(Query.query)}${Query.types?.length ? `&types=${Query.types.join(",")}` : ""}`);
229
- const res = (response.status === 204 ? {} : await response.json());
230
- if (throwOnEmpty === true && !Object.entries(res).flat().filter(Boolean).length) {
231
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
232
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.LavaSearchNothingFound, {
233
- state: "warn",
234
- message: `LavaSearch found nothing for Request: "${Query.source ? `${Query.source}:` : ""}${Query.query}"`,
235
- functionLayer: "(LavalinkNode > node | player) > lavaSearch()",
236
- });
237
- }
238
- throw new Error("Nothing found");
239
- }
240
- return {
241
- tracks: res.tracks?.map(v => this.NodeManager.LavalinkManager.utils.buildTrack(v, requestUser)) || [],
242
- albums: res.albums?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.NodeManager.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
243
- artists: res.artists?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.NodeManager.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
244
- playlists: res.playlists?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.NodeManager.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
245
- texts: res.texts?.map(v => ({ text: v.text, pluginInfo: v?.plugin || v.pluginInfo })) || [],
246
- pluginInfo: res.pluginInfo || res?.plugin
247
- };
248
- }
249
- /**
250
- * Update the Player State on the Lavalink Server
251
- * @param data data to send to lavalink and sync locally
252
- * @returns result from lavalink
253
- *
254
- * @example
255
- * ```ts
256
- * // use player.search() instead
257
- * player.node.updatePlayer({ guildId: player.guildId, playerOptions: { paused: true } }); // example to pause it
258
- * ```
259
- */
260
- async updatePlayer(data) {
261
- if (!this.sessionId)
262
- throw new Error("The Lavalink Node is either not ready, or not up to date!");
263
- this.syncPlayerData(data);
264
- const res = await this.request(`/sessions/${this.sessionId}/players/${data.guildId}`, r => {
265
- r.method = "PATCH";
266
- r.headers["Content-Type"] = "application/json";
267
- r.body = safeStringify(data.playerOptions);
268
- if (data.noReplace) {
269
- const url = new URL(`${this.restAddress}${r.path}`);
270
- url.searchParams.append("noReplace", data.noReplace === true && typeof data.noReplace === "boolean" ? "true" : "false");
271
- r.path = url.pathname + url.search;
272
- }
273
- });
274
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
275
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.PlayerUpdateSuccess, {
276
- state: "log",
277
- message: `Player get's updated with following payload :: ${safeStringify(data.playerOptions, 3)}`,
278
- functionLayer: "LavalinkNode > node > updatePlayer()",
279
- });
280
- }
281
- this.syncPlayerData({}, res);
282
- return res;
283
- }
284
- /**
285
- * Destroys the Player on the Lavalink Server
286
- * @param guildId
287
- * @returns request result
288
- *
289
- * @example
290
- * ```ts
291
- * // use player.destroy() instead
292
- * player.node.destroyPlayer(player.guildId);
293
- * ```
294
- */
295
- async destroyPlayer(guildId) {
296
- if (!this.sessionId)
297
- throw new Error("The Lavalink-Node is either not ready, or not up to date!");
298
- return this.request(`/sessions/${this.sessionId}/players/${guildId}`, r => { r.method = "DELETE"; });
299
- }
300
- /**
301
- * Connect to the Lavalink Node
302
- * @param sessionId Provide the Session Id of the previous connection, to resume the node and it's player(s)
303
- * @returns void
304
- *
305
- * @example
306
- * ```ts
307
- * player.node.connect(); // if provided on bootup in managerOptions#nodes, this will be called automatically when doing lavalink.init()
308
- *
309
- * // or connect from a resuming session:
310
- * player.node.connect("sessionId");
311
- * ```
312
- */
313
- connect(sessionId) {
314
- if (this.connected) {
315
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
316
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.TryingConnectWhileConnected, {
317
- state: "warn",
318
- message: `Tryed to connect to node, but it's already connected!`,
319
- functionLayer: "LavalinkNode > node > connect()",
320
- });
321
- }
322
- return;
323
- }
324
- const headers = {
325
- Authorization: this.options.authorization,
326
- "User-Id": this.NodeManager.LavalinkManager.options.client.id,
327
- "Client-Name": this.NodeManager.LavalinkManager.options.client.username || "Lavalink-Client",
328
- };
329
- if (typeof this.options.sessionId === "string" || typeof sessionId === "string") {
330
- headers["Session-Id"] = this.options.sessionId || sessionId;
331
- this.sessionId = this.options.sessionId || sessionId;
332
- }
333
- this.socket = new WebSocket(`ws${this.options.secure ? "s" : ""}://${this.options.host}:${this.options.port}/v4/websocket`, { headers });
334
- this.socket.on("open", this.open.bind(this));
335
- this.socket.on("close", (code, reason) => this.close(code, reason?.toString()));
336
- this.socket.on("message", this.message.bind(this));
337
- this.socket.on("error", this.error.bind(this));
338
- // this.socket.on("ping", () => this.heartBeat("ping")); // lavalink doesn'T send ping periodically, therefore we use the stats message
339
- }
340
- heartBeat() {
341
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
342
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.HeartBeatTriggered, {
343
- state: "log",
344
- message: `Node Socket Heartbeat triggered, resetting old Timeout to 65000ms (should happen every 60s due to /stats event)`,
345
- functionLayer: "LavalinkNode > nodeEvent > stats > heartBeat()",
346
- });
347
- }
348
- if (this.pingTimeout)
349
- clearTimeout(this.pingTimeout);
350
- this.pingTimeout = setTimeout(() => {
351
- this.pingTimeout = null;
352
- if (!this.socket) {
353
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
354
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.NoSocketOnDestroy, {
355
- state: "error",
356
- message: `Heartbeat registered a disconnect, but socket didn't exist therefore can't terminate`,
357
- functionLayer: "LavalinkNode > nodeEvent > stats > heartBeat() > timeoutHit",
358
- });
359
- }
360
- return;
361
- }
362
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
363
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.SocketTerminateHeartBeatTimeout, {
364
- state: "warn",
365
- message: `Heartbeat registered a disconnect, because timeout wasn't resetted in time. Terminating Web-Socket`,
366
- functionLayer: "LavalinkNode > nodeEvent > stats > heartBeat() > timeoutHit",
367
- });
368
- }
369
- this.isAlive = false;
370
- this.socket.terminate();
371
- }, 65_000); // the stats endpoint get's sent every 60s. se wee add a 5s buffer to make sure we don't miss any stats message
372
- }
373
- /**
374
- * Get the id of the node
375
- *
376
- * @example
377
- * ```ts
378
- * const nodeId = player.node.id;
379
- * console.log("node id is: ", nodeId)
380
- * ```
381
- */
382
- get id() {
383
- return this.options.id || `${this.options.host}:${this.options.port}`;
384
- }
385
- /**
386
- * Destroys the Node-Connection (Websocket) and all player's of the node
387
- * @param destroyReason Destroy Reason to use when destroying the players
388
- * @param deleteNode wether to delete the nodte from the nodes list too, if false it will emit a disconnect. @default true
389
- * @param movePlayers whether to movePlayers to different eligible connected node. If false players won't be moved @default false
390
- * @returns void
391
- *
392
- * @example
393
- * Destroys node and its players
394
- * ```ts
395
- * player.node.destroy("custom Player Destroy Reason", true);
396
- * ```
397
- * destroys only the node and moves its players to different connected node.
398
- * ```ts
399
- * player.node.destroy("custom Player Destroy Reason", true, true);
400
- * ```
401
- */
402
- destroy(destroyReason, deleteNode = true, movePlayers = false) {
403
- if (!this.connected)
404
- return;
405
- const players = this.NodeManager.LavalinkManager.players.filter(p => p.node.id === this.id);
406
- if (players.size) {
407
- const enableDebugEvents = this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents;
408
- const handlePlayerOperations = () => {
409
- if (movePlayers) {
410
- const nodeToMove = Array.from(this.NodeManager.leastUsedNodes("playingPlayers"))
411
- .find(n => n.connected && n.options.id !== this.id);
412
- if (nodeToMove) {
413
- return Promise.allSettled(Array.from(players.values()).map(player => player.changeNode(nodeToMove.options.id)
414
- .catch(error => {
415
- if (enableDebugEvents) {
416
- console.error(`Node > destroy() Failed to move player ${player.guildId}: ${error.message}`);
417
- }
418
- return player.destroy(error.message ?? DestroyReasons.PlayerChangeNodeFail)
419
- .catch(destroyError => {
420
- if (enableDebugEvents) {
421
- console.error(`Node > destroy() Failed to destroy player ${player.guildId} after move failure: ${destroyError.message}`);
422
- }
423
- });
424
- })));
425
- }
426
- else {
427
- return Promise.allSettled(Array.from(players.values()).map(player => player.destroy(DestroyReasons.PlayerChangeNodeFailNoEligibleNode)
428
- .catch(error => {
429
- if (enableDebugEvents) {
430
- console.error(`Node > destroy() Failed to destroy player ${player.guildId}: ${error.message}`);
431
- }
432
- })));
433
- }
434
- }
435
- else {
436
- return Promise.allSettled(Array.from(players.values()).map(player => player.destroy(destroyReason || DestroyReasons.NodeDestroy)
437
- .catch(error => {
438
- if (enableDebugEvents) {
439
- console.error(`Node > destroy() Failed to destroy player ${player.guildId}: ${error.message}`);
440
- }
441
- })));
442
- }
443
- };
444
- // Handle all player operations first, then clean up the socket
445
- handlePlayerOperations().finally(() => {
446
- this.socket.close(1000, "Node-Destroy");
447
- this.socket.removeAllListeners();
448
- this.socket = null;
449
- this.reconnectAttempts = 1;
450
- clearTimeout(this.reconnectTimeout);
451
- if (deleteNode) {
452
- this.NodeManager.emit("destroy", this, destroyReason);
453
- this.NodeManager.nodes.delete(this.id);
454
- clearInterval(this.heartBeatInterval);
455
- clearTimeout(this.pingTimeout);
456
- }
457
- else {
458
- this.NodeManager.emit("disconnect", this, { code: 1000, reason: destroyReason });
459
- }
460
- });
461
- }
462
- else { // If no players, proceed with socket cleanup immediately
463
- this.socket.close(1000, "Node-Destroy");
464
- this.socket.removeAllListeners();
465
- this.socket = null;
466
- this.reconnectAttempts = 1;
467
- clearTimeout(this.reconnectTimeout);
468
- if (deleteNode) {
469
- this.NodeManager.emit("destroy", this, destroyReason);
470
- this.NodeManager.nodes.delete(this.id);
471
- clearInterval(this.heartBeatInterval);
472
- clearTimeout(this.pingTimeout);
473
- }
474
- else {
475
- this.NodeManager.emit("disconnect", this, { code: 1000, reason: destroyReason });
476
- }
477
- }
478
- return;
479
- }
480
- /**
481
- * Disconnects the Node-Connection (Websocket)
482
- * @param disconnectReason Disconnect Reason to use when disconnecting Node
483
- * @returns void
484
- *
485
- * Also the node will not get re-connected again.
486
- *
487
- * @example
488
- * ```ts
489
- * player.node.destroy("custom Player Destroy Reason", true);
490
- * ```
491
- */
492
- disconnect(disconnectReason) {
493
- if (!this.connected)
494
- return;
495
- this.socket.close(1000, "Node-Disconnect");
496
- this.socket.removeAllListeners();
497
- this.socket = null;
498
- this.reconnectAttempts = 1;
499
- clearTimeout(this.reconnectTimeout);
500
- this.NodeManager.emit("disconnect", this, { code: 1000, reason: disconnectReason });
501
- }
502
- /**
503
- * Returns if connected to the Node.
504
- *
505
- * @example
506
- * ```ts
507
- * const isConnected = player.node.connected;
508
- * console.log("node is connected: ", isConnected ? "yes" : "no")
509
- * ```
510
- */
511
- get connected() {
512
- return this.socket && this.socket.readyState === WebSocket.OPEN;
513
- }
514
- /**
515
- * Returns the current ConnectionStatus
516
- *
517
- * @example
518
- * ```ts
519
- * try {
520
- * const statusOfConnection = player.node.connectionStatus;
521
- * console.log("node's connection status is:", statusOfConnection)
522
- * } catch (error) {
523
- * console.error("no socket available?", error)
524
- * }
525
- * ```
526
- */
527
- get connectionStatus() {
528
- if (!this.socket)
529
- throw new Error("no websocket was initialized yet");
530
- return ["CONNECTING", "OPEN", "CLOSING", "CLOSED"][this.socket.readyState] || "UNKNOWN";
531
- }
532
- /**
533
- * Gets all Players of a Node
534
- * @returns array of players inside of lavalink
535
- *
536
- * @example
537
- * ```ts
538
- * const node = lavalink.nodes.get("NODEID");
539
- * const playersOfLavalink = await node?.fetchAllPlayers();
540
- * ```
541
- */
542
- async fetchAllPlayers() {
543
- if (!this.sessionId)
544
- throw new Error("The Lavalink-Node is either not ready, or not up to date!");
545
- return this.request(`/sessions/${this.sessionId}/players`) || [];
546
- }
547
- /**
548
- * Gets specific Player Information
549
- * @returns lavalink player object if player exists on lavalink
550
- *
551
- * @example
552
- * ```ts
553
- * const node = lavalink.nodes.get("NODEID");
554
- * const playerInformation = await node?.fetchPlayer("guildId");
555
- * ```
556
- */
557
- async fetchPlayer(guildId) {
558
- if (!this.sessionId)
559
- throw new Error("The Lavalink-Node is either not ready, or not up to date!");
560
- return this.request(`/sessions/${this.sessionId}/players/${guildId}`);
561
- }
562
- /**
563
- * Updates the session with and enables/disables resuming and timeout
564
- * @param resuming Whether resuming is enabled for this session or not
565
- * @param timeout The timeout in seconds (default is 60s)
566
- * @returns the result of the request
567
- *
568
- * @example
569
- * ```ts
570
- * const node = player.node || lavalink.nodes.get("NODEID");
571
- * await node?.updateSession(true, 180e3); // will enable resuming for 180seconds
572
- * ```
573
- */
574
- async updateSession(resuming, timeout) {
575
- if (!this.sessionId)
576
- throw new Error("the Lavalink-Node is either not ready, or not up to date!");
577
- const data = {};
578
- if (typeof resuming === "boolean")
579
- data.resuming = resuming;
580
- if (typeof timeout === "number" && timeout > 0)
581
- data.timeout = timeout;
582
- this.resuming = {
583
- enabled: typeof resuming === "boolean" ? resuming : false,
584
- timeout: typeof resuming === "boolean" && resuming === true ? timeout : null,
585
- };
586
- return this.request(`/sessions/${this.sessionId}`, r => {
587
- r.method = "PATCH";
588
- r.headers = { Authorization: this.options.authorization, 'Content-Type': 'application/json' };
589
- r.body = safeStringify(data);
590
- });
591
- }
592
- /**
593
- * Decode Track or Tracks
594
- */
595
- decode = {
596
- /**
597
- * Decode a single track into its info
598
- * @param encoded valid encoded base64 string from a track
599
- * @param requester the requesteruser for building the track
600
- * @returns decoded track from lavalink
601
- *
602
- * @example
603
- * ```ts
604
- * const encodedBase64 = 'QAACDgMACk5vIERpZ2dpdHkAC0JsYWNrc3RyZWV0AAAAAAAEo4AABjkxNjQ5NgABAB9odHRwczovL2RlZXplci5jb20vdHJhY2svOTE2NDk2AQBpaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvY292ZXIvZGFlN2EyNjViNzlmYjcxMjc4Y2RlMjUwNDg0OWQ2ZjcvMTAwMHgxMDAwLTAwMDAwMC04MC0wLTAuanBnAQAMVVNJUjE5NjAwOTc4AAZkZWV6ZXIBAChObyBEaWdnaXR5OiBUaGUgVmVyeSBCZXN0IE9mIEJsYWNrc3RyZWV0AQAjaHR0cHM6Ly93d3cuZGVlemVyLmNvbS9hbGJ1bS8xMDMyNTQBACJodHRwczovL3d3dy5kZWV6ZXIuY29tL2FydGlzdC8xODYxAQBqaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvYXJ0aXN0L2YxNmNhYzM2ZmVjMzkxZjczN2I3ZDQ4MmY1YWM3M2UzLzEwMDB4MTAwMC0wMDAwMDAtODAtMC0wLmpwZwEAT2h0dHBzOi8vY2RuLXByZXZpZXctYS5kemNkbi5uZXQvc3RyZWFtL2MtYTE1Yjg1NzFhYTYyMDBjMDQ0YmY1OWM3NmVkOTEyN2MtNi5tcDMAAAAAAAAAAAA=';
605
- * const track = await player.node.decode.singleTrack(encodedBase64, interaction.user);
606
- * ```
607
- */
608
- singleTrack: async (encoded, requester) => {
609
- if (!encoded)
610
- throw new SyntaxError("No encoded (Base64 string) was provided");
611
- // return the decoded + builded track
612
- return this.NodeManager.LavalinkManager.utils?.buildTrack(await this.request(`/decodetrack?encodedTrack=${encodeURIComponent(encoded.replace(/\s/g, ""))}`), requester);
613
- },
614
- /**
615
- * Decodes multiple tracks into their info
616
- * @param encodeds valid encoded base64 string array from all tracks
617
- * @param requester the requesteruser for building the tracks
618
- * @returns array of all tracks you decoded
619
- *
620
- * @example
621
- * ```ts
622
- * const encodedBase64_1 = 'QAACDgMACk5vIERpZ2dpdHkAC0JsYWNrc3RyZWV0AAAAAAAEo4AABjkxNjQ5NgABAB9odHRwczovL2RlZXplci5jb20vdHJhY2svOTE2NDk2AQBpaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvY292ZXIvZGFlN2EyNjViNzlmYjcxMjc4Y2RlMjUwNDg0OWQ2ZjcvMTAwMHgxMDAwLTAwMDAwMC04MC0wLTAuanBnAQAMVVNJUjE5NjAwOTc4AAZkZWV6ZXIBAChObyBEaWdnaXR5OiBUaGUgVmVyeSBCZXN0IE9mIEJsYWNrc3RyZWV0AQAjaHR0cHM6Ly93d3cuZGVlemVyLmNvbS9hbGJ1bS8xMDMyNTQBACJodHRwczovL3d3dy5kZWV6ZXIuY29tL2FydGlzdC8xODYxAQBqaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvYXJ0aXN0L2YxNmNhYzM2ZmVjMzkxZjczN2I3ZDQ4MmY1YWM3M2UzLzEwMDB4MTAwMC0wMDAwMDAtODAtMC0wLmpwZwEAT2h0dHBzOi8vY2RuLXByZXZpZXctYS5kemNkbi5uZXQvc3RyZWFtL2MtYTE1Yjg1NzFhYTYyMDBjMDQ0YmY1OWM3NmVkOTEyN2MtNi5tcDMAAAAAAAAAAAA=';
623
- * const encodedBase64_2 = 'QAABJAMAClRhbGsgYSBMb3QACjQwNHZpbmNlbnQAAAAAAAHr1gBxTzpodHRwczovL2FwaS12Mi5zb3VuZGNsb3VkLmNvbS9tZWRpYS9zb3VuZGNsb3VkOnRyYWNrczo4NTE0MjEwNzYvMzUyYTRiOTAtNzYxOS00M2E5LWJiOGItMjIxMzE0YzFjNjNhL3N0cmVhbS9obHMAAQAsaHR0cHM6Ly9zb3VuZGNsb3VkLmNvbS80MDR2aW5jZW50L3RhbGstYS1sb3QBADpodHRwczovL2kxLnNuZGNkbi5jb20vYXJ0d29ya3MtRTN1ek5Gc0Y4QzBXLTAtb3JpZ2luYWwuanBnAQAMUVpITkExOTg1Nzg0AApzb3VuZGNsb3VkAAAAAAAAAAA=';
624
- * const tracks = await player.node.decode.multipleTracks([encodedBase64_1, encodedBase64_2], interaction.user);
625
- * ```
626
- */
627
- multipleTracks: async (encodeds, requester) => {
628
- if (!Array.isArray(encodeds) || !encodeds.every(v => typeof v === "string" && v.length > 1))
629
- throw new SyntaxError("You need to provide encodeds, which is an array of base64 strings");
630
- // return the decoded + builded tracks
631
- return await this.request(`/decodetracks`, r => {
632
- r.method = "POST";
633
- r.body = safeStringify(encodeds);
634
- r.headers["Content-Type"] = "application/json";
635
- }).then((r) => r.map(track => this.NodeManager.LavalinkManager.utils.buildTrack(track, requester)));
636
- }
637
- };
638
- lyrics = {
639
- /**
640
- * Get the lyrics of a track
641
- * @param track the track to get the lyrics for
642
- * @param skipTrackSource wether to skip the track source or not
643
- * @returns the lyrics of the track
644
- * @example
645
- *
646
- * ```ts
647
- * const lyrics = await player.node.lyrics.get(track, true);
648
- * // use it of player instead:
649
- * // const lyrics = await player.getLyrics(track, true);
650
- * ```
651
- */
652
- get: async (track, skipTrackSource = false) => {
653
- if (!this.sessionId)
654
- throw new Error("the Lavalink-Node is either not ready, or not up to date!");
655
- if (!this.info.plugins.find(v => v.name === "lavalyrics-plugin"))
656
- throw new RangeError(`there is no lavalyrics-plugin available in the lavalink node (required for lyrics): ${this.id}`);
657
- if (!this.info.plugins.find(v => v.name === "lavasrc-plugin") &&
658
- !this.info.plugins.find(v => v.name === "java-lyrics-plugin"))
659
- throw new RangeError(`there is no lyrics source (via lavasrc-plugin / java-lyrics-plugin) available in the lavalink node (required for lyrics): ${this.id}`);
660
- const url = `/lyrics?track=${track.encoded}&skipTrackSource=${skipTrackSource}`;
661
- return (await this.request(url));
662
- },
663
- /**
664
- * Get the lyrics of the current playing track
665
- *
666
- * @param guildId the guild id of the player
667
- * @param skipTrackSource wether to skip the track source or not
668
- * @returns the lyrics of the current playing track
669
- * @example
670
- * ```ts
671
- * const lyrics = await player.node.lyrics.getCurrent(guildId);
672
- * // use it of player instead:
673
- * // const lyrics = await player.getCurrentLyrics();
674
- * ```
675
- */
676
- getCurrent: async (guildId, skipTrackSource = false) => {
677
- if (!this.sessionId)
678
- throw new Error("the Lavalink-Node is either not ready, or not up to date!");
679
- if (!this.info.plugins.find(v => v.name === "lavalyrics-plugin"))
680
- throw new RangeError(`there is no lavalyrics-plugin available in the lavalink node (required for lyrics): ${this.id}`);
681
- if (!this.info.plugins.find(v => v.name === "lavasrc-plugin") &&
682
- !this.info.plugins.find(v => v.name === "java-lyrics-plugin"))
683
- throw new RangeError(`there is no lyrics source (via lavasrc-plugin / java-lyrics-plugin) available in the lavalink node (required for lyrics): ${this.id}`);
684
- const url = `/sessions/${this.sessionId}/players/${guildId}/track/lyrics?skipTrackSource=${skipTrackSource}`;
685
- return (await this.request(url));
686
- },
687
- /**
688
- * subscribe to lyrics updates for a guild
689
- * @param guildId the guild id of the player
690
- * @returns request data of the request
691
- *
692
- * @example
693
- * ```ts
694
- * await player.node.lyrics.subscribe(guildId);
695
- * // use it of player instead:
696
- * // const lyrics = await player.subscribeLyrics();
697
- * ```
698
- */
699
- subscribe: async (guildId) => {
700
- if (!this.sessionId)
701
- throw new Error("the Lavalink-Node is either not ready, or not up to date!");
702
- if (!this.info.plugins.find(v => v.name === "lavalyrics-plugin"))
703
- throw new RangeError(`there is no lavalyrics-plugin available in the lavalink node (required for lyrics): ${this.id}`);
704
- if (!this.info.plugins.find(v => v.name === "lavasrc-plugin") &&
705
- !this.info.plugins.find(v => v.name === "java-lyrics-plugin"))
706
- throw new RangeError(`there is no lyrics source (via lavasrc-plugin / java-lyrics-plugin) available in the lavalink node (required for lyrics): ${this.id}`);
707
- return await this.request(`/sessions/${this.sessionId}/players/${guildId}/lyrics/subscribe`, (options) => {
708
- options.method = "POST";
709
- });
710
- },
711
- /**
712
- * unsubscribe from lyrics updates for a guild
713
- * @param guildId the guild id of the player
714
- * @returns request data of the request
715
- *
716
- * @example
717
- * ```ts
718
- * await player.node.lyrics.unsubscribe(guildId);
719
- * // use it of player instead:
720
- * // const lyrics = await player.unsubscribeLyrics();
721
- * ```
722
- */
723
- unsubscribe: async (guildId) => {
724
- if (!this.sessionId)
725
- throw new Error("the Lavalink-Node is either not ready, or not up to date!");
726
- if (!this.info.plugins.find(v => v.name === "lavalyrics-plugin"))
727
- throw new RangeError(`there is no lavalyrics-plugin available in the lavalink node (required for lyrics): ${this.id}`);
728
- if (!this.info.plugins.find(v => v.name === "lavasrc-plugin") &&
729
- !this.info.plugins.find(v => v.name === "java-lyrics-plugin"))
730
- throw new RangeError(`there is no lyrics source (via lavasrc-plugin / java-lyrics-plugin) available in the lavalink node (required for lyrics): ${this.id}`);
731
- return await this.request(`/sessions/${this.sessionId}/players/${guildId}/lyrics/subscribe`, (options) => {
732
- options.method = "DELETE";
733
- });
734
- },
735
- };
736
- /**
737
- * Request Lavalink statistics.
738
- * @returns the lavalink node stats
739
- *
740
- * @example
741
- * ```ts
742
- * const lavalinkStats = await player.node.fetchStats();
743
- * ```
744
- */
745
- async fetchStats() {
746
- return await this.request(`/stats`);
747
- }
748
- /**
749
- * Request Lavalink version.
750
- * @returns the current used lavalink version
751
- *
752
- * @example
753
- * ```ts
754
- * const lavalinkVersion = await player.node.fetchVersion();
755
- * ```
756
- */
757
- async fetchVersion() {
758
- // need to adjust path for no-prefix version info
759
- return await this.request(`/version`, r => { r.path = "/version"; }, true);
760
- }
761
- /**
762
- * Request Lavalink information.
763
- * @returns lavalink info object
764
- *
765
- * @example
766
- * ```ts
767
- * const lavalinkInfo = await player.node.fetchInfo();
768
- * const availablePlugins:string[] = lavalinkInfo.plugins.map(plugin => plugin.name);
769
- * const availableSources:string[] = lavalinkInfo.sourceManagers;
770
- * ```
771
- */
772
- async fetchInfo() {
773
- return await this.request(`/info`);
774
- }
775
- /**
776
- * Lavalink's Route Planner Api
777
- */
778
- routePlannerApi = {
779
- /**
780
- * Get routplanner Info from Lavalink for ip rotation
781
- * @returns the status of the routeplanner
782
- *
783
- * @example
784
- * ```ts
785
- * const routePlannerStatus = await player.node.routePlannerApi.getStatus();
786
- * const usedBlock = routePlannerStatus.details?.ipBlock;
787
- * const currentIp = routePlannerStatus.currentAddress;
788
- * ```
789
- */
790
- getStatus: async () => {
791
- if (!this.sessionId)
792
- throw new Error("the Lavalink-Node is either not ready, or not up to date!");
793
- return await this.request(`/routeplanner/status`);
794
- },
795
- /**
796
- * Release blacklisted IP address into pool of IPs for ip rotation
797
- * @param address IP address
798
- * @returns request data of the request
799
- *
800
- * @example
801
- * ```ts
802
- * await player.node.routePlannerApi.unmarkFailedAddress("ipv6address");
803
- * ```
804
- */
805
- unmarkFailedAddress: async (address) => {
806
- if (!this.sessionId)
807
- throw new Error("the Lavalink-Node is either not ready, or not up to date!");
808
- return await this.request(`/routeplanner/free/address`, r => {
809
- r.method = "POST";
810
- r.headers["Content-Type"] = "application/json";
811
- r.body = safeStringify({ address });
812
- });
813
- },
814
- /**
815
- * Release all blacklisted IP addresses into pool of IPs
816
- * @returns request data of the request
817
- *
818
- * @example
819
- * ```ts
820
- * await player.node.routePlannerApi.unmarkAllFailedAddresses();
821
- * ```
822
- */
823
- unmarkAllFailedAddresses: async () => {
824
- if (!this.sessionId)
825
- throw new Error("the Lavalink-Node is either not ready, or not up to date!");
826
- return await this.request(`/routeplanner/free/all`, r => {
827
- r.method = "POST";
828
- r.headers["Content-Type"] = "application/json";
829
- });
830
- }
831
- };
832
- /** @private Utils for validating the */
833
- validate() {
834
- if (!this.options.authorization)
835
- throw new SyntaxError("LavalinkNode requires 'authorization'");
836
- if (!this.options.host)
837
- throw new SyntaxError("LavalinkNode requires 'host'");
838
- if (!this.options.port)
839
- throw new SyntaxError("LavalinkNode requires 'port'");
840
- // TODO add more validations
841
- }
842
- /**
843
- * Sync the data of the player you make an action to lavalink to
844
- * @param data data to use to update the player
845
- * @param res result data from lavalink, to override, if available
846
- * @returns boolean
847
- */
848
- syncPlayerData(data, res) {
849
- if (typeof data === "object" && typeof data?.guildId === "string" && typeof data.playerOptions === "object" && Object.keys(data.playerOptions).length > 0) {
850
- const player = this.NodeManager.LavalinkManager.getPlayer(data.guildId);
851
- if (!player)
852
- return;
853
- if (typeof data.playerOptions.paused !== "undefined") {
854
- player.paused = data.playerOptions.paused;
855
- player.playing = !data.playerOptions.paused;
856
- }
857
- if (typeof data.playerOptions.position === "number") {
858
- // player.position = data.playerOptions.position;
859
- player.lastPosition = data.playerOptions.position;
860
- player.lastPositionChange = Date.now();
861
- }
862
- if (typeof data.playerOptions.voice !== "undefined")
863
- player.voice = data.playerOptions.voice;
864
- if (typeof data.playerOptions.volume !== "undefined") {
865
- if (this.NodeManager.LavalinkManager.options.playerOptions.volumeDecrementer) {
866
- player.volume = Math.round(data.playerOptions.volume / this.NodeManager.LavalinkManager.options.playerOptions.volumeDecrementer);
867
- player.lavalinkVolume = Math.round(data.playerOptions.volume);
868
- }
869
- else {
870
- player.volume = Math.round(data.playerOptions.volume);
871
- player.lavalinkVolume = Math.round(data.playerOptions.volume);
872
- }
873
- }
874
- if (typeof data.playerOptions.filters !== "undefined") {
875
- const oldFilterTimescale = { ...player.filterManager.data.timescale };
876
- Object.freeze(oldFilterTimescale);
877
- if (data.playerOptions.filters.timescale)
878
- player.filterManager.data.timescale = data.playerOptions.filters.timescale;
879
- if (data.playerOptions.filters.distortion)
880
- player.filterManager.data.distortion = data.playerOptions.filters.distortion;
881
- if (data.playerOptions.filters.pluginFilters)
882
- player.filterManager.data.pluginFilters = data.playerOptions.filters.pluginFilters;
883
- if (data.playerOptions.filters.vibrato)
884
- player.filterManager.data.vibrato = data.playerOptions.filters.vibrato;
885
- if (data.playerOptions.filters.volume)
886
- player.filterManager.data.volume = data.playerOptions.filters.volume;
887
- if (data.playerOptions.filters.equalizer)
888
- player.filterManager.equalizerBands = data.playerOptions.filters.equalizer;
889
- if (data.playerOptions.filters.karaoke)
890
- player.filterManager.data.karaoke = data.playerOptions.filters.karaoke;
891
- if (data.playerOptions.filters.lowPass)
892
- player.filterManager.data.lowPass = data.playerOptions.filters.lowPass;
893
- if (data.playerOptions.filters.rotation)
894
- player.filterManager.data.rotation = data.playerOptions.filters.rotation;
895
- if (data.playerOptions.filters.tremolo)
896
- player.filterManager.data.tremolo = data.playerOptions.filters.tremolo;
897
- player.filterManager.checkFiltersState(oldFilterTimescale);
898
- }
899
- }
900
- // just for res
901
- if (res?.guildId === "string" && typeof res?.voice !== "undefined") {
902
- const player = this.NodeManager.LavalinkManager.getPlayer(data.guildId);
903
- if (!player)
904
- return;
905
- if (typeof res?.voice?.connected === "boolean" && res.voice.connected === false) {
906
- player.destroy(DestroyReasons.LavalinkNoVoice);
907
- return;
908
- }
909
- player.ping.ws = res?.voice?.ping || player?.ping.ws;
910
- }
911
- return;
912
- }
913
- /**
914
- * Get the rest Adress for making requests
915
- */
916
- get restAddress() {
917
- return `http${this.options.secure ? "s" : ""}://${this.options.host}:${this.options.port}`;
918
- }
919
- /**
920
- * Reconnect to the lavalink node
921
- * @param instaReconnect @default false wether to instantly try to reconnect
922
- * @returns void
923
- *
924
- * @example
925
- * ```ts
926
- * await player.node.reconnect();
927
- * ```
928
- */
929
- reconnect(instaReconnect = false) {
930
- this.NodeManager.emit("reconnectinprogress", this);
931
- if (instaReconnect) {
932
- if (this.reconnectAttempts >= this.options.retryAmount) {
933
- const error = new Error(`Unable to connect after ${this.options.retryAmount} attempts.`);
934
- this.NodeManager.emit("error", this, error);
935
- return this.destroy(DestroyReasons.NodeReconnectFail);
936
- }
937
- this.socket.removeAllListeners();
938
- this.socket = null;
939
- this.NodeManager.emit("reconnecting", this);
940
- this.connect();
941
- this.reconnectAttempts++;
942
- return;
943
- }
944
- this.reconnectTimeout = setTimeout(() => {
945
- this.reconnectTimeout = null;
946
- if (this.reconnectAttempts >= this.options.retryAmount) {
947
- const error = new Error(`Unable to connect after ${this.options.retryAmount} attempts.`);
948
- this.NodeManager.emit("error", this, error);
949
- return this.destroy(DestroyReasons.NodeReconnectFail);
950
- }
951
- this.socket.removeAllListeners();
952
- this.socket = null;
953
- this.NodeManager.emit("reconnecting", this);
954
- this.connect();
955
- this.reconnectAttempts++;
956
- }, this.options.retryDelay || 1000);
957
- }
958
- /** @private util function for handling opening events from websocket */
959
- async open() {
960
- this.isAlive = true;
961
- // trigger heartbeat-ping timeout - this is to check wether the client lost connection without knowing it
962
- if (this.options.enablePingOnStatsCheck)
963
- this.heartBeat();
964
- if (this.heartBeatInterval)
965
- clearInterval(this.heartBeatInterval);
966
- if (this.options.heartBeatInterval > 0) {
967
- // everytime a pong happens, set this.isAlive to true
968
- this.socket.on("pong", () => {
969
- this.heartBeatPongTimestamp = performance.now();
970
- this.isAlive = true;
971
- });
972
- // every x ms send a ping to lavalink to retrieve a pong later on
973
- this.heartBeatInterval = setInterval(() => {
974
- if (!this.socket)
975
- return console.error("Node-Heartbeat-Interval - Socket not available - maybe reconnecting?");
976
- if (!this.isAlive)
977
- this.close(500, "Node-Heartbeat-Timeout");
978
- this.isAlive = false;
979
- this.heartBeatPingTimestamp = performance.now();
980
- this.socket.ping();
981
- }, this.options.heartBeatInterval || 30_000);
982
- }
983
- if (this.reconnectTimeout)
984
- clearTimeout(this.reconnectTimeout);
985
- // reset the reconnect attempts amount
986
- this.reconnectAttempts = 1;
987
- this.info = await this.fetchInfo().catch((e) => (console.error(e, "ON-OPEN-FETCH"), null));
988
- if (!this.info && ["v3", "v4"].includes(this.version)) {
989
- const errorString = `Lavalink Node (${this.restAddress}) does not provide any /${this.version}/info`;
990
- throw new Error(errorString);
991
- }
992
- this.NodeManager.emit("connect", this);
993
- }
994
- /** @private util function for handling closing events from websocket */
995
- close(code, reason) {
996
- if (this.pingTimeout)
997
- clearTimeout(this.pingTimeout);
998
- if (this.heartBeatInterval)
999
- clearInterval(this.heartBeatInterval);
1000
- if (code === 1006 && !reason)
1001
- reason = "Socket got terminated due to no ping connection";
1002
- if (code === 1000 && reason === "Node-Disconnect")
1003
- return; // manually disconnected and already emitted the event.
1004
- this.NodeManager.emit("disconnect", this, { code, reason });
1005
- if (code !== 1000 || reason !== "Node-Destroy") {
1006
- if (this.NodeManager.nodes.has(this.id)) { // try to reconnect only when the node is still in the nodeManager.nodes list
1007
- this.reconnect();
1008
- }
1009
- }
1010
- }
1011
- /** @private util function for handling error events from websocket */
1012
- error(error) {
1013
- if (!error)
1014
- return;
1015
- this.NodeManager.emit("error", this, error);
1016
- if (this.options.closeOnError) {
1017
- if (this.heartBeatInterval)
1018
- clearInterval(this.heartBeatInterval);
1019
- if (this.pingTimeout)
1020
- clearTimeout(this.pingTimeout);
1021
- this.socket?.close(500, "Node-Error - Force Reconnect");
1022
- }
1023
- ;
1024
- }
1025
- /** @private util function for handling message events from websocket */
1026
- async message(d) {
1027
- if (Array.isArray(d))
1028
- d = Buffer.concat(d);
1029
- else if (d instanceof ArrayBuffer)
1030
- d = Buffer.from(d);
1031
- let payload;
1032
- try {
1033
- payload = JSON.parse(d.toString());
1034
- }
1035
- catch (e) {
1036
- this.NodeManager.emit("error", this, e);
1037
- return;
1038
- }
1039
- if (!payload.op)
1040
- return;
1041
- this.NodeManager.emit("raw", this, payload);
1042
- switch (payload.op) {
1043
- case "stats":
1044
- if (this.options.enablePingOnStatsCheck)
1045
- this.heartBeat(); // lavalink doesn'T send "ping" periodically, therefore we use the stats message to check for a ping
1046
- delete payload.op;
1047
- this.stats = { ...payload };
1048
- break;
1049
- case "playerUpdate":
1050
- {
1051
- const player = this.NodeManager.LavalinkManager.getPlayer(payload.guildId);
1052
- if (!player) {
1053
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1054
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.PlayerUpdateNoPlayer, {
1055
- state: "error",
1056
- message: `PlayerUpdate Event Triggered, but no player found of payload.guildId: ${payload.guildId}`,
1057
- functionLayer: "LavalinkNode > nodeEvent > playerUpdate",
1058
- });
1059
- }
1060
- return;
1061
- }
1062
- const oldPlayer = player?.toJSON();
1063
- player.lastPositionChange = Date.now();
1064
- player.lastPosition = payload.state.position || 0;
1065
- player.connected = payload.state.connected;
1066
- player.ping.ws = payload.state.ping >= 0 ? payload.state.ping : player.ping.ws <= 0 && player.connected ? null : player.ping.ws || 0;
1067
- if (!player.createdTimeStamp && payload.state.time)
1068
- player.createdTimeStamp = payload.state.time;
1069
- if (player.filterManager.filterUpdatedState === true && ((player.queue.current?.info?.duration || 0) <= (player.LavalinkManager.options.advancedOptions.maxFilterFixDuration || 600_000) || (player.queue.current?.info?.uri && isAbsolute(player.queue.current?.info?.uri)))) {
1070
- player.filterManager.filterUpdatedState = false;
1071
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1072
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.PlayerUpdateFilterFixApply, {
1073
- state: "log",
1074
- message: `Fixing FilterState on "${player.guildId}" because player.options.instaUpdateFiltersFix === true`,
1075
- functionLayer: "LavalinkNode > nodeEvent > playerUpdate",
1076
- });
1077
- }
1078
- await player.seek(player.position);
1079
- }
1080
- this.NodeManager.LavalinkManager.emit("playerUpdate", oldPlayer, player);
1081
- }
1082
- break;
1083
- case "event":
1084
- this.handleEvent(payload);
1085
- break;
1086
- case "ready": // payload: { resumed: false, sessionId: 'ytva350aevn6n9n8', op: 'ready' }
1087
- this.sessionId = payload.sessionId;
1088
- this.resuming.enabled = payload.resumed;
1089
- if (payload.resumed === true) {
1090
- try {
1091
- this.NodeManager.emit("resumed", this, payload, await this.fetchAllPlayers());
1092
- }
1093
- catch (e) {
1094
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1095
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.ResumingFetchingError, {
1096
- state: "error",
1097
- message: `Failed to fetch players for resumed event, falling back without players array`,
1098
- error: e,
1099
- functionLayer: "LavalinkNode > nodeEvent > resumed",
1100
- });
1101
- }
1102
- this.NodeManager.emit("resumed", this, payload, []);
1103
- }
1104
- }
1105
- break;
1106
- default:
1107
- this.NodeManager.emit("error", this, new Error(`Unexpected op "${payload.op}" with data`), payload);
1108
- return;
1109
- }
1110
- }
1111
- /** @private middleware util function for handling all kind of events from websocket */
1112
- async handleEvent(payload) {
1113
- if (!payload?.guildId)
1114
- return;
1115
- const player = this.NodeManager.LavalinkManager.getPlayer(payload.guildId);
1116
- if (!player)
1117
- return;
1118
- switch (payload.type) {
1119
- case "TrackStartEvent":
1120
- this.trackStart(player, player.queue.current, payload);
1121
- break;
1122
- case "TrackEndEvent":
1123
- this.trackEnd(player, player.queue.current, payload);
1124
- break;
1125
- case "TrackStuckEvent":
1126
- this.trackStuck(player, player.queue.current, payload);
1127
- break;
1128
- case "TrackExceptionEvent":
1129
- this.trackError(player, player.queue.current, payload);
1130
- break;
1131
- case "WebSocketClosedEvent":
1132
- this.socketClosed(player, payload);
1133
- break;
1134
- case "SegmentsLoaded":
1135
- this.SponsorBlockSegmentLoaded(player, player.queue.current, payload);
1136
- break;
1137
- case "SegmentSkipped":
1138
- this.SponsorBlockSegmentSkipped(player, player.queue.current, payload);
1139
- break;
1140
- case "ChaptersLoaded":
1141
- this.SponsorBlockChaptersLoaded(player, player.queue.current, payload);
1142
- break;
1143
- case "ChapterStarted":
1144
- this.SponsorBlockChapterStarted(player, player.queue.current, payload);
1145
- break;
1146
- case "LyricsLineEvent":
1147
- this.LyricsLine(player, player.queue.current, payload);
1148
- break;
1149
- case "LyricsFoundEvent":
1150
- this.LyricsFound(player, player.queue.current, payload);
1151
- break;
1152
- case "LyricsNotFoundEvent":
1153
- this.LyricsNotFound(player, player.queue.current, payload);
1154
- break;
1155
- default:
1156
- this.NodeManager.emit("error", this, new Error(`Node#event unknown event '${payload.type}'.`), payload);
1157
- break;
1158
- }
1159
- return;
1160
- }
1161
- getTrackOfPayload(payload) {
1162
- return "track" in payload
1163
- ? this.NodeManager.LavalinkManager.utils.buildTrack(payload.track, undefined)
1164
- : null;
1165
- }
1166
- /** @private util function for handling trackStart event */
1167
- async trackStart(player, track, payload) {
1168
- player.playing = true;
1169
- player.paused = false;
1170
- // don't emit the event if previous track == new track aka track loop
1171
- if (this.NodeManager.LavalinkManager.options?.emitNewSongsOnly === true && player.queue.previous[0]?.info?.identifier === track?.info?.identifier) {
1172
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1173
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.TrackStartNewSongsOnly, {
1174
- state: "log",
1175
- message: `TrackStart not Emitting, because playing the previous song again.`,
1176
- functionLayer: "LavalinkNode > trackStart()",
1177
- });
1178
- }
1179
- return;
1180
- }
1181
- if (!player.queue.current) {
1182
- player.queue.current = this.getTrackOfPayload(payload);
1183
- if (player.queue.current) {
1184
- await player.queue.utils.save();
1185
- }
1186
- else {
1187
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1188
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.TrackStartNoTrack, {
1189
- state: "warn",
1190
- message: `Trackstart emitted but there is no track on player.queue.current, trying to get the track of the payload failed too.`,
1191
- functionLayer: "LavalinkNode > trackStart()",
1192
- });
1193
- }
1194
- }
1195
- }
1196
- this.NodeManager.LavalinkManager.emit("trackStart", player, player.queue.current, payload);
1197
- return;
1198
- }
1199
- /** @private util function for handling trackEnd event */
1200
- async trackEnd(player, track, payload) {
1201
- if (player.get('internal_nodeChanging') === true)
1202
- return; // Check if nodeChange is in Progress than stop the trackEnd Event from being triggered.
1203
- const trackToUse = track || this.getTrackOfPayload(payload);
1204
- // If a track was forcibly played
1205
- if (payload.reason === "replaced") {
1206
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1207
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.TrackEndReplaced, {
1208
- state: "warn",
1209
- message: `TrackEnd Event does not handle any playback, because the track was replaced.`,
1210
- functionLayer: "LavalinkNode > trackEnd()",
1211
- });
1212
- }
1213
- this.NodeManager.LavalinkManager.emit("trackEnd", player, trackToUse, payload);
1214
- return;
1215
- }
1216
- // If there are no songs in the queue
1217
- if (!player.queue.tracks.length && (player.repeatMode === "off" || player.get("internal_stopPlaying")))
1218
- return this.queueEnd(player, track, payload);
1219
- // If a track had an error while starting
1220
- if (["loadFailed", "cleanup"].includes(payload.reason)) {
1221
- //Dont add tracks if the player is already destroying.
1222
- if (player.get("internal_destroystatus") === true)
1223
- return;
1224
- await queueTrackEnd(player);
1225
- // if no track available, end queue
1226
- if (!player.queue.current)
1227
- return this.queueEnd(player, trackToUse, payload);
1228
- // fire event
1229
- this.NodeManager.LavalinkManager.emit("trackEnd", player, trackToUse, payload);
1230
- // play track if autoSkip is true
1231
- if (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) {
1232
- player.play({ noReplace: true });
1233
- }
1234
- return;
1235
- }
1236
- // remove tracks from the queue
1237
- if (player.repeatMode !== "track" || player.get("internal_skipped"))
1238
- await queueTrackEnd(player);
1239
- else if (trackToUse && !trackToUse?.pluginInfo?.clientData?.previousTrack) { // If there was a current Track already and repeatmode === true, add it to the queue.
1240
- player.queue.previous.unshift(trackToUse);
1241
- if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
1242
- player.queue.previous.splice(player.queue.options.maxPreviousTracks, player.queue.previous.length);
1243
- await player.queue.utils.save();
1244
- }
1245
- // if no track available, end queue
1246
- if (!player.queue.current)
1247
- return this.queueEnd(player, trackToUse, payload);
1248
- player.set("internal_skipped", false);
1249
- // fire event
1250
- this.NodeManager.LavalinkManager.emit("trackEnd", player, trackToUse, payload);
1251
- // play track if autoSkip is true
1252
- if (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) {
1253
- player.play({ noReplace: true });
1254
- }
1255
- return;
1256
- }
1257
- /** @private util function for handling trackStuck event */
1258
- async trackStuck(player, track, payload) {
1259
- if (this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.threshold > 0 && this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.maxAmount >= 0) {
1260
- const oldTimestamps = (player.get("internal_erroredTracksTimestamps") || [])
1261
- .filter(v => Date.now() - v < this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.threshold);
1262
- player.set("internal_erroredTracksTimestamps", [...oldTimestamps, Date.now()]);
1263
- if (oldTimestamps.length > this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.maxAmount) {
1264
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1265
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.TrackStuckMaxTracksErroredPerTime, {
1266
- state: "log",
1267
- message: `trackStuck Event was triggered too often within a given threshold (LavalinkManager.options.playerOptions.maxErrorsPerTime). Threshold: "${this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.threshold}ms", maxAmount: "${this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.maxAmount}"`,
1268
- functionLayer: "LavalinkNode > trackStuck()",
1269
- });
1270
- }
1271
- player.destroy(DestroyReasons.TrackStuckMaxTracksErroredPerTime);
1272
- return;
1273
- }
1274
- }
1275
- this.NodeManager.LavalinkManager.emit("trackStuck", player, track || this.getTrackOfPayload(payload), payload);
1276
- // If there are no songs in the queue
1277
- if (!player.queue.tracks.length && (player.repeatMode === "off" || player.get("internal_stopPlaying"))) {
1278
- try { //Sometimes the trackStuck event triggers from the Lavalink server, but the track continues playing or resumes after. We explicitly end the track in such cases
1279
- await player.node.updatePlayer({ guildId: player.guildId, playerOptions: { track: { encoded: null } } }); //trackEnd -> queueEnd
1280
- return;
1281
- }
1282
- catch {
1283
- return this.queueEnd(player, track || this.getTrackOfPayload(payload), payload);
1284
- }
1285
- }
1286
- // remove the current track, and enqueue the next one
1287
- await queueTrackEnd(player);
1288
- // if no track available, end queue
1289
- if (!player.queue.current) {
1290
- return this.queueEnd(player, track || this.getTrackOfPayload(payload), payload);
1291
- }
1292
- // play track if autoSkip is true
1293
- if (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) {
1294
- player.play({ track: player.queue.current, noReplace: false }); // Replace the stuck track with the new track.
1295
- }
1296
- return;
1297
- }
1298
- /** @private util function for handling trackError event */
1299
- async trackError(player, track, payload) {
1300
- if (this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.threshold > 0 && this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.maxAmount >= 0) {
1301
- const oldTimestamps = (player.get("internal_erroredTracksTimestamps") || [])
1302
- .filter(v => Date.now() - v < this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.threshold);
1303
- player.set("internal_erroredTracksTimestamps", [...oldTimestamps, Date.now()]);
1304
- if (oldTimestamps.length > this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.maxAmount) {
1305
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1306
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.TrackErrorMaxTracksErroredPerTime, {
1307
- state: "log",
1308
- message: `TrackError Event was triggered too often within a given threshold (LavalinkManager.options.playerOptions.maxErrorsPerTime). Threshold: "${this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.threshold}ms", maxAmount: "${this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.maxAmount}"`,
1309
- functionLayer: "LavalinkNode > trackError()",
1310
- });
1311
- }
1312
- player.destroy(DestroyReasons.TrackErrorMaxTracksErroredPerTime);
1313
- return;
1314
- }
1315
- }
1316
- this.NodeManager.LavalinkManager.emit("trackError", player, track || this.getTrackOfPayload(payload), payload);
1317
- return;
1318
- }
1319
- /** @private util function for handling socketClosed event */
1320
- socketClosed(player, payload) {
1321
- this.NodeManager.LavalinkManager.emit("playerSocketClosed", player, payload);
1322
- return;
1323
- }
1324
- /** @private util function for handling SponsorBlock Segmentloaded event */
1325
- SponsorBlockSegmentLoaded(player, track, payload) {
1326
- this.NodeManager.LavalinkManager.emit("SegmentsLoaded", player, track || this.getTrackOfPayload(payload), payload);
1327
- return;
1328
- }
1329
- /** @private util function for handling SponsorBlock SegmentSkipped event */
1330
- SponsorBlockSegmentSkipped(player, track, payload) {
1331
- this.NodeManager.LavalinkManager.emit("SegmentSkipped", player, track || this.getTrackOfPayload(payload), payload);
1332
- return;
1333
- }
1334
- /** @private util function for handling SponsorBlock Chaptersloaded event */
1335
- SponsorBlockChaptersLoaded(player, track, payload) {
1336
- this.NodeManager.LavalinkManager.emit("ChaptersLoaded", player, track || this.getTrackOfPayload(payload), payload);
1337
- return;
1338
- }
1339
- /** @private util function for handling SponsorBlock Chaptersstarted event */
1340
- SponsorBlockChapterStarted(player, track, payload) {
1341
- this.NodeManager.LavalinkManager.emit("ChapterStarted", player, track || this.getTrackOfPayload(payload), payload);
1342
- return;
1343
- }
1344
- /**
1345
- * Get the current sponsorblocks for the sponsorblock plugin
1346
- * @param player passthrough the player
1347
- * @returns sponsorblock seggment from lavalink
1348
- *
1349
- * @example
1350
- * ```ts
1351
- * // use it on the player via player.getSponsorBlock();
1352
- * const sponsorBlockSegments = await player.node.getSponsorBlock(player);
1353
- * ```
1354
- */
1355
- async getSponsorBlock(player) {
1356
- // no plugin enabled
1357
- if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
1358
- throw new RangeError(`there is no sponsorblock-plugin available in the lavalink node: ${this.id}`);
1359
- // do the request
1360
- return await this.request(`/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`);
1361
- }
1362
- /**
1363
- * Set the current sponsorblocks for the sponsorblock plugin
1364
- * @param player passthrough the player
1365
- * @returns void
1366
- *
1367
- * @example
1368
- * ```ts
1369
- * // use it on the player via player.setSponsorBlock();
1370
- * const sponsorBlockSegments = await player.node.setSponsorBlock(player, ["sponsor", "selfpromo"]);
1371
- * ```
1372
- */
1373
- async setSponsorBlock(player, segments = ["sponsor", "selfpromo"]) {
1374
- // no plugin enabled
1375
- if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
1376
- throw new RangeError(`there is no sponsorblock-plugin available in the lavalink node: ${this.id}`);
1377
- // no segments length
1378
- if (!segments.length)
1379
- throw new RangeError("No Segments provided. Did you ment to use 'deleteSponsorBlock'?");
1380
- // a not valid segment
1381
- if (segments.some(v => !validSponsorBlocks.includes(v.toLowerCase())))
1382
- throw new SyntaxError(`You provided a sponsorblock which isn't valid, valid ones are: ${validSponsorBlocks.map(v => `'${v}'`).join(", ")}`);
1383
- // do the request
1384
- await this.request(`/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`, (r) => {
1385
- r.method = "PUT";
1386
- r.headers = { Authorization: this.options.authorization, 'Content-Type': 'application/json' };
1387
- r.body = safeStringify(segments.map(v => v.toLowerCase()));
1388
- });
1389
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1390
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.SetSponsorBlock, {
1391
- state: "log",
1392
- message: `SponsorBlock was set for Player: ${player.guildId} to: ${segments.map(v => `'${v.toLowerCase()}'`).join(", ")}`,
1393
- functionLayer: "LavalinkNode > setSponsorBlock()",
1394
- });
1395
- }
1396
- return;
1397
- }
1398
- /**
1399
- * Delete the sponsorblock plugins
1400
- * @param player passthrough the player
1401
- * @returns void
1402
- *
1403
- * @example
1404
- * ```ts
1405
- * // use it on the player via player.deleteSponsorBlock();
1406
- * const sponsorBlockSegments = await player.node.deleteSponsorBlock(player);
1407
- * ```
1408
- */
1409
- async deleteSponsorBlock(player) {
1410
- // no plugin enabled
1411
- if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
1412
- throw new RangeError(`there is no sponsorblock-plugin available in the lavalink node: ${this.id}`);
1413
- // do the request
1414
- await this.request(`/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`, (r) => {
1415
- r.method = "DELETE";
1416
- });
1417
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1418
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.DeleteSponsorBlock, {
1419
- state: "log",
1420
- message: `SponsorBlock was deleted for Player: ${player.guildId}`,
1421
- functionLayer: "LavalinkNode > deleteSponsorBlock()",
1422
- });
1423
- }
1424
- return;
1425
- }
1426
- /** private util function for handling the queue end event */
1427
- async queueEnd(player, track, payload) {
1428
- if (player.get('internal_nodeChanging') === true)
1429
- return; // Check if nodeChange is in Progress than stop the queueEnd Event from being triggered.
1430
- // add previous track to the queue!
1431
- player.queue.current = null;
1432
- player.playing = false;
1433
- player.set("internal_stopPlaying", undefined);
1434
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1435
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.QueueEnded, {
1436
- state: "log",
1437
- message: `Queue Ended because no more Tracks were in the Queue, due to EventName: "${payload.type}"`,
1438
- functionLayer: "LavalinkNode > queueEnd()",
1439
- });
1440
- }
1441
- if (typeof this.NodeManager.LavalinkManager.options?.playerOptions?.onEmptyQueue?.autoPlayFunction === "function" && typeof player.get("internal_autoplayStopPlaying") === "undefined") {
1442
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1443
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.AutoplayExecution, {
1444
- state: "log",
1445
- message: `Now Triggering Autoplay.`,
1446
- functionLayer: "LavalinkNode > queueEnd() > autoplayFunction",
1447
- });
1448
- }
1449
- const previousAutoplayTime = player.get("internal_previousautoplay");
1450
- const duration = previousAutoplayTime ? Date.now() - previousAutoplayTime : 0;
1451
- if (!duration || duration > this.NodeManager.LavalinkManager.options.playerOptions.minAutoPlayMs || !!player.get("internal_skipped")) {
1452
- await this.NodeManager.LavalinkManager.options?.playerOptions?.onEmptyQueue?.autoPlayFunction(player, track);
1453
- player.set("internal_previousautoplay", Date.now());
1454
- if (player.queue.tracks.length > 0)
1455
- await queueTrackEnd(player);
1456
- else if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1457
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.AutoplayNoSongsAdded, {
1458
- state: "warn",
1459
- message: `Autoplay was triggered but no songs were added to the queue.`,
1460
- functionLayer: "LavalinkNode > queueEnd() > autoplayFunction",
1461
- });
1462
- }
1463
- if (player.queue.current) {
1464
- if (payload.type === "TrackEndEvent")
1465
- this.NodeManager.LavalinkManager.emit("trackEnd", player, track, payload);
1466
- if (this.NodeManager.LavalinkManager.options.autoSkip)
1467
- return player.play({ noReplace: true, paused: false });
1468
- }
1469
- }
1470
- else {
1471
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1472
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.AutoplayThresholdSpamLimiter, {
1473
- state: "warn",
1474
- message: `Autoplay was triggered after the previousautoplay too early. Threshold is: ${this.NodeManager.LavalinkManager.options.playerOptions.minAutoPlayMs}ms and the Duration was ${duration}ms`,
1475
- functionLayer: "LavalinkNode > queueEnd() > autoplayFunction",
1476
- });
1477
- }
1478
- }
1479
- }
1480
- player.set("internal_skipped", false);
1481
- player.set("internal_autoplayStopPlaying", undefined);
1482
- if (track && !track?.pluginInfo?.clientData?.previousTrack) { // If there was a current Track already and repeatmode === true, add it to the queue.
1483
- player.queue.previous.unshift(track);
1484
- if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
1485
- player.queue.previous.splice(player.queue.options.maxPreviousTracks, player.queue.previous.length);
1486
- await player.queue.utils.save();
1487
- }
1488
- if (payload?.reason !== "stopped") {
1489
- await player.queue.utils.save();
1490
- }
1491
- if (typeof this.NodeManager.LavalinkManager.options.playerOptions?.onEmptyQueue?.destroyAfterMs === "number" && !isNaN(this.NodeManager.LavalinkManager.options.playerOptions.onEmptyQueue?.destroyAfterMs) && this.NodeManager.LavalinkManager.options.playerOptions.onEmptyQueue?.destroyAfterMs >= 0) {
1492
- if (this.NodeManager.LavalinkManager.options.playerOptions.onEmptyQueue?.destroyAfterMs === 0) {
1493
- player.destroy(DestroyReasons.QueueEmpty);
1494
- return;
1495
- }
1496
- else {
1497
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1498
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.TriggerQueueEmptyInterval, {
1499
- state: "log",
1500
- message: `Trigger Queue Empty Interval was Triggered because playerOptions.onEmptyQueue.destroyAfterMs is set to ${this.NodeManager.LavalinkManager.options.playerOptions.onEmptyQueue?.destroyAfterMs}ms`,
1501
- functionLayer: "LavalinkNode > queueEnd() > destroyAfterMs",
1502
- });
1503
- }
1504
- this.NodeManager.LavalinkManager.emit("playerQueueEmptyStart", player, this.NodeManager.LavalinkManager.options.playerOptions.onEmptyQueue?.destroyAfterMs);
1505
- if (player.get("internal_queueempty")) {
1506
- clearTimeout(player.get("internal_queueempty"));
1507
- player.set("internal_queueempty", undefined);
1508
- }
1509
- player.set("internal_queueempty", setTimeout(() => {
1510
- player.set("internal_queueempty", undefined);
1511
- if (player.queue.current) {
1512
- return this.NodeManager.LavalinkManager.emit("playerQueueEmptyCancel", player);
1513
- }
1514
- this.NodeManager.LavalinkManager.emit("playerQueueEmptyEnd", player);
1515
- player.destroy(DestroyReasons.QueueEmpty);
1516
- }, this.NodeManager.LavalinkManager.options.playerOptions.onEmptyQueue?.destroyAfterMs));
1517
- }
1518
- }
1519
- this.NodeManager.LavalinkManager.emit("queueEnd", player, track, payload);
1520
- return;
1521
- }
1522
- /**
1523
- * Emitted whenever a line of lyrics gets emitted
1524
- * @event
1525
- * @param {Player} player The player that emitted the event
1526
- * @param {Track} track The track that emitted the event
1527
- * @param {LyricsLineEvent} payload The payload of the event
1528
- */
1529
- async LyricsLine(player, track, payload) {
1530
- if (!player.queue.current) {
1531
- player.queue.current = this.getTrackOfPayload(payload);
1532
- if (player.queue.current) {
1533
- await player.queue.utils.save();
1534
- }
1535
- else {
1536
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1537
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.TrackStartNoTrack, {
1538
- state: "warn",
1539
- message: `Trackstart emitted but there is no track on player.queue.current, trying to get the track of the payload failed too.`,
1540
- functionLayer: "LavalinkNode > trackStart()",
1541
- });
1542
- }
1543
- }
1544
- }
1545
- this.NodeManager.LavalinkManager.emit("LyricsLine", player, track, payload);
1546
- return;
1547
- }
1548
- /**
1549
- * Emitted whenever the lyrics for a track got found
1550
- * @event
1551
- * @param {Player} player The player that emitted the event
1552
- * @param {Track} track The track that emitted the event
1553
- * @param {LyricsFoundEvent} payload The payload of the event
1554
- */
1555
- async LyricsFound(player, track, payload) {
1556
- if (!player.queue.current) {
1557
- player.queue.current = this.getTrackOfPayload(payload);
1558
- if (player.queue.current) {
1559
- await player.queue.utils.save();
1560
- }
1561
- else {
1562
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1563
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.TrackStartNoTrack, {
1564
- state: "warn",
1565
- message: `Trackstart emitted but there is no track on player.queue.current, trying to get the track of the payload failed too.`,
1566
- functionLayer: "LavalinkNode > trackStart()",
1567
- });
1568
- }
1569
- }
1570
- }
1571
- this.NodeManager.LavalinkManager.emit("LyricsFound", player, track, payload);
1572
- return;
1573
- }
1574
- /**
1575
- * Emitted whenever the lyrics for a track got not found
1576
- * @event
1577
- * @param {Player} player The player that emitted the event
1578
- * @param {Track} track The track that emitted the event
1579
- * @param {LyricsNotFoundEvent} payload The payload of the event
1580
- */
1581
- async LyricsNotFound(player, track, payload) {
1582
- if (!player.queue.current) {
1583
- player.queue.current = this.getTrackOfPayload(payload);
1584
- if (player.queue.current) {
1585
- await player.queue.utils.save();
1586
- }
1587
- else {
1588
- if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1589
- this.NodeManager.LavalinkManager.emit("debug", DebugEvents.TrackStartNoTrack, {
1590
- state: "warn",
1591
- message: `Trackstart emitted but there is no track on player.queue.current, trying to get the track of the payload failed too.`,
1592
- functionLayer: "LavalinkNode > trackStart()",
1593
- });
1594
- }
1595
- }
1596
- }
1597
- this.NodeManager.LavalinkManager.emit("LyricsNotFound", player, track, payload);
1598
- return;
1599
- }
1600
- }