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.
- package/README.md +9 -2
- package/dist/index.d.mts +3036 -0
- package/dist/index.d.ts +3036 -0
- package/dist/index.js +4965 -0
- package/dist/index.mjs +4904 -0
- package/package.json +21 -24
- package/dist/cjs/index.d.ts +0 -16
- package/dist/cjs/index.js +0 -19
- package/dist/cjs/package.json +0 -3
- package/dist/cjs/structures/Constants.d.ts +0 -90
- package/dist/cjs/structures/Constants.js +0 -296
- package/dist/cjs/structures/CustomSearches/BandCampSearch.d.ts +0 -3
- package/dist/cjs/structures/CustomSearches/BandCampSearch.js +0 -39
- package/dist/cjs/structures/Filters.d.ts +0 -169
- package/dist/cjs/structures/Filters.js +0 -700
- package/dist/cjs/structures/LavalinkManager.d.ts +0 -232
- package/dist/cjs/structures/LavalinkManager.js +0 -621
- package/dist/cjs/structures/LavalinkManagerStatics.d.ts +0 -15
- package/dist/cjs/structures/LavalinkManagerStatics.js +0 -149
- package/dist/cjs/structures/Node.d.ts +0 -523
- package/dist/cjs/structures/Node.js +0 -1605
- package/dist/cjs/structures/NodeManager.d.ts +0 -100
- package/dist/cjs/structures/NodeManager.js +0 -224
- package/dist/cjs/structures/Player.d.ts +0 -223
- package/dist/cjs/structures/Player.js +0 -807
- package/dist/cjs/structures/Queue.d.ts +0 -186
- package/dist/cjs/structures/Queue.js +0 -390
- package/dist/cjs/structures/Types/Filters.d.ts +0 -190
- package/dist/cjs/structures/Types/Filters.js +0 -2
- package/dist/cjs/structures/Types/Manager.d.ts +0 -271
- package/dist/cjs/structures/Types/Manager.js +0 -2
- package/dist/cjs/structures/Types/Node.d.ts +0 -238
- package/dist/cjs/structures/Types/Node.js +0 -2
- package/dist/cjs/structures/Types/Player.d.ts +0 -114
- package/dist/cjs/structures/Types/Player.js +0 -2
- package/dist/cjs/structures/Types/Queue.d.ts +0 -35
- package/dist/cjs/structures/Types/Queue.js +0 -2
- package/dist/cjs/structures/Types/Track.d.ts +0 -134
- package/dist/cjs/structures/Types/Track.js +0 -2
- package/dist/cjs/structures/Types/Utils.d.ts +0 -443
- package/dist/cjs/structures/Types/Utils.js +0 -2
- package/dist/cjs/structures/Utils.d.ts +0 -116
- package/dist/cjs/structures/Utils.js +0 -567
- package/dist/esm/index.d.ts +0 -16
- package/dist/esm/index.js +0 -16
- package/dist/esm/package.json +0 -3
- package/dist/esm/structures/Constants.d.ts +0 -90
- package/dist/esm/structures/Constants.js +0 -293
- package/dist/esm/structures/CustomSearches/BandCampSearch.d.ts +0 -3
- package/dist/esm/structures/CustomSearches/BandCampSearch.js +0 -35
- package/dist/esm/structures/Filters.d.ts +0 -169
- package/dist/esm/structures/Filters.js +0 -696
- package/dist/esm/structures/LavalinkManager.d.ts +0 -232
- package/dist/esm/structures/LavalinkManager.js +0 -617
- package/dist/esm/structures/LavalinkManagerStatics.d.ts +0 -15
- package/dist/esm/structures/LavalinkManagerStatics.js +0 -146
- package/dist/esm/structures/Node.d.ts +0 -523
- package/dist/esm/structures/Node.js +0 -1600
- package/dist/esm/structures/NodeManager.d.ts +0 -100
- package/dist/esm/structures/NodeManager.js +0 -220
- package/dist/esm/structures/Player.d.ts +0 -223
- package/dist/esm/structures/Player.js +0 -803
- package/dist/esm/structures/Queue.d.ts +0 -186
- package/dist/esm/structures/Queue.js +0 -384
- package/dist/esm/structures/Types/Filters.d.ts +0 -190
- package/dist/esm/structures/Types/Filters.js +0 -1
- package/dist/esm/structures/Types/Manager.d.ts +0 -271
- package/dist/esm/structures/Types/Manager.js +0 -1
- package/dist/esm/structures/Types/Node.d.ts +0 -238
- package/dist/esm/structures/Types/Node.js +0 -1
- package/dist/esm/structures/Types/Player.d.ts +0 -114
- package/dist/esm/structures/Types/Player.js +0 -1
- package/dist/esm/structures/Types/Queue.d.ts +0 -35
- package/dist/esm/structures/Types/Queue.js +0 -1
- package/dist/esm/structures/Types/Track.d.ts +0 -134
- package/dist/esm/structures/Types/Track.js +0 -1
- package/dist/esm/structures/Types/Utils.d.ts +0 -443
- package/dist/esm/structures/Types/Utils.js +0 -1
- package/dist/esm/structures/Utils.d.ts +0 -116
- package/dist/esm/structures/Utils.js +0 -559
- package/dist/types/index.d.ts +0 -16
- package/dist/types/structures/Constants.d.ts +0 -90
- package/dist/types/structures/CustomSearches/BandCampSearch.d.ts +0 -3
- package/dist/types/structures/Filters.d.ts +0 -169
- package/dist/types/structures/LavalinkManager.d.ts +0 -232
- package/dist/types/structures/LavalinkManagerStatics.d.ts +0 -15
- package/dist/types/structures/Node.d.ts +0 -523
- package/dist/types/structures/NodeManager.d.ts +0 -100
- package/dist/types/structures/Player.d.ts +0 -223
- package/dist/types/structures/Queue.d.ts +0 -186
- package/dist/types/structures/Types/Filters.d.ts +0 -190
- package/dist/types/structures/Types/Manager.d.ts +0 -271
- package/dist/types/structures/Types/Node.d.ts +0 -238
- package/dist/types/structures/Types/Player.d.ts +0 -114
- package/dist/types/structures/Types/Queue.d.ts +0 -35
- package/dist/types/structures/Types/Track.d.ts +0 -134
- package/dist/types/structures/Types/Utils.d.ts +0 -443
- 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
|
-
}
|