magmastream 2.9.3-dev.2 → 2.9.3-dev.20
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/index.d.ts +168 -101
- package/dist/index.js +1 -1
- package/dist/statestorage/JsonQueue.js +28 -4
- package/dist/statestorage/MemoryQueue.js +51 -36
- package/dist/statestorage/RedisQueue.js +30 -9
- package/dist/structures/Enums.js +6 -0
- package/dist/structures/Filters.js +5 -4
- package/dist/structures/Manager.js +62 -48
- package/dist/structures/Node.js +91 -145
- package/dist/structures/Player.js +31 -124
- package/dist/structures/Rest.js +41 -21
- package/dist/structures/Utils.js +139 -77
- package/dist/wrappers/discord.js.js +13 -4
- package/dist/wrappers/discordeno.js +73 -0
- package/dist/wrappers/eris.js +14 -3
- package/dist/wrappers/oceanic.js +16 -4
- package/dist/wrappers/seyfert.js +19 -1
- package/package.json +30 -23
- package/dist/wrappers/detritus.js +0 -52
package/dist/structures/Node.js
CHANGED
|
@@ -31,7 +31,6 @@ class Node {
|
|
|
31
31
|
reconnectTimeout;
|
|
32
32
|
reconnectAttempts = 1;
|
|
33
33
|
redisPrefix;
|
|
34
|
-
sessionIdsFilePath;
|
|
35
34
|
sessionIdsMap = new Map();
|
|
36
35
|
/**
|
|
37
36
|
* Creates an instance of Node.
|
|
@@ -97,18 +96,12 @@ class Node {
|
|
|
97
96
|
switch (this.manager.options.stateStorage.type) {
|
|
98
97
|
case Enums_1.StateStorageType.Memory:
|
|
99
98
|
case Enums_1.StateStorageType.JSON:
|
|
100
|
-
this.sessionIdsFilePath = path_1.default.join(process.cwd(), "magmastream", "sessionData", "sessionIds.json");
|
|
101
|
-
const configDir = path_1.default.dirname(this.sessionIdsFilePath);
|
|
102
|
-
if (!fs_1.default.existsSync(configDir)) {
|
|
103
|
-
fs_1.default.mkdirSync(configDir, { recursive: true });
|
|
104
|
-
}
|
|
105
|
-
this.createSessionIdsFile();
|
|
106
99
|
this.createReadmeFile();
|
|
107
100
|
break;
|
|
108
101
|
case Enums_1.StateStorageType.Redis:
|
|
109
102
|
this.redisPrefix = this.manager.options.stateStorage.redisConfig.prefix?.endsWith(":")
|
|
110
103
|
? this.manager.options.stateStorage.redisConfig.prefix
|
|
111
|
-
: this.manager.options.stateStorage.redisConfig.prefix ?? "magmastream:";
|
|
104
|
+
: (this.manager.options.stateStorage.redisConfig.prefix ?? "magmastream:");
|
|
112
105
|
break;
|
|
113
106
|
}
|
|
114
107
|
}
|
|
@@ -125,16 +118,18 @@ class Node {
|
|
|
125
118
|
get address() {
|
|
126
119
|
return `${this.options.host}:${this.options.port}`;
|
|
127
120
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
121
|
+
getCompositeKey() {
|
|
122
|
+
return `${this.options.identifier}::${this.manager.options.clusterId}`;
|
|
123
|
+
}
|
|
124
|
+
getRedisSessionIdsKey() {
|
|
125
|
+
return `${this.redisPrefix}node:sessionIds`;
|
|
126
|
+
}
|
|
127
|
+
getNodeSessionsDir() {
|
|
128
|
+
return path_1.default.join(process.cwd(), "magmastream", "sessionData", "cluster", String(this.manager.options.clusterId), "nodeSessions");
|
|
129
|
+
}
|
|
130
|
+
getNodeSessionPath() {
|
|
131
|
+
const safeId = String(this.options.identifier).replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
132
|
+
return path_1.default.join(this.getNodeSessionsDir(), `${safeId}.txt`);
|
|
138
133
|
}
|
|
139
134
|
/**
|
|
140
135
|
* Loads session IDs from the sessionIds.json file if it exists.
|
|
@@ -148,49 +143,43 @@ class Node {
|
|
|
148
143
|
switch (this.manager.options.stateStorage.type) {
|
|
149
144
|
case Enums_1.StateStorageType.Memory:
|
|
150
145
|
case Enums_1.StateStorageType.JSON: {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
146
|
+
const dir = this.getNodeSessionsDir();
|
|
147
|
+
const filePath = this.getNodeSessionPath();
|
|
148
|
+
if (!fs_1.default.existsSync(dir))
|
|
149
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
150
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
151
|
+
this.sessionId = null;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
const raw = fs_1.default.readFileSync(filePath, "utf-8").trim();
|
|
156
|
+
this.sessionId = raw.length ? raw : null;
|
|
157
|
+
if (this.sessionId)
|
|
158
|
+
this.sessionIdsMap.set(this.getCompositeKey(), this.sessionId);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
this.sessionId = null;
|
|
159
162
|
}
|
|
160
163
|
break;
|
|
161
164
|
}
|
|
162
|
-
case Enums_1.StateStorageType.Redis:
|
|
163
|
-
const key =
|
|
164
|
-
this.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
message: "Invalid sessionIds data type from Redis.",
|
|
173
|
-
context: { sessionIds },
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
this.sessionIdsMap = new Map(Object.entries(sessionIds));
|
|
177
|
-
const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
|
|
178
|
-
if (this.sessionIdsMap.has(compositeKey)) {
|
|
179
|
-
this.sessionId = this.sessionIdsMap.get(compositeKey) || null;
|
|
180
|
-
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Restored sessionId for ${compositeKey}: ${this.sessionId}`);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
catch (err) {
|
|
184
|
-
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Failed to parse Redis sessionIds: ${err.message}`);
|
|
185
|
-
this.sessionIdsMap = new Map();
|
|
165
|
+
case Enums_1.StateStorageType.Redis: {
|
|
166
|
+
const key = this.getRedisSessionIdsKey();
|
|
167
|
+
const compositeKey = this.getCompositeKey();
|
|
168
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Loading sessionId from Redis hash: ${key} field: ${compositeKey}`);
|
|
169
|
+
try {
|
|
170
|
+
const sid = await this.manager.redis.hget(key, compositeKey);
|
|
171
|
+
this.sessionId = sid ?? null;
|
|
172
|
+
if (this.sessionId) {
|
|
173
|
+
this.sessionIdsMap.set(compositeKey, this.sessionId);
|
|
174
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Restored sessionId for ${compositeKey}: ${this.sessionId}`);
|
|
186
175
|
}
|
|
187
176
|
}
|
|
188
|
-
|
|
189
|
-
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE]
|
|
190
|
-
|
|
191
|
-
this.sessionIdsMap = new Map();
|
|
177
|
+
catch (err) {
|
|
178
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Failed to load sessionId from Redis hash: ${err.message}`);
|
|
179
|
+
this.sessionId = null;
|
|
192
180
|
}
|
|
193
181
|
break;
|
|
182
|
+
}
|
|
194
183
|
}
|
|
195
184
|
}
|
|
196
185
|
/**
|
|
@@ -207,84 +196,53 @@ class Node {
|
|
|
207
196
|
async updateSessionId() {
|
|
208
197
|
switch (this.manager.options.stateStorage.type) {
|
|
209
198
|
case Enums_1.StateStorageType.Memory:
|
|
210
|
-
case Enums_1.StateStorageType.JSON:
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
this.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
fs_1.default.writeFileSync(tmpPath, Utils_1.JSONUtils.safe(fileData, 2), "utf-8");
|
|
232
|
-
fs_1.default.renameSync(tmpPath, filePath);
|
|
233
|
-
this.sessionIdsMap = new Map(Object.entries(fileData));
|
|
234
|
-
updated = true;
|
|
235
|
-
}
|
|
236
|
-
catch (err) {
|
|
237
|
-
retries--;
|
|
238
|
-
if (retries === 0) {
|
|
239
|
-
throw new MagmastreamError_1.MagmaStreamError({
|
|
240
|
-
code: Enums_1.MagmaStreamErrorCode.NODE_SESSION_IDS_UPDATE_FAILED,
|
|
241
|
-
message: `Failed to update sessionIds after retries.`,
|
|
242
|
-
cause: err instanceof Error ? err : undefined,
|
|
243
|
-
context: { filePath, compositeKey, storage: "file" },
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
break;
|
|
199
|
+
case Enums_1.StateStorageType.JSON:
|
|
200
|
+
return this.updateSessionIdFile();
|
|
201
|
+
case Enums_1.StateStorageType.Redis:
|
|
202
|
+
return this.updateSessionIdRedis();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async updateSessionIdFile() {
|
|
206
|
+
const dir = this.getNodeSessionsDir();
|
|
207
|
+
const filePath = this.getNodeSessionPath();
|
|
208
|
+
const tmpPath = `${filePath}.tmp`;
|
|
209
|
+
if (!fs_1.default.existsSync(dir))
|
|
210
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
211
|
+
if (this.sessionId) {
|
|
212
|
+
fs_1.default.writeFileSync(tmpPath, this.sessionId, "utf-8");
|
|
213
|
+
fs_1.default.renameSync(tmpPath, filePath);
|
|
214
|
+
this.sessionIdsMap.set(this.getCompositeKey(), this.sessionId);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
try {
|
|
218
|
+
if (fs_1.default.existsSync(filePath))
|
|
219
|
+
fs_1.default.unlinkSync(filePath);
|
|
250
220
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Redis key not found — creating new sessionIds key.`);
|
|
266
|
-
sessionIds = {};
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
catch (err) {
|
|
270
|
-
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Corrupted Redis sessionIds, reinitializing: ${err.message}`);
|
|
271
|
-
sessionIds = {};
|
|
272
|
-
}
|
|
273
|
-
try {
|
|
274
|
-
sessionIds[compositeKey] = this.sessionId;
|
|
275
|
-
this.sessionIdsMap = new Map(Object.entries(sessionIds));
|
|
276
|
-
await this.manager.redis.set(key, Utils_1.JSONUtils.safe(sessionIds));
|
|
277
|
-
}
|
|
278
|
-
catch (err) {
|
|
279
|
-
throw new MagmastreamError_1.MagmaStreamError({
|
|
280
|
-
code: Enums_1.MagmaStreamErrorCode.NODE_SESSION_IDS_UPDATE_FAILED,
|
|
281
|
-
message: `Failed to update sessionIds in Redis.`,
|
|
282
|
-
cause: err instanceof Error ? err : undefined,
|
|
283
|
-
context: { key, compositeKey, storage: "redis" },
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
break;
|
|
221
|
+
catch { }
|
|
222
|
+
this.sessionIdsMap.delete(this.getCompositeKey());
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async updateSessionIdRedis() {
|
|
226
|
+
const key = this.getRedisSessionIdsKey();
|
|
227
|
+
const compositeKey = this.getCompositeKey();
|
|
228
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Updating sessionId in Redis hash: ${key} field: ${compositeKey}`);
|
|
229
|
+
try {
|
|
230
|
+
if (this.sessionId) {
|
|
231
|
+
await this.manager.redis.hset(key, compositeKey, this.sessionId);
|
|
232
|
+
this.sessionIdsMap.set(compositeKey, this.sessionId);
|
|
287
233
|
}
|
|
234
|
+
else {
|
|
235
|
+
await this.manager.redis.hdel(key, compositeKey);
|
|
236
|
+
this.sessionIdsMap.delete(compositeKey);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
241
|
+
code: Enums_1.MagmaStreamErrorCode.NODE_SESSION_IDS_UPDATE_FAILED,
|
|
242
|
+
message: "Failed to update sessionId in Redis hash.",
|
|
243
|
+
cause: err instanceof Error ? err : undefined,
|
|
244
|
+
context: { key, compositeKey, storage: "redis-hash" },
|
|
245
|
+
});
|
|
288
246
|
}
|
|
289
247
|
}
|
|
290
248
|
/**
|
|
@@ -305,12 +263,7 @@ class Node {
|
|
|
305
263
|
"User-Id": this.manager.options.clientId,
|
|
306
264
|
"Client-Name": this.manager.options.clientName,
|
|
307
265
|
};
|
|
308
|
-
|
|
309
|
-
if (this.sessionId) {
|
|
310
|
-
headers["Session-Id"] = this.sessionId;
|
|
311
|
-
}
|
|
312
|
-
else if (this.options.enableSessionResumeOption && this.sessionIdsMap.has(compositeKey)) {
|
|
313
|
-
this.sessionId = this.sessionIdsMap.get(compositeKey) || null;
|
|
266
|
+
if (typeof this.sessionId === "string" && this.sessionId.length > 0) {
|
|
314
267
|
headers["Session-Id"] = this.sessionId;
|
|
315
268
|
}
|
|
316
269
|
this.socket = new ws_1.default(`ws${this.options.useSSL ? "s" : ""}://${this.address}/v4/websocket`, { headers });
|
|
@@ -356,9 +309,7 @@ class Node {
|
|
|
356
309
|
// Automove all players connected to that node
|
|
357
310
|
const players = this.manager.players.filter((p) => p.node == this);
|
|
358
311
|
if (players.size) {
|
|
359
|
-
|
|
360
|
-
await player.autoMoveNode();
|
|
361
|
-
}
|
|
312
|
+
await Promise.all(Array.from(players.values(), (player) => player.autoMoveNode()));
|
|
362
313
|
}
|
|
363
314
|
this.socket.close(1000, "destroy");
|
|
364
315
|
this.socket.removeAllListeners();
|
|
@@ -632,12 +583,7 @@ class Node {
|
|
|
632
583
|
player.playing = true;
|
|
633
584
|
player.paused = false;
|
|
634
585
|
this.manager.emit(Enums_1.ManagerEventTypes.TrackStart, player, track, payload);
|
|
635
|
-
|
|
636
|
-
if (!track.requester || !track.requester.id) {
|
|
637
|
-
console.log(track);
|
|
638
|
-
console.warn(`[WARN] Track requester missing for guild ${player.guildId}`, track);
|
|
639
|
-
}
|
|
640
|
-
if (AutoplayUser && track.requester && AutoplayUser.id === track.requester.id) {
|
|
586
|
+
if (track.isAutoplay) {
|
|
641
587
|
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, player, {
|
|
642
588
|
changeType: Enums_1.PlayerStateEventTypes.TrackChange,
|
|
643
589
|
details: {
|
|
@@ -82,7 +82,7 @@ class Player {
|
|
|
82
82
|
message: "Manager instance is required.",
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
|
-
this.clusterId = this.manager.options.clusterId
|
|
85
|
+
this.clusterId = this.manager.options.clusterId ?? 0;
|
|
86
86
|
// Check the player options for errors.
|
|
87
87
|
(0, playerCheck_1.default)(options);
|
|
88
88
|
this.options = {
|
|
@@ -260,21 +260,30 @@ class Player {
|
|
|
260
260
|
*/
|
|
261
261
|
async destroy(disconnect = true) {
|
|
262
262
|
this.state = Enums_1.StateTypes.Destroying;
|
|
263
|
+
if (this.dynamicLoopInterval) {
|
|
264
|
+
clearInterval(this.dynamicLoopInterval);
|
|
265
|
+
this.dynamicLoopInterval = null;
|
|
266
|
+
}
|
|
267
|
+
if (this.voiceReceiverReconnectTimeout) {
|
|
268
|
+
clearTimeout(this.voiceReceiverReconnectTimeout);
|
|
269
|
+
this.voiceReceiverReconnectTimeout = null;
|
|
270
|
+
}
|
|
271
|
+
if (this.voiceReceiverWsClient) {
|
|
272
|
+
this.voiceReceiverWsClient.removeAllListeners();
|
|
273
|
+
this.voiceReceiverWsClient.close();
|
|
274
|
+
this.voiceReceiverWsClient = null;
|
|
275
|
+
}
|
|
263
276
|
if (disconnect) {
|
|
264
|
-
await this.disconnect().catch((
|
|
265
|
-
console.warn(`[Player#destroy] Failed to disconnect player ${this.guildId}:`, err);
|
|
266
|
-
});
|
|
277
|
+
await this.disconnect().catch(() => { });
|
|
267
278
|
}
|
|
268
|
-
await this.node.rest.destroyPlayer(this.guildId).catch((
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
await this.queue.clear();
|
|
272
|
-
await this.queue.clearPrevious();
|
|
273
|
-
await this.queue.setCurrent(null);
|
|
279
|
+
await this.node.rest.destroyPlayer(this.guildId).catch(() => { });
|
|
280
|
+
await this.queue.destroy();
|
|
281
|
+
this.nowPlayingMessage = undefined;
|
|
274
282
|
this.manager.emit(Enums_1.ManagerEventTypes.PlayerDestroy, this);
|
|
275
283
|
const deleted = this.manager.players.delete(this.guildId);
|
|
276
|
-
if (this.manager.options.stateStorage.deleteInactivePlayers)
|
|
284
|
+
if (this.manager.options.stateStorage.deleteInactivePlayers) {
|
|
277
285
|
await this.manager.cleanupInactivePlayer(this.guildId);
|
|
286
|
+
}
|
|
278
287
|
return deleted;
|
|
279
288
|
}
|
|
280
289
|
/**
|
|
@@ -806,11 +815,8 @@ class Player {
|
|
|
806
815
|
if (currentPlayingTrack) {
|
|
807
816
|
await this.queue.add(currentPlayingTrack, 0);
|
|
808
817
|
}
|
|
809
|
-
await this.play(lastTrack);
|
|
810
|
-
}
|
|
811
|
-
else {
|
|
812
|
-
await this.play(lastTrack);
|
|
813
818
|
}
|
|
819
|
+
await this.play(lastTrack);
|
|
814
820
|
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
|
|
815
821
|
changeType: Enums_1.PlayerStateEventTypes.TrackChange,
|
|
816
822
|
details: {
|
|
@@ -922,7 +928,8 @@ class Player {
|
|
|
922
928
|
const sessionId = this.voiceState?.sessionId;
|
|
923
929
|
const token = this.voiceState?.event?.token;
|
|
924
930
|
const endpoint = this.voiceState?.event?.endpoint;
|
|
925
|
-
|
|
931
|
+
const channelId = this.voiceState?.channelId;
|
|
932
|
+
if (!sessionId || !token || !endpoint || !channelId) {
|
|
926
933
|
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Voice state is not properly initialized for player ${this.guildId}. The bot might not be connected to a voice channel.`);
|
|
927
934
|
throw new MagmastreamError_1.MagmaStreamError({
|
|
928
935
|
code: Enums_1.MagmaStreamErrorCode.PLAYER_STATE_INVALID,
|
|
@@ -936,7 +943,7 @@ class Player {
|
|
|
936
943
|
this.manager.players.set(this.guildId, this);
|
|
937
944
|
await this.node.rest.updatePlayer({
|
|
938
945
|
guildId: this.guildId,
|
|
939
|
-
data: { paused: this.paused, volume: this.volume, position: playerPosition, encodedTrack: currentTrack?.track, voice: { token, endpoint, sessionId } },
|
|
946
|
+
data: { paused: this.paused, volume: this.volume, position: playerPosition, encodedTrack: currentTrack?.track, voice: { token, endpoint, sessionId, channelId } },
|
|
940
947
|
});
|
|
941
948
|
await this.filters.updateFilters();
|
|
942
949
|
}
|
|
@@ -953,113 +960,6 @@ class Player {
|
|
|
953
960
|
console.error(error);
|
|
954
961
|
}
|
|
955
962
|
}
|
|
956
|
-
/**
|
|
957
|
-
* Transfers the player to a new server. If the player already exists on the new server
|
|
958
|
-
* and force is false, this method will return the existing player. Otherwise, a new player
|
|
959
|
-
* will be created and the current player will be destroyed.
|
|
960
|
-
* @param {PlayerOptions} newOptions - The new options for the player.
|
|
961
|
-
* @param {boolean} force - Whether to force the creation of a new player.
|
|
962
|
-
* @returns {Promise<Player>} - The new player instance.
|
|
963
|
-
*/
|
|
964
|
-
async switchGuild(newOptions, force = false) {
|
|
965
|
-
if (!newOptions.guildId) {
|
|
966
|
-
throw new MagmastreamError_1.MagmaStreamError({
|
|
967
|
-
code: Enums_1.MagmaStreamErrorCode.PLAYER_INVALID_CONFIG,
|
|
968
|
-
message: "guildId is required for switchGuild",
|
|
969
|
-
});
|
|
970
|
-
}
|
|
971
|
-
if (!newOptions.voiceChannelId) {
|
|
972
|
-
throw new MagmastreamError_1.MagmaStreamError({
|
|
973
|
-
code: Enums_1.MagmaStreamErrorCode.PLAYER_INVALID_CONFIG,
|
|
974
|
-
message: "voiceChannelId is required for switchGuild",
|
|
975
|
-
});
|
|
976
|
-
}
|
|
977
|
-
if (!newOptions.textChannelId) {
|
|
978
|
-
throw new MagmastreamError_1.MagmaStreamError({
|
|
979
|
-
code: Enums_1.MagmaStreamErrorCode.PLAYER_INVALID_CONFIG,
|
|
980
|
-
message: "textChannelId is required for switchGuild",
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
// Check if a player already exists for the new guild
|
|
984
|
-
let newPlayer = this.manager.getPlayer(newOptions.guildId);
|
|
985
|
-
// If the player already exists and force is false, return the existing player
|
|
986
|
-
if (newPlayer && !force)
|
|
987
|
-
return newPlayer;
|
|
988
|
-
const oldPlayerProperties = {
|
|
989
|
-
paused: this.paused,
|
|
990
|
-
selfMute: this.options.selfMute,
|
|
991
|
-
selfDeafen: this.options.selfDeafen,
|
|
992
|
-
volume: this.volume,
|
|
993
|
-
position: this.position,
|
|
994
|
-
queue: {
|
|
995
|
-
current: await this.queue.getCurrent(),
|
|
996
|
-
tracks: [...(await this.queue.getTracks())],
|
|
997
|
-
previous: [...(await this.queue.getPrevious())],
|
|
998
|
-
},
|
|
999
|
-
trackRepeat: this.trackRepeat,
|
|
1000
|
-
queueRepeat: this.queueRepeat,
|
|
1001
|
-
dynamicRepeat: this.dynamicRepeat,
|
|
1002
|
-
dynamicRepeatIntervalMs: this.dynamicRepeatIntervalMs,
|
|
1003
|
-
ClientUser: this.get("Internal_AutoplayUser"),
|
|
1004
|
-
filters: this.filters,
|
|
1005
|
-
nowPlayingMessage: this.nowPlayingMessage,
|
|
1006
|
-
isAutoplay: this.isAutoplay,
|
|
1007
|
-
applyVolumeAsFilter: this.options.applyVolumeAsFilter,
|
|
1008
|
-
};
|
|
1009
|
-
// If force is true, destroy the existing player for the new guild
|
|
1010
|
-
if (force && newPlayer) {
|
|
1011
|
-
await newPlayer.destroy();
|
|
1012
|
-
}
|
|
1013
|
-
newOptions.nodeIdentifier = newOptions.nodeIdentifier ?? this.options.nodeIdentifier;
|
|
1014
|
-
newOptions.selfDeafen = newOptions.selfDeafen ?? oldPlayerProperties.selfDeafen;
|
|
1015
|
-
newOptions.selfMute = newOptions.selfMute ?? oldPlayerProperties.selfMute;
|
|
1016
|
-
newOptions.volume = newOptions.volume ?? oldPlayerProperties.volume;
|
|
1017
|
-
newOptions.applyVolumeAsFilter = newOptions.applyVolumeAsFilter ?? oldPlayerProperties.applyVolumeAsFilter;
|
|
1018
|
-
// Deep clone the current player
|
|
1019
|
-
const clonedPlayer = this.manager.create(newOptions);
|
|
1020
|
-
// Connect the cloned player to the new voice channel
|
|
1021
|
-
clonedPlayer.connect();
|
|
1022
|
-
// Update the player's state on the Lavalink node
|
|
1023
|
-
await clonedPlayer.node.rest.updatePlayer({
|
|
1024
|
-
guildId: clonedPlayer.guildId,
|
|
1025
|
-
data: {
|
|
1026
|
-
paused: oldPlayerProperties.paused,
|
|
1027
|
-
volume: oldPlayerProperties.volume,
|
|
1028
|
-
position: oldPlayerProperties.position,
|
|
1029
|
-
encodedTrack: oldPlayerProperties.queue.current?.track,
|
|
1030
|
-
},
|
|
1031
|
-
});
|
|
1032
|
-
await clonedPlayer.queue.setCurrent(oldPlayerProperties.queue.current);
|
|
1033
|
-
await clonedPlayer.queue.addPrevious(oldPlayerProperties.queue.previous);
|
|
1034
|
-
await clonedPlayer.queue.add(oldPlayerProperties.queue.tracks);
|
|
1035
|
-
clonedPlayer.filters = oldPlayerProperties.filters;
|
|
1036
|
-
clonedPlayer.isAutoplay = oldPlayerProperties.isAutoplay;
|
|
1037
|
-
clonedPlayer.nowPlayingMessage = oldPlayerProperties.nowPlayingMessage;
|
|
1038
|
-
clonedPlayer.trackRepeat = oldPlayerProperties.trackRepeat;
|
|
1039
|
-
clonedPlayer.queueRepeat = oldPlayerProperties.queueRepeat;
|
|
1040
|
-
clonedPlayer.dynamicRepeat = oldPlayerProperties.dynamicRepeat;
|
|
1041
|
-
clonedPlayer.dynamicRepeatIntervalMs = oldPlayerProperties.dynamicRepeatIntervalMs;
|
|
1042
|
-
clonedPlayer.set("Internal_AutoplayUser", oldPlayerProperties.ClientUser);
|
|
1043
|
-
clonedPlayer.paused = oldPlayerProperties.paused;
|
|
1044
|
-
// Update filters for the cloned player
|
|
1045
|
-
await clonedPlayer.filters.updateFilters();
|
|
1046
|
-
// Debug information
|
|
1047
|
-
const debugInfo = {
|
|
1048
|
-
success: true,
|
|
1049
|
-
message: `Transferred ${await clonedPlayer.queue.size()} tracks successfully to <#${newOptions.voiceChannelId}> bound to <#${newOptions.textChannelId}>.`,
|
|
1050
|
-
player: {
|
|
1051
|
-
guildId: clonedPlayer.guildId,
|
|
1052
|
-
voiceChannelId: clonedPlayer.voiceChannelId,
|
|
1053
|
-
textChannelId: clonedPlayer.textChannelId,
|
|
1054
|
-
volume: clonedPlayer.volume,
|
|
1055
|
-
playing: clonedPlayer.playing,
|
|
1056
|
-
queueSize: clonedPlayer.queue.size,
|
|
1057
|
-
},
|
|
1058
|
-
};
|
|
1059
|
-
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[PLAYER] Transferred player to a new server: ${Utils_1.JSONUtils.safe(debugInfo, 2)}.`);
|
|
1060
|
-
// Return the cloned player
|
|
1061
|
-
return clonedPlayer;
|
|
1062
|
-
}
|
|
1063
963
|
/**
|
|
1064
964
|
* Retrieves the data associated with the player.
|
|
1065
965
|
* @returns {Record<string, unknown>} - The data associated with the player.
|
|
@@ -1074,6 +974,13 @@ class Player {
|
|
|
1074
974
|
getDynamicLoopIntervalPublic() {
|
|
1075
975
|
return this.dynamicLoopInterval;
|
|
1076
976
|
}
|
|
977
|
+
/**
|
|
978
|
+
* Retrieves the data associated with the player.
|
|
979
|
+
* @returns {Record<string, unknown>} - The data associated with the player.
|
|
980
|
+
*/
|
|
981
|
+
getSerializableData() {
|
|
982
|
+
return { ...this.data };
|
|
983
|
+
}
|
|
1077
984
|
/**
|
|
1078
985
|
* Retrieves the current lyrics for the playing track.
|
|
1079
986
|
* @param skipTrackSource - Indicates whether to skip the track source when fetching lyrics.
|
package/dist/structures/Rest.js
CHANGED
|
@@ -104,35 +104,55 @@ class Rest {
|
|
|
104
104
|
},
|
|
105
105
|
data: body,
|
|
106
106
|
timeout: this.node.options.apiRequestTimeoutMs,
|
|
107
|
+
validateStatus: () => true,
|
|
107
108
|
};
|
|
109
|
+
let response;
|
|
108
110
|
try {
|
|
109
|
-
|
|
110
|
-
return response.data;
|
|
111
|
+
response = await (0, axios_1.default)(config);
|
|
111
112
|
}
|
|
112
113
|
catch (err) {
|
|
113
|
-
const
|
|
114
|
-
if (!error.response) {
|
|
115
|
-
throw new MagmastreamError_1.MagmaStreamError({
|
|
116
|
-
code: Enums_1.MagmaStreamErrorCode.REST_REQUEST_FAILED,
|
|
117
|
-
message: `No response from node ${this.node.options.identifier}: ${error.message}`,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
const data = error.response.data;
|
|
121
|
-
if (data?.message === "Guild not found") {
|
|
122
|
-
return [];
|
|
123
|
-
}
|
|
124
|
-
if (error.response.status === 401) {
|
|
125
|
-
throw new MagmastreamError_1.MagmaStreamError({
|
|
126
|
-
code: Enums_1.MagmaStreamErrorCode.REST_UNAUTHORIZED,
|
|
127
|
-
message: `Unauthorized access to node ${this.node.options.identifier}`,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
const dataMessage = typeof data === "string" ? data : data?.message ? data.message : Utils_1.JSONUtils.safe(data, 2);
|
|
114
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
131
115
|
throw new MagmastreamError_1.MagmaStreamError({
|
|
132
116
|
code: Enums_1.MagmaStreamErrorCode.REST_REQUEST_FAILED,
|
|
133
|
-
message: `
|
|
117
|
+
message: `No response from node ${this.node.options.identifier}: ${message}`,
|
|
134
118
|
});
|
|
135
119
|
}
|
|
120
|
+
const { status, data } = response;
|
|
121
|
+
if (status >= 200 && status < 300) {
|
|
122
|
+
return data;
|
|
123
|
+
}
|
|
124
|
+
// Lavalink sometimes returns "Guild not found" for inactive players
|
|
125
|
+
if (status === 404 && data?.message === "Guild not found") {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
if (status === 401) {
|
|
129
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
130
|
+
code: Enums_1.MagmaStreamErrorCode.REST_UNAUTHORIZED,
|
|
131
|
+
message: `Unauthorized access to node ${this.node.options.identifier}`,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (status >= 400 && status < 500) {
|
|
135
|
+
const message = typeof data === "string"
|
|
136
|
+
? data
|
|
137
|
+
: typeof data === "object" && data !== null && "message" in data && typeof data.message === "string"
|
|
138
|
+
? data.message
|
|
139
|
+
: "Unknown client error";
|
|
140
|
+
return {
|
|
141
|
+
status,
|
|
142
|
+
error: true,
|
|
143
|
+
message,
|
|
144
|
+
data,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const safeMessage = typeof data === "string"
|
|
148
|
+
? data
|
|
149
|
+
: typeof data === "object" && data !== null && "message" in data && typeof data.message === "string"
|
|
150
|
+
? data.message
|
|
151
|
+
: Utils_1.JSONUtils.safe(data, 2);
|
|
152
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
153
|
+
code: Enums_1.MagmaStreamErrorCode.REST_REQUEST_FAILED,
|
|
154
|
+
message: `Request to node ${this.node.options.identifier} failed (${status}): ${safeMessage}`,
|
|
155
|
+
});
|
|
136
156
|
}
|
|
137
157
|
/**
|
|
138
158
|
* Sends a GET request to the specified endpoint and returns the response data.
|