magmastream 2.10.2-dev.4 → 2.10.3-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/statestorage/JsonQueue.js +6 -6
- package/dist/statestorage/MemoryQueue.js +6 -6
- package/dist/statestorage/RedisQueue.js +4 -4
- package/dist/structures/Manager.js +40 -38
- package/dist/structures/Node.d.ts +3 -2
- package/dist/structures/Node.js +50 -31
- package/dist/structures/Player.js +14 -9
- package/dist/structures/Utils.js +14 -14
- package/dist/wrappers/cloudstorm.js +3 -3
- package/dist/wrappers/discord.js.js +4 -4
- package/dist/wrappers/discordeno.js +3 -3
- package/dist/wrappers/eris.js +3 -3
- package/dist/wrappers/oceanic.js +3 -3
- package/dist/wrappers/seyfert.js +4 -4
- package/package.json +1 -1
|
@@ -102,11 +102,11 @@ class JsonQueue {
|
|
|
102
102
|
const tracks = Array.isArray(track) ? track : [track];
|
|
103
103
|
if (!tracks.length)
|
|
104
104
|
return;
|
|
105
|
-
const current = (await this.getPrevious()).filter((
|
|
106
|
-
const validTracks = tracks.filter((
|
|
105
|
+
const current = (await this.getPrevious()).filter((previousTrack) => previousTrack !== null);
|
|
106
|
+
const validTracks = tracks.filter((track) => track !== null && typeof track.uri === "string");
|
|
107
107
|
if (!validTracks.length)
|
|
108
108
|
return;
|
|
109
|
-
const newTracks = validTracks.filter((
|
|
109
|
+
const newTracks = validTracks.filter((track) => !current.some((previousTrack) => previousTrack.uri === track.uri));
|
|
110
110
|
if (!newTracks.length)
|
|
111
111
|
return;
|
|
112
112
|
const updated = [...current, ...newTracks];
|
|
@@ -391,9 +391,9 @@ class JsonQueue {
|
|
|
391
391
|
const users = [...userMap.keys()];
|
|
392
392
|
const queues = users.map((id) => userMap.get(id));
|
|
393
393
|
const shuffledQueue = [];
|
|
394
|
-
while (queues.some((
|
|
395
|
-
for (const
|
|
396
|
-
const track =
|
|
394
|
+
while (queues.some((queue) => queue.length > 0)) {
|
|
395
|
+
for (const queue of queues) {
|
|
396
|
+
const track = queue.shift();
|
|
397
397
|
if (track)
|
|
398
398
|
shuffledQueue.push(track);
|
|
399
399
|
}
|
|
@@ -43,7 +43,7 @@ class MemoryQueue extends Array {
|
|
|
43
43
|
const isArray = Array.isArray(track);
|
|
44
44
|
const tracks = isArray ? [...track] : [track];
|
|
45
45
|
// Get the track info as a string
|
|
46
|
-
const trackInfo = isArray ? tracks.map((
|
|
46
|
+
const trackInfo = isArray ? tracks.map((track) => Utils_1.JSONUtils.safe(track, 2)).join(", ") : Utils_1.JSONUtils.safe(track, 2);
|
|
47
47
|
// Emit a debug message
|
|
48
48
|
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[MEMORYQUEUE] Added ${tracks.length} track(s) to queue: ${trackInfo}`);
|
|
49
49
|
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
@@ -129,13 +129,13 @@ class MemoryQueue extends Array {
|
|
|
129
129
|
addPrevious(track) {
|
|
130
130
|
try {
|
|
131
131
|
const max = this.manager.options.maxPreviousTracks;
|
|
132
|
-
this.previous = this.previous.filter((
|
|
132
|
+
this.previous = this.previous.filter((previousTrack) => previousTrack !== null);
|
|
133
133
|
if (Array.isArray(track)) {
|
|
134
|
-
const newTracks = track.filter((
|
|
134
|
+
const newTracks = track.filter((track) => !this.previous.some((previousTrack) => previousTrack?.identifier === track.identifier));
|
|
135
135
|
this.previous.push(...newTracks);
|
|
136
136
|
}
|
|
137
137
|
else {
|
|
138
|
-
const exists = this.previous.some((
|
|
138
|
+
const exists = this.previous.some((previousTrack) => previousTrack?.identifier === track.identifier);
|
|
139
139
|
if (!exists) {
|
|
140
140
|
this.previous.push(track);
|
|
141
141
|
}
|
|
@@ -261,7 +261,7 @@ class MemoryQueue extends Array {
|
|
|
261
261
|
* @returns The previous tracks.
|
|
262
262
|
*/
|
|
263
263
|
getPrevious() {
|
|
264
|
-
this.previous = this.previous.map((
|
|
264
|
+
this.previous = this.previous.map((track) => Utils_1.TrackUtils.revive(track));
|
|
265
265
|
return [...this.previous];
|
|
266
266
|
}
|
|
267
267
|
/**
|
|
@@ -274,7 +274,7 @@ class MemoryQueue extends Array {
|
|
|
274
274
|
* @returns The tracks in the queue.
|
|
275
275
|
*/
|
|
276
276
|
getTracks() {
|
|
277
|
-
this.forEach((
|
|
277
|
+
this.forEach((track) => Utils_1.TrackUtils.revive(track));
|
|
278
278
|
return [...this]; // clone to avoid direct mutation
|
|
279
279
|
}
|
|
280
280
|
/**
|
|
@@ -44,7 +44,7 @@ class RedisQueue {
|
|
|
44
44
|
const isArray = Array.isArray(track);
|
|
45
45
|
const tracks = isArray ? track : [track];
|
|
46
46
|
// Serialize tracks
|
|
47
|
-
const serialized = tracks.map((
|
|
47
|
+
const serialized = tracks.map((track) => this.serialize(track));
|
|
48
48
|
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
49
49
|
// Set current track if none exists
|
|
50
50
|
if (!(await this.getCurrent())) {
|
|
@@ -524,9 +524,9 @@ class RedisQueue {
|
|
|
524
524
|
const users = [...userMap.keys()];
|
|
525
525
|
const queues = users.map((id) => userMap.get(id));
|
|
526
526
|
const shuffledQueue = [];
|
|
527
|
-
while (queues.some((
|
|
528
|
-
for (const
|
|
529
|
-
const track =
|
|
527
|
+
while (queues.some((queue) => queue.length > 0)) {
|
|
528
|
+
for (const queue of queues) {
|
|
529
|
+
const track = queue.shift();
|
|
530
530
|
if (track)
|
|
531
531
|
shuffledQueue.push(track);
|
|
532
532
|
}
|
|
@@ -216,8 +216,8 @@ class Manager extends events_1.EventEmitter {
|
|
|
216
216
|
const search = isUrl ? _query.query : `${_source}:${_query.query}`;
|
|
217
217
|
this.emit(Enums_1.ManagerEventTypes.Debug, isUrl ? `[MANAGER] Performing search for: ${_query.query}` : `[MANAGER] Performing ${_source} search for: ${_query.query}`);
|
|
218
218
|
try {
|
|
219
|
-
const
|
|
220
|
-
if (!
|
|
219
|
+
const lavalinkResponse = (await node.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(search)}`));
|
|
220
|
+
if (!lavalinkResponse) {
|
|
221
221
|
throw new MagmastreamError_1.MagmaStreamError({
|
|
222
222
|
code: Enums_1.MagmaStreamErrorCode.REST_REQUEST_FAILED,
|
|
223
223
|
message: `No results returned from Lavalink for query "${search}".`,
|
|
@@ -225,16 +225,16 @@ class Manager extends events_1.EventEmitter {
|
|
|
225
225
|
});
|
|
226
226
|
}
|
|
227
227
|
let result;
|
|
228
|
-
switch (
|
|
228
|
+
switch (lavalinkResponse.loadType) {
|
|
229
229
|
case Enums_1.LoadTypes.Search: {
|
|
230
|
-
const tracks =
|
|
231
|
-
result = { loadType:
|
|
230
|
+
const tracks = lavalinkResponse.data.map((track) => Utils_1.TrackUtils.build(track, requester));
|
|
231
|
+
result = { loadType: lavalinkResponse.loadType, tracks };
|
|
232
232
|
break;
|
|
233
233
|
}
|
|
234
234
|
case Enums_1.LoadTypes.Short:
|
|
235
235
|
case Enums_1.LoadTypes.Track: {
|
|
236
|
-
const track = Utils_1.TrackUtils.build(
|
|
237
|
-
result = { loadType:
|
|
236
|
+
const track = Utils_1.TrackUtils.build(lavalinkResponse.data, requester);
|
|
237
|
+
result = { loadType: lavalinkResponse.loadType, tracks: [track] };
|
|
238
238
|
break;
|
|
239
239
|
}
|
|
240
240
|
case Enums_1.LoadTypes.Album:
|
|
@@ -243,10 +243,10 @@ class Manager extends events_1.EventEmitter {
|
|
|
243
243
|
case Enums_1.LoadTypes.Podcast:
|
|
244
244
|
case Enums_1.LoadTypes.Show:
|
|
245
245
|
case Enums_1.LoadTypes.Playlist: {
|
|
246
|
-
const playlistData =
|
|
247
|
-
const tracks = playlistData.tracks.map((
|
|
246
|
+
const playlistData = lavalinkResponse.data;
|
|
247
|
+
const tracks = playlistData.tracks.map((track) => Utils_1.TrackUtils.build(track, requester));
|
|
248
248
|
result = {
|
|
249
|
-
loadType:
|
|
249
|
+
loadType: lavalinkResponse.loadType,
|
|
250
250
|
tracks,
|
|
251
251
|
playlist: {
|
|
252
252
|
name: playlistData.info.name,
|
|
@@ -259,7 +259,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
259
259
|
break;
|
|
260
260
|
}
|
|
261
261
|
default:
|
|
262
|
-
result = { loadType:
|
|
262
|
+
result = { loadType: lavalinkResponse.loadType };
|
|
263
263
|
}
|
|
264
264
|
if (this.options.normalizeYouTubeTitles && "tracks" in result) {
|
|
265
265
|
const processTrack = (track) => {
|
|
@@ -275,7 +275,9 @@ class Manager extends events_1.EventEmitter {
|
|
|
275
275
|
result.playlist.tracks = result.tracks;
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
|
-
const summary = "tracks" in result
|
|
278
|
+
const summary = "tracks" in result
|
|
279
|
+
? result.tracks.map((track) => Object.fromEntries(Object.entries(track).filter(([key]) => key !== "requester")))
|
|
280
|
+
: [];
|
|
279
281
|
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Result search for ${_query.query}: ${Utils_1.JSONUtils.safe(summary, 2)}`);
|
|
280
282
|
return result;
|
|
281
283
|
}
|
|
@@ -412,14 +414,14 @@ class Manager extends events_1.EventEmitter {
|
|
|
412
414
|
message: "No available nodes to decode tracks.",
|
|
413
415
|
});
|
|
414
416
|
}
|
|
415
|
-
const
|
|
416
|
-
if (!
|
|
417
|
+
const decodedTrackData = (await node.rest.post("/v4/decodetracks", Utils_1.JSONUtils.safe(tracks, 2)).catch((err) => reject(err)));
|
|
418
|
+
if (!decodedTrackData) {
|
|
417
419
|
throw new MagmastreamError_1.MagmaStreamError({
|
|
418
420
|
code: Enums_1.MagmaStreamErrorCode.REST_REQUEST_FAILED,
|
|
419
421
|
message: "No decoded tracks returned from node.",
|
|
420
422
|
});
|
|
421
423
|
}
|
|
422
|
-
return resolve(
|
|
424
|
+
return resolve(decodedTrackData);
|
|
423
425
|
});
|
|
424
426
|
}
|
|
425
427
|
/**
|
|
@@ -429,9 +431,9 @@ class Manager extends events_1.EventEmitter {
|
|
|
429
431
|
* @throws Will throw an error if no nodes are available or if the API request fails.
|
|
430
432
|
*/
|
|
431
433
|
async decodeTrack(track) {
|
|
432
|
-
const
|
|
434
|
+
const decodedTracks = await this.decodeTracks([track]);
|
|
433
435
|
// Since we're only decoding one track, we can just return the first element of the array
|
|
434
|
-
return
|
|
436
|
+
return decodedTracks[0];
|
|
435
437
|
}
|
|
436
438
|
/**
|
|
437
439
|
* Saves player states.
|
|
@@ -577,7 +579,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
577
579
|
}
|
|
578
580
|
async restorePreviousQueue(player, state) {
|
|
579
581
|
if (state.queue.previous.length > 0) {
|
|
580
|
-
const validPrevious = state.queue.previous.filter((
|
|
582
|
+
const validPrevious = state.queue.previous.filter((track) => track !== null && typeof track.identifier === "string");
|
|
581
583
|
if (validPrevious.length > 0)
|
|
582
584
|
await player.queue.addPrevious(validPrevious);
|
|
583
585
|
}
|
|
@@ -604,31 +606,31 @@ class Manager extends events_1.EventEmitter {
|
|
|
604
606
|
restoreFilters(player, state) {
|
|
605
607
|
const filterActions = {
|
|
606
608
|
bassboost: () => player.filters.bassBoost(state.filters.bassBoostlevel),
|
|
607
|
-
distort: (
|
|
609
|
+
distort: (isEnabled) => player.filters.distort(isEnabled),
|
|
608
610
|
setDistortion: () => player.filters.setDistortion(state.filters.distortion),
|
|
609
|
-
eightD: (
|
|
611
|
+
eightD: (isEnabled) => player.filters.eightD(isEnabled),
|
|
610
612
|
setKaraoke: () => player.filters.setKaraoke(state.filters.karaoke),
|
|
611
|
-
nightcore: (
|
|
612
|
-
slowmo: (
|
|
613
|
-
soft: (
|
|
614
|
-
trebleBass: (
|
|
613
|
+
nightcore: (isEnabled) => player.filters.nightcore(isEnabled),
|
|
614
|
+
slowmo: (isEnabled) => player.filters.slowmo(isEnabled),
|
|
615
|
+
soft: (isEnabled) => player.filters.soft(isEnabled),
|
|
616
|
+
trebleBass: (isEnabled) => player.filters.trebleBass(isEnabled),
|
|
615
617
|
setTimescale: () => player.filters.setTimescale(state.filters.timescale),
|
|
616
|
-
tv: (
|
|
618
|
+
tv: (isEnabled) => player.filters.tv(isEnabled),
|
|
617
619
|
vibrato: () => player.filters.setVibrato(state.filters.vibrato),
|
|
618
|
-
vaporwave: (
|
|
619
|
-
pop: (
|
|
620
|
-
party: (
|
|
621
|
-
earrape: (
|
|
622
|
-
electronic: (
|
|
623
|
-
radio: (
|
|
620
|
+
vaporwave: (isEnabled) => player.filters.vaporwave(isEnabled),
|
|
621
|
+
pop: (isEnabled) => player.filters.pop(isEnabled),
|
|
622
|
+
party: (isEnabled) => player.filters.party(isEnabled),
|
|
623
|
+
earrape: (isEnabled) => player.filters.earrape(isEnabled),
|
|
624
|
+
electronic: (isEnabled) => player.filters.electronic(isEnabled),
|
|
625
|
+
radio: (isEnabled) => player.filters.radio(isEnabled),
|
|
624
626
|
setRotation: () => player.filters.setRotation(state.filters.rotation),
|
|
625
|
-
tremolo: (
|
|
626
|
-
china: (
|
|
627
|
-
chipmunk: (
|
|
628
|
-
darthvader: (
|
|
629
|
-
daycore: (
|
|
630
|
-
doubletime: (
|
|
631
|
-
demon: (
|
|
627
|
+
tremolo: (isEnabled) => player.filters.tremolo(isEnabled),
|
|
628
|
+
china: (isEnabled) => player.filters.china(isEnabled),
|
|
629
|
+
chipmunk: (isEnabled) => player.filters.chipmunk(isEnabled),
|
|
630
|
+
darthvader: (isEnabled) => player.filters.darthvader(isEnabled),
|
|
631
|
+
daycore: (isEnabled) => player.filters.daycore(isEnabled),
|
|
632
|
+
doubletime: (isEnabled) => player.filters.doubletime(isEnabled),
|
|
633
|
+
demon: (isEnabled) => player.filters.demon(isEnabled),
|
|
632
634
|
};
|
|
633
635
|
for (const [filter, isEnabled] of Object.entries(state.filters.filterStatus)) {
|
|
634
636
|
if (isEnabled && filterActions[filter])
|
|
@@ -23,7 +23,8 @@ export declare class Node {
|
|
|
23
23
|
private reconnectTimeout?;
|
|
24
24
|
private reconnectAttempts;
|
|
25
25
|
private redisPrefix?;
|
|
26
|
-
|
|
26
|
+
/** Session ID sent in the reconnect header for resumption — cleared once the ready op is received. */
|
|
27
|
+
private pendingResumeSessionId;
|
|
27
28
|
/**
|
|
28
29
|
* Creates an instance of Node.
|
|
29
30
|
* @param manager - The manager for the node.
|
|
@@ -149,7 +150,7 @@ export declare class Node {
|
|
|
149
150
|
* @emits {nodeRaw} - Emits a nodeRaw event with the raw message received from the WebSocket connection.
|
|
150
151
|
* @private
|
|
151
152
|
*/
|
|
152
|
-
protected message(
|
|
153
|
+
protected message(messagePayload: Buffer | string): Promise<void>;
|
|
153
154
|
/**
|
|
154
155
|
* Handles an event emitted from the Lavalink node.
|
|
155
156
|
* @param {PlayerEvent & PlayerEvents} payload The event emitted from the node.
|
package/dist/structures/Node.js
CHANGED
|
@@ -10,7 +10,7 @@ const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
|
10
10
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
11
11
|
const Enums_1 = require("./Enums");
|
|
12
12
|
const MagmastreamError_1 = require("./MagmastreamError");
|
|
13
|
-
const validSponsorBlocks = Object.values(Enums_1.SponsorBlockSegment).map((
|
|
13
|
+
const validSponsorBlocks = Object.values(Enums_1.SponsorBlockSegment).map((segment) => segment.toLowerCase());
|
|
14
14
|
class Node {
|
|
15
15
|
manager;
|
|
16
16
|
options;
|
|
@@ -31,7 +31,8 @@ class Node {
|
|
|
31
31
|
reconnectTimeout;
|
|
32
32
|
reconnectAttempts = 1;
|
|
33
33
|
redisPrefix;
|
|
34
|
-
|
|
34
|
+
/** Session ID sent in the reconnect header for resumption — cleared once the ready op is received. */
|
|
35
|
+
pendingResumeSessionId = null;
|
|
35
36
|
/**
|
|
36
37
|
* Creates an instance of Node.
|
|
37
38
|
* @param manager - The manager for the node.
|
|
@@ -153,8 +154,6 @@ class Node {
|
|
|
153
154
|
try {
|
|
154
155
|
const raw = fs_1.default.readFileSync(filePath, "utf-8").trim();
|
|
155
156
|
this.sessionId = raw.length ? raw : null;
|
|
156
|
-
if (this.sessionId)
|
|
157
|
-
this.sessionIdsMap.set(this.getCompositeKey(), this.sessionId);
|
|
158
157
|
}
|
|
159
158
|
catch {
|
|
160
159
|
this.sessionId = null;
|
|
@@ -169,7 +168,6 @@ class Node {
|
|
|
169
168
|
const sid = await this.manager.redis.hget(key, compositeKey);
|
|
170
169
|
this.sessionId = sid ?? null;
|
|
171
170
|
if (this.sessionId) {
|
|
172
|
-
this.sessionIdsMap.set(compositeKey, this.sessionId);
|
|
173
171
|
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Restored sessionId for ${compositeKey}: ${this.sessionId}`);
|
|
174
172
|
}
|
|
175
173
|
}
|
|
@@ -210,7 +208,6 @@ class Node {
|
|
|
210
208
|
if (this.sessionId) {
|
|
211
209
|
fs_1.default.writeFileSync(tmpPath, this.sessionId, "utf-8");
|
|
212
210
|
fs_1.default.renameSync(tmpPath, filePath);
|
|
213
|
-
this.sessionIdsMap.set(this.getCompositeKey(), this.sessionId);
|
|
214
211
|
}
|
|
215
212
|
else {
|
|
216
213
|
try {
|
|
@@ -218,7 +215,6 @@ class Node {
|
|
|
218
215
|
fs_1.default.unlinkSync(filePath);
|
|
219
216
|
}
|
|
220
217
|
catch { }
|
|
221
|
-
this.sessionIdsMap.delete(this.getCompositeKey());
|
|
222
218
|
}
|
|
223
219
|
}
|
|
224
220
|
async updateSessionIdRedis() {
|
|
@@ -228,11 +224,9 @@ class Node {
|
|
|
228
224
|
try {
|
|
229
225
|
if (this.sessionId) {
|
|
230
226
|
await this.manager.redis.hset(key, compositeKey, this.sessionId);
|
|
231
|
-
this.sessionIdsMap.set(compositeKey, this.sessionId);
|
|
232
227
|
}
|
|
233
228
|
else {
|
|
234
229
|
await this.manager.redis.hdel(key, compositeKey);
|
|
235
|
-
this.sessionIdsMap.delete(compositeKey);
|
|
236
230
|
}
|
|
237
231
|
}
|
|
238
232
|
catch (err) {
|
|
@@ -262,8 +256,16 @@ class Node {
|
|
|
262
256
|
"User-Id": this.manager.options.clientId,
|
|
263
257
|
"Client-Name": this.manager.options.clientName,
|
|
264
258
|
};
|
|
265
|
-
|
|
266
|
-
|
|
259
|
+
// Capture resume session ID for the WS header, then clear this.sessionId.
|
|
260
|
+
// REST calls guard on this.sessionId being non-null — keeping the stale value
|
|
261
|
+
// would let updatePlayer/destroyPlayer fire with an invalid session during the
|
|
262
|
+
// reconnect window (between connect() and the 'ready' op).
|
|
263
|
+
// pendingResumeSessionId is kept so the 'ready' handler can still compute
|
|
264
|
+
// hadPreviousSession correctly. this.sessionId is re-set by 'ready'.
|
|
265
|
+
this.pendingResumeSessionId = this.sessionId;
|
|
266
|
+
this.sessionId = null;
|
|
267
|
+
if (typeof this.pendingResumeSessionId === "string" && this.pendingResumeSessionId.length > 0) {
|
|
268
|
+
headers["Session-Id"] = this.pendingResumeSessionId;
|
|
267
269
|
}
|
|
268
270
|
this.socket = new ws_1.default(`ws${this.options.useSSL ? "s" : ""}://${this.address}/v4/websocket`, { headers });
|
|
269
271
|
this.socket.on("open", this.open.bind(this));
|
|
@@ -274,7 +276,7 @@ class Node {
|
|
|
274
276
|
const debugInfo = {
|
|
275
277
|
connected: this.connected,
|
|
276
278
|
address: this.address,
|
|
277
|
-
|
|
279
|
+
pendingResumeSessionId: this.pendingResumeSessionId,
|
|
278
280
|
options: {
|
|
279
281
|
clientId: this.manager.options.clientId,
|
|
280
282
|
clientName: this.manager.options.clientName,
|
|
@@ -295,25 +297,29 @@ class Node {
|
|
|
295
297
|
* @returns {Promise<void>} A promise that resolves when the node and its resources have been destroyed.
|
|
296
298
|
*/
|
|
297
299
|
async destroy() {
|
|
298
|
-
if (!this.connected)
|
|
299
|
-
return;
|
|
300
300
|
const debugInfo = {
|
|
301
301
|
connected: this.connected,
|
|
302
302
|
identifier: this.options.identifier,
|
|
303
303
|
address: this.address,
|
|
304
304
|
sessionId: this.sessionId,
|
|
305
|
-
playerCount: this.manager.players.filter((
|
|
305
|
+
playerCount: this.manager.players.filter((player) => player.node == this).size,
|
|
306
306
|
};
|
|
307
307
|
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Destroying node: ${Utils_1.JSONUtils.safe(debugInfo, 2)}`);
|
|
308
308
|
// Automove all players connected to that node
|
|
309
|
-
const players = this.manager.players.filter((
|
|
309
|
+
const players = this.manager.players.filter((player) => player.node == this);
|
|
310
310
|
if (players.size) {
|
|
311
311
|
await Promise.all(Array.from(players.values(), (player) => player.autoMoveNode()));
|
|
312
312
|
}
|
|
313
|
-
|
|
314
|
-
this.socket.removeAllListeners();
|
|
315
|
-
this.reconnectAttempts = 1;
|
|
313
|
+
// Always clear reconnect state regardless of connection status
|
|
316
314
|
clearTimeout(this.reconnectTimeout);
|
|
315
|
+
this.reconnectTimeout = undefined;
|
|
316
|
+
this.reconnectAttempts = 1;
|
|
317
|
+
// Only close the socket if it is actually open
|
|
318
|
+
if (this.connected) {
|
|
319
|
+
this.socket.close(1000, "destroy");
|
|
320
|
+
this.socket.removeAllListeners();
|
|
321
|
+
}
|
|
322
|
+
this.socket = null;
|
|
317
323
|
this.manager.emit(Enums_1.ManagerEventTypes.NodeDestroy, this);
|
|
318
324
|
this.manager.nodes.delete(this.options.identifier);
|
|
319
325
|
}
|
|
@@ -384,7 +390,7 @@ class Node {
|
|
|
384
390
|
};
|
|
385
391
|
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Connected node: ${Utils_1.JSONUtils.safe(debugInfo, 2)}`);
|
|
386
392
|
this.manager.emit(Enums_1.ManagerEventTypes.NodeConnect, this);
|
|
387
|
-
const playersOnBackupNode = this.manager.players.filter((
|
|
393
|
+
const playersOnBackupNode = this.manager.players.filter((player) => player.node.options.isBackup);
|
|
388
394
|
if (playersOnBackupNode.size) {
|
|
389
395
|
Promise.all(Array.from(playersOnBackupNode.values(), (player) => player.moveNode(this.options.identifier)));
|
|
390
396
|
}
|
|
@@ -410,8 +416,12 @@ class Node {
|
|
|
410
416
|
};
|
|
411
417
|
this.manager.emit(Enums_1.ManagerEventTypes.NodeDisconnect, this, { code, reason });
|
|
412
418
|
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Disconnected node: ${Utils_1.JSONUtils.safe(debugInfo, 2)}`);
|
|
419
|
+
// Null the session ID immediately so REST calls during the reconnect delay
|
|
420
|
+
// window don't fire with a stale session. connect() reloads it from storage
|
|
421
|
+
// into pendingResumeSessionId for the WS Session-Id header.
|
|
422
|
+
this.sessionId = null;
|
|
413
423
|
if (this.manager.useableNode) {
|
|
414
|
-
const players = this.manager.players.filter((
|
|
424
|
+
const players = this.manager.players.filter((player) => player.node.options.identifier == this.options.identifier);
|
|
415
425
|
if (players.size) {
|
|
416
426
|
await Promise.all(Array.from(players.values(), (player) => player.autoMoveNode()));
|
|
417
427
|
}
|
|
@@ -453,12 +463,12 @@ class Node {
|
|
|
453
463
|
* @emits {nodeRaw} - Emits a nodeRaw event with the raw message received from the WebSocket connection.
|
|
454
464
|
* @private
|
|
455
465
|
*/
|
|
456
|
-
async message(
|
|
457
|
-
if (Array.isArray(
|
|
458
|
-
|
|
459
|
-
else if (
|
|
460
|
-
|
|
461
|
-
const payload = JSON.parse(
|
|
466
|
+
async message(messagePayload) {
|
|
467
|
+
if (Array.isArray(messagePayload))
|
|
468
|
+
messagePayload = Buffer.concat(messagePayload);
|
|
469
|
+
else if (messagePayload instanceof ArrayBuffer)
|
|
470
|
+
messagePayload = Buffer.from(messagePayload);
|
|
471
|
+
const payload = JSON.parse(messagePayload.toString());
|
|
462
472
|
if (!payload.op)
|
|
463
473
|
return;
|
|
464
474
|
this.manager.emit(Enums_1.ManagerEventTypes.NodeRaw, payload);
|
|
@@ -487,7 +497,11 @@ class Node {
|
|
|
487
497
|
case "ready":
|
|
488
498
|
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Node message: ${Utils_1.JSONUtils.safe(payload, 2)}`);
|
|
489
499
|
this.rest.setSessionId(payload.sessionId);
|
|
490
|
-
|
|
500
|
+
// pendingResumeSessionId holds what we sent in Session-Id header (if anything).
|
|
501
|
+
// Use it — not this.sessionId which was nulled in connect() — to detect whether
|
|
502
|
+
// we attempted resumption with a different session than what Lavalink gave back.
|
|
503
|
+
const hadPreviousSession = this.pendingResumeSessionId && this.pendingResumeSessionId !== payload.sessionId;
|
|
504
|
+
this.pendingResumeSessionId = null;
|
|
491
505
|
this.sessionId = payload.sessionId;
|
|
492
506
|
await this.updateSessionId();
|
|
493
507
|
this.info = await this.fetchInfo();
|
|
@@ -520,6 +534,11 @@ class Node {
|
|
|
520
534
|
return;
|
|
521
535
|
const track = await player.queue.getCurrent();
|
|
522
536
|
const type = payload.type;
|
|
537
|
+
const TRACK_EVENTS = ["TrackStartEvent", "TrackEndEvent", "TrackStuckEvent", "TrackExceptionEvent"];
|
|
538
|
+
if (!track && TRACK_EVENTS.includes(type)) {
|
|
539
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[Node] Received ${type} for guild ${payload.guildId} but queue has no current track — ignoring.`);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
523
542
|
let error;
|
|
524
543
|
switch (type) {
|
|
525
544
|
case "TrackStartEvent":
|
|
@@ -1117,15 +1136,15 @@ class Node {
|
|
|
1117
1136
|
context: { identifier: this.options.identifier, guildId: player.guildId },
|
|
1118
1137
|
});
|
|
1119
1138
|
}
|
|
1120
|
-
if (segments.some((
|
|
1139
|
+
if (segments.some((segment) => !validSponsorBlocks.includes(segment.toLowerCase()))) {
|
|
1121
1140
|
throw new MagmastreamError_1.MagmaStreamError({
|
|
1122
1141
|
code: Enums_1.MagmaStreamErrorCode.NODE_PROTOCOL_ERROR,
|
|
1123
|
-
message: `Invalid SponsorBlock segments provided. Valid ones are: ${validSponsorBlocks.map((
|
|
1142
|
+
message: `Invalid SponsorBlock segments provided. Valid ones are: ${validSponsorBlocks.map((segment) => `'${segment}'`).join(", ")}`,
|
|
1124
1143
|
context: { identifier: this.options.identifier, guildId: player.guildId, invalidSegments: segments },
|
|
1125
1144
|
});
|
|
1126
1145
|
}
|
|
1127
1146
|
try {
|
|
1128
|
-
await this.rest.put(`/v4/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`, segments.map((
|
|
1147
|
+
await this.rest.put(`/v4/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`, segments.map((segment) => segment.toLowerCase()));
|
|
1129
1148
|
}
|
|
1130
1149
|
catch (err) {
|
|
1131
1150
|
throw err instanceof MagmastreamError_1.MagmaStreamError
|
|
@@ -943,7 +943,12 @@ class Player {
|
|
|
943
943
|
context: { guildId: this.guildId },
|
|
944
944
|
});
|
|
945
945
|
}
|
|
946
|
-
|
|
946
|
+
// Only destroy the player on the old node if it is still reachable.
|
|
947
|
+
// If the server is down the REST call would fail anyway; skipping it
|
|
948
|
+
// also prevents a spurious error when switching nodes during an outage.
|
|
949
|
+
if (this.node.connected) {
|
|
950
|
+
await this.node.rest.destroyPlayer(this.guildId).catch(() => { });
|
|
951
|
+
}
|
|
947
952
|
this.manager.players.delete(this.guildId);
|
|
948
953
|
this.node = node;
|
|
949
954
|
this.manager.players.set(this.guildId, this);
|
|
@@ -1038,7 +1043,7 @@ class Player {
|
|
|
1038
1043
|
this.voiceReceiverWsClient = new ws_1.WebSocket(`${useSSL ? "wss" : "ws"}://${host}:${port}/connection/data`, { headers });
|
|
1039
1044
|
this.voiceReceiverWsClient.on("open", () => this.openVoiceReceiver());
|
|
1040
1045
|
this.voiceReceiverWsClient.on("error", (err) => this.onVoiceReceiverError(err));
|
|
1041
|
-
this.voiceReceiverWsClient.on("message", (
|
|
1046
|
+
this.voiceReceiverWsClient.on("message", (rawMessage) => this.onVoiceReceiverMessage(rawMessage.toString()));
|
|
1042
1047
|
this.voiceReceiverWsClient.on("close", (code, reason) => this.closeVoiceReceiver(code, reason.toString()));
|
|
1043
1048
|
}
|
|
1044
1049
|
/**
|
|
@@ -1161,18 +1166,18 @@ class Player {
|
|
|
1161
1166
|
* @returns {Promise<void>} - A promise that resolves when the voice state is updated.
|
|
1162
1167
|
*/
|
|
1163
1168
|
async updateVoice() {
|
|
1164
|
-
const
|
|
1165
|
-
const
|
|
1166
|
-
if (!
|
|
1169
|
+
const voiceState = this.voiceState;
|
|
1170
|
+
const voiceEvent = voiceState?.event;
|
|
1171
|
+
if (!voiceState?.channelId || !voiceState?.sessionId || !voiceEvent?.token || !voiceEvent?.endpoint)
|
|
1167
1172
|
return;
|
|
1168
1173
|
await this.node.rest.updatePlayer({
|
|
1169
1174
|
guildId: this.options.guildId,
|
|
1170
1175
|
data: {
|
|
1171
1176
|
voice: {
|
|
1172
|
-
token:
|
|
1173
|
-
endpoint:
|
|
1174
|
-
sessionId:
|
|
1175
|
-
channelId:
|
|
1177
|
+
token: voiceEvent.token,
|
|
1178
|
+
endpoint: voiceEvent.endpoint,
|
|
1179
|
+
sessionId: voiceState.sessionId,
|
|
1180
|
+
channelId: voiceState.channelId,
|
|
1176
1181
|
},
|
|
1177
1182
|
},
|
|
1178
1183
|
});
|
package/dist/structures/Utils.js
CHANGED
|
@@ -62,8 +62,8 @@ class TrackUtils {
|
|
|
62
62
|
static isTrack(track) {
|
|
63
63
|
if (typeof track !== "object" || track === null)
|
|
64
64
|
return false;
|
|
65
|
-
const
|
|
66
|
-
return REQUIRED_TRACK_KEYS.every((key) => typeof
|
|
65
|
+
const trackRecord = track;
|
|
66
|
+
return REQUIRED_TRACK_KEYS.every((key) => typeof trackRecord[key] === "string");
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
69
|
* Checks if the provided argument is a valid Track array.
|
|
@@ -295,7 +295,7 @@ class AutoPlayUtils {
|
|
|
295
295
|
const resolvedTracks = await this.resolveTracksFromQuery(`${randomTrack.artist.name} - ${randomTrack.name}`, this.manager.options.defaultSearchPlatform, track.requester);
|
|
296
296
|
if (!resolvedTracks.length)
|
|
297
297
|
return [];
|
|
298
|
-
const filteredTracks = resolvedTracks.filter((
|
|
298
|
+
const filteredTracks = resolvedTracks.filter((resolvedTrack) => resolvedTrack.uri !== track.uri);
|
|
299
299
|
if (!filteredTracks.length) {
|
|
300
300
|
return [];
|
|
301
301
|
}
|
|
@@ -392,10 +392,10 @@ class AutoPlayUtils {
|
|
|
392
392
|
const h2 = element.querySelector('h2[itemprop="name"]');
|
|
393
393
|
if (!h2)
|
|
394
394
|
return null;
|
|
395
|
-
const
|
|
396
|
-
if (!
|
|
395
|
+
const anchorElement = h2.querySelector('a[itemprop="url"]');
|
|
396
|
+
if (!anchorElement)
|
|
397
397
|
return null;
|
|
398
|
-
const href =
|
|
398
|
+
const href = anchorElement.getAttribute("href");
|
|
399
399
|
return href ? `https://soundcloud.com${href}` : null;
|
|
400
400
|
})
|
|
401
401
|
.filter(Boolean);
|
|
@@ -433,7 +433,7 @@ class AutoPlayUtils {
|
|
|
433
433
|
searchURI = `https://www.youtube.com/watch?v=${videoID}&list=RD${videoID}&index=${randomIndex}`;
|
|
434
434
|
} while (track.uri.includes(searchURI));
|
|
435
435
|
const resolvedTracks = await this.resolveTracksFromQuery(searchURI, Enums_1.SearchPlatform.YouTube, requester);
|
|
436
|
-
const filteredTracks = resolvedTracks.filter((
|
|
436
|
+
const filteredTracks = resolvedTracks.filter((resolvedTrack) => resolvedTrack.uri !== track.uri);
|
|
437
437
|
return filteredTracks;
|
|
438
438
|
}
|
|
439
439
|
case Enums_1.AutoPlayPlatform.Tidal: {
|
|
@@ -596,7 +596,7 @@ class AutoPlayUtils {
|
|
|
596
596
|
context: { recommendedResult },
|
|
597
597
|
});
|
|
598
598
|
}
|
|
599
|
-
return data.map((
|
|
599
|
+
return data.map((trackData) => TrackUtils.build(trackData, requester, true));
|
|
600
600
|
}
|
|
601
601
|
case Enums_1.LoadTypes.Album:
|
|
602
602
|
case Enums_1.LoadTypes.Artist:
|
|
@@ -606,7 +606,7 @@ class AutoPlayUtils {
|
|
|
606
606
|
case Enums_1.LoadTypes.Playlist: {
|
|
607
607
|
const data = recommendedResult.data;
|
|
608
608
|
if (this.isPlaylistRawData(data)) {
|
|
609
|
-
return data.tracks.map((
|
|
609
|
+
return data.tracks.map((trackData) => TrackUtils.build(trackData, requester, true));
|
|
610
610
|
}
|
|
611
611
|
throw new MagmastreamError_1.MagmaStreamError({
|
|
612
612
|
code: Enums_1.MagmaStreamErrorCode.UTILS_AUTOPLAY_BUILD_FAILED,
|
|
@@ -666,9 +666,9 @@ class PlayerUtils {
|
|
|
666
666
|
if (!obj || typeof obj !== "object")
|
|
667
667
|
return obj;
|
|
668
668
|
const result = {};
|
|
669
|
-
for (const [k,
|
|
670
|
-
if (!isNonSerializable(
|
|
671
|
-
result[k] =
|
|
669
|
+
for (const [k, entryValue] of Object.entries(obj)) {
|
|
670
|
+
if (!isNonSerializable(entryValue)) {
|
|
671
|
+
result[k] = entryValue;
|
|
672
672
|
}
|
|
673
673
|
}
|
|
674
674
|
return result;
|
|
@@ -690,8 +690,8 @@ class PlayerUtils {
|
|
|
690
690
|
}
|
|
691
691
|
};
|
|
692
692
|
const safeCurrent = current ? serializeTrack(current) : null;
|
|
693
|
-
const safeTracks = tracks.map(serializeTrack).filter((
|
|
694
|
-
const safePrevious = previous.map(serializeTrack).filter((
|
|
693
|
+
const safeTracks = tracks.map(serializeTrack).filter((serializedTrack) => serializedTrack !== null);
|
|
694
|
+
const safePrevious = previous.map(serializeTrack).filter((serializedTrack) => serializedTrack !== null);
|
|
695
695
|
let safeNode = null;
|
|
696
696
|
if (player.node) {
|
|
697
697
|
try {
|
|
@@ -63,12 +63,12 @@ class CloudstormManager extends Manager_1.Manager {
|
|
|
63
63
|
}
|
|
64
64
|
// CloudStorm has no user/guild cache — return minimal portable info only.
|
|
65
65
|
async resolveUser(user) {
|
|
66
|
-
const
|
|
67
|
-
const cached = this.getUserFromCache(
|
|
66
|
+
const userId = typeof user === "string" ? user : String(user.id);
|
|
67
|
+
const cached = this.getUserFromCache(userId);
|
|
68
68
|
if (cached)
|
|
69
69
|
return cached;
|
|
70
70
|
return {
|
|
71
|
-
id,
|
|
71
|
+
id: userId,
|
|
72
72
|
username: typeof user === "string" ? undefined : user.username,
|
|
73
73
|
};
|
|
74
74
|
}
|
|
@@ -50,16 +50,16 @@ class DiscordJSManager extends Manager_1.Manager {
|
|
|
50
50
|
guild.shard.send(packet);
|
|
51
51
|
}
|
|
52
52
|
async resolveUser(user) {
|
|
53
|
-
const
|
|
54
|
-
const cached = this.client.users.cache.get(
|
|
53
|
+
const userId = typeof user === "string" ? user : String(user.id);
|
|
54
|
+
const cached = this.client.users.cache.get(userId);
|
|
55
55
|
if (cached)
|
|
56
56
|
return cached;
|
|
57
57
|
try {
|
|
58
|
-
const fetched = await this.client.users.fetch(
|
|
58
|
+
const fetched = await this.client.users.fetch(userId);
|
|
59
59
|
return fetched;
|
|
60
60
|
}
|
|
61
61
|
catch {
|
|
62
|
-
return { id, username: typeof user === "string" ? undefined : user.username };
|
|
62
|
+
return { id: userId, username: typeof user === "string" ? undefined : user.username };
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
resolveGuild(guildId) {
|
|
@@ -57,14 +57,14 @@ class DiscordenoManager extends Manager_1.Manager {
|
|
|
57
57
|
* Uses user-provided cache getter if available, otherwise falls back to minimal info.
|
|
58
58
|
*/
|
|
59
59
|
async resolveUser(user) {
|
|
60
|
-
const
|
|
60
|
+
const userId = typeof user === "string" ? user : String(user.id);
|
|
61
61
|
// Try user-provided cache getter
|
|
62
|
-
const cached = this.getUserFromCache(
|
|
62
|
+
const cached = this.getUserFromCache(userId);
|
|
63
63
|
if (cached)
|
|
64
64
|
return cached;
|
|
65
65
|
// Fallback: return minimal info
|
|
66
66
|
return {
|
|
67
|
-
id,
|
|
67
|
+
id: userId,
|
|
68
68
|
username: typeof user === "string" ? undefined : user.username,
|
|
69
69
|
};
|
|
70
70
|
}
|
package/dist/wrappers/eris.js
CHANGED
|
@@ -35,12 +35,12 @@ class ErisManager extends Manager_1.Manager {
|
|
|
35
35
|
guild.shard.sendWS(packet.op, packet.d);
|
|
36
36
|
}
|
|
37
37
|
async resolveUser(user) {
|
|
38
|
-
const
|
|
39
|
-
const cached = this.client.users.get(
|
|
38
|
+
const userId = typeof user === "string" ? user : String(user.id);
|
|
39
|
+
const cached = this.client.users.get(userId);
|
|
40
40
|
if (cached)
|
|
41
41
|
return cached;
|
|
42
42
|
return {
|
|
43
|
-
id,
|
|
43
|
+
id: userId,
|
|
44
44
|
username: typeof user === "string" ? undefined : user.username,
|
|
45
45
|
};
|
|
46
46
|
}
|
package/dist/wrappers/oceanic.js
CHANGED
|
@@ -35,12 +35,12 @@ class OceanicManager extends Manager_1.Manager {
|
|
|
35
35
|
guild.shard.send(packet.op, packet.d);
|
|
36
36
|
}
|
|
37
37
|
async resolveUser(user) {
|
|
38
|
-
const
|
|
39
|
-
const cached = this.client.users.get(
|
|
38
|
+
const userId = typeof user === "string" ? user : String(user.id);
|
|
39
|
+
const cached = this.client.users.get(userId);
|
|
40
40
|
if (cached)
|
|
41
41
|
return cached;
|
|
42
42
|
return {
|
|
43
|
-
id,
|
|
43
|
+
id: userId,
|
|
44
44
|
username: typeof user === "string" ? undefined : user.username,
|
|
45
45
|
};
|
|
46
46
|
}
|
package/dist/wrappers/seyfert.js
CHANGED
|
@@ -63,15 +63,15 @@ class SeyfertManager extends Manager_1.Manager {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
async resolveUser(user) {
|
|
66
|
-
const
|
|
67
|
-
const cached = this.client.cache.users?.get(
|
|
66
|
+
const userId = typeof user === "string" ? user : String(user.id);
|
|
67
|
+
const cached = this.client.cache.users?.get(userId);
|
|
68
68
|
if (cached)
|
|
69
69
|
return cached;
|
|
70
70
|
try {
|
|
71
|
-
return await this.client.users.fetch(
|
|
71
|
+
return await this.client.users.fetch(userId);
|
|
72
72
|
}
|
|
73
73
|
catch {
|
|
74
|
-
return { id, username: typeof user === "string" ? undefined : user.username };
|
|
74
|
+
return { id: userId, username: typeof user === "string" ? undefined : user.username };
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
resolveGuild(guildId) {
|