magmastream 2.9.0-dev.37 → 2.9.0-dev.39
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 +20 -11
- package/dist/structures/Manager.js +40 -33
- package/dist/structures/Node.js +113 -84
- package/dist/structures/Player.js +1 -0
- package/dist/structures/Utils.js +48 -16
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1378,7 +1378,7 @@ interface ManagerEvents {
|
|
|
1378
1378
|
[ManagerEventTypes.PlayerCreate]: [player: Player];
|
|
1379
1379
|
[ManagerEventTypes.PlayerDestroy]: [player: Player];
|
|
1380
1380
|
[ManagerEventTypes.PlayerDisconnect]: [player: Player, oldChannel: string];
|
|
1381
|
-
[ManagerEventTypes.PlayerMove]: [player: Player,
|
|
1381
|
+
[ManagerEventTypes.PlayerMove]: [player: Player, oldChannel: string, newChannel: string];
|
|
1382
1382
|
[ManagerEventTypes.PlayerRestored]: [player: Player, node: Node];
|
|
1383
1383
|
[ManagerEventTypes.PlayerStateUpdate]: [oldPlayer: Player, newPlayer: Player, changeType: PlayerStateUpdateEvent];
|
|
1384
1384
|
[ManagerEventTypes.QueueEnd]: [player: Player, track: Track, payload: TrackEndEvent];
|
|
@@ -1949,15 +1949,21 @@ declare class Node {
|
|
|
1949
1949
|
isNodeLink: boolean;
|
|
1950
1950
|
private reconnectTimeout?;
|
|
1951
1951
|
private reconnectAttempts;
|
|
1952
|
+
private redisPrefix?;
|
|
1953
|
+
private sessionIdsFilePath?;
|
|
1954
|
+
private sessionIdsMap;
|
|
1952
1955
|
/**
|
|
1953
1956
|
* Creates an instance of Node.
|
|
1954
1957
|
* @param manager - The manager for the node.
|
|
1955
1958
|
* @param options - The options for the node.
|
|
1956
1959
|
*/
|
|
1957
1960
|
constructor(manager: Manager, options: NodeOptions);
|
|
1958
|
-
/**
|
|
1961
|
+
/**
|
|
1962
|
+
* Checks if the Node is currently connected.
|
|
1963
|
+
* This method returns true if the Node has an active WebSocket connection, indicating it is ready to receive and process commands.
|
|
1964
|
+
*/
|
|
1959
1965
|
get connected(): boolean;
|
|
1960
|
-
/** Returns the address for this node. */
|
|
1966
|
+
/** Returns the full address for this node, including the host and port. */
|
|
1961
1967
|
get address(): string;
|
|
1962
1968
|
/**
|
|
1963
1969
|
* Creates the sessionIds.json file if it doesn't exist. This file is used to
|
|
@@ -1973,7 +1979,7 @@ declare class Node {
|
|
|
1973
1979
|
* of the node identifier and cluster ID. This allows multiple clusters to
|
|
1974
1980
|
* be used with the same node identifier.
|
|
1975
1981
|
*/
|
|
1976
|
-
loadSessionIds(): void
|
|
1982
|
+
loadSessionIds(): Promise<void>;
|
|
1977
1983
|
/**
|
|
1978
1984
|
* Updates the session ID in the sessionIds.json file.
|
|
1979
1985
|
*
|
|
@@ -1985,7 +1991,7 @@ declare class Node {
|
|
|
1985
1991
|
* of the node identifier and cluster ID. This allows multiple clusters to
|
|
1986
1992
|
* be used with the same node identifier.
|
|
1987
1993
|
*/
|
|
1988
|
-
updateSessionId(): void
|
|
1994
|
+
updateSessionId(): Promise<void>;
|
|
1989
1995
|
/**
|
|
1990
1996
|
* Connects to the Node.
|
|
1991
1997
|
*
|
|
@@ -1995,7 +2001,7 @@ declare class Node {
|
|
|
1995
2001
|
* If the node has no session ID but the `enableSessionResumeOption` option is true, it will use the session ID
|
|
1996
2002
|
* stored in the sessionIds.json file if it exists.
|
|
1997
2003
|
*/
|
|
1998
|
-
connect(): void
|
|
2004
|
+
connect(): Promise<void>;
|
|
1999
2005
|
/**
|
|
2000
2006
|
* Destroys the node and cleans up associated resources.
|
|
2001
2007
|
*
|
|
@@ -2323,7 +2329,7 @@ declare class Manager extends EventEmitter {
|
|
|
2323
2329
|
readonly options: ManagerOptions;
|
|
2324
2330
|
initiated: boolean;
|
|
2325
2331
|
redis?: Redis;
|
|
2326
|
-
private _send
|
|
2332
|
+
private _send;
|
|
2327
2333
|
private loadedPlugins;
|
|
2328
2334
|
/**
|
|
2329
2335
|
* Initiates the Manager class.
|
|
@@ -2347,7 +2353,7 @@ declare class Manager extends EventEmitter {
|
|
|
2347
2353
|
* @param clusterId - The cluster ID which runs the current process (required).
|
|
2348
2354
|
* @returns The manager instance.
|
|
2349
2355
|
*/
|
|
2350
|
-
init(options?: ManagerInitOptions): this
|
|
2356
|
+
init(options?: ManagerInitOptions): Promise<this>;
|
|
2351
2357
|
/**
|
|
2352
2358
|
* Searches the enabled sources based off the URL or the `source` property.
|
|
2353
2359
|
* @param query
|
|
@@ -2423,7 +2429,7 @@ declare class Manager extends EventEmitter {
|
|
|
2423
2429
|
*/
|
|
2424
2430
|
decodeTrack(track: string): Promise<TrackData>;
|
|
2425
2431
|
/**
|
|
2426
|
-
* Saves player states
|
|
2432
|
+
* Saves player states.
|
|
2427
2433
|
* @param {string} guildId - The guild ID of the player to save
|
|
2428
2434
|
*/
|
|
2429
2435
|
savePlayerState(guildId: string): Promise<void>;
|
|
@@ -2522,8 +2528,8 @@ declare class Manager extends EventEmitter {
|
|
|
2522
2528
|
*/
|
|
2523
2529
|
cleanupInactivePlayers(): Promise<void>;
|
|
2524
2530
|
/**
|
|
2525
|
-
* Cleans up an inactive player by removing its state
|
|
2526
|
-
* This is done to prevent stale state
|
|
2531
|
+
* Cleans up an inactive player by removing its state data.
|
|
2532
|
+
* This is done to prevent stale state data from accumulating.
|
|
2527
2533
|
* @param guildId The guild ID of the player to clean up.
|
|
2528
2534
|
*/
|
|
2529
2535
|
cleanupInactivePlayer(guildId: string): Promise<void>;
|
|
@@ -3396,6 +3402,9 @@ declare abstract class AutoPlayUtils {
|
|
|
3396
3402
|
* @returns A single resolved {@link Track} object if found, or `null` if the search fails or returns no results.
|
|
3397
3403
|
*/
|
|
3398
3404
|
private static resolveFirstTrackFromQuery;
|
|
3405
|
+
private static isPlaylistRawData;
|
|
3406
|
+
private static isTrackData;
|
|
3407
|
+
private static isTrackDataArray;
|
|
3399
3408
|
static buildTracksFromResponse<T>(recommendedResult: LavalinkResponse, requester?: T): Track[];
|
|
3400
3409
|
}
|
|
3401
3410
|
/** Gets or extends structures to extend the built in, or already extended, classes to add more functionality. */
|
|
@@ -87,6 +87,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
87
87
|
deleteInactivePlayers: options.stateStorage?.deleteInactivePlayers ?? true,
|
|
88
88
|
},
|
|
89
89
|
autoPlaySearchPlatforms: options.autoPlaySearchPlatforms ?? [Enums_1.AutoPlayPlatform.YouTube],
|
|
90
|
+
send: this._send,
|
|
90
91
|
};
|
|
91
92
|
Utils_1.AutoPlayUtils.init(this);
|
|
92
93
|
if (this.options.nodes) {
|
|
@@ -127,7 +128,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
127
128
|
* @param clusterId - The cluster ID which runs the current process (required).
|
|
128
129
|
* @returns The manager instance.
|
|
129
130
|
*/
|
|
130
|
-
init(options = {}) {
|
|
131
|
+
async init(options = {}) {
|
|
131
132
|
if (this.initiated) {
|
|
132
133
|
return this;
|
|
133
134
|
}
|
|
@@ -145,14 +146,6 @@ class Manager extends events_1.EventEmitter {
|
|
|
145
146
|
else {
|
|
146
147
|
this.options.clusterId = clusterId;
|
|
147
148
|
}
|
|
148
|
-
for (const node of this.nodes.values()) {
|
|
149
|
-
try {
|
|
150
|
-
node.connect();
|
|
151
|
-
}
|
|
152
|
-
catch (err) {
|
|
153
|
-
this.emit(Enums_1.ManagerEventTypes.NodeError, node, err);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
149
|
if (this.options.stateStorage.type === Enums_1.StateStorageType.Redis) {
|
|
157
150
|
const config = this.options.stateStorage.redisConfig;
|
|
158
151
|
this.redis = new ioredis_1.default({
|
|
@@ -162,6 +155,14 @@ class Manager extends events_1.EventEmitter {
|
|
|
162
155
|
db: config.db ?? 0,
|
|
163
156
|
});
|
|
164
157
|
}
|
|
158
|
+
for (const node of this.nodes.values()) {
|
|
159
|
+
try {
|
|
160
|
+
await node.connect();
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
this.emit(Enums_1.ManagerEventTypes.NodeError, node, err);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
165
166
|
this.loadPlugins();
|
|
166
167
|
this.initiated = true;
|
|
167
168
|
return this;
|
|
@@ -397,7 +398,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
397
398
|
return res[0];
|
|
398
399
|
}
|
|
399
400
|
/**
|
|
400
|
-
* Saves player states
|
|
401
|
+
* Saves player states.
|
|
401
402
|
* @param {string} guildId - The guild ID of the player to save
|
|
402
403
|
*/
|
|
403
404
|
async savePlayerState(guildId) {
|
|
@@ -1193,8 +1194,8 @@ class Manager extends events_1.EventEmitter {
|
|
|
1193
1194
|
}
|
|
1194
1195
|
}
|
|
1195
1196
|
/**
|
|
1196
|
-
* Cleans up an inactive player by removing its state
|
|
1197
|
-
* This is done to prevent stale state
|
|
1197
|
+
* Cleans up an inactive player by removing its state data.
|
|
1198
|
+
* This is done to prevent stale state data from accumulating.
|
|
1198
1199
|
* @param guildId The guild ID of the player to clean up.
|
|
1199
1200
|
*/
|
|
1200
1201
|
async cleanupInactivePlayer(guildId) {
|
|
@@ -1302,30 +1303,32 @@ class Manager extends events_1.EventEmitter {
|
|
|
1302
1303
|
const prefix = this.options.stateStorage.redisConfig.prefix?.endsWith(":")
|
|
1303
1304
|
? this.options.stateStorage.redisConfig.prefix
|
|
1304
1305
|
: this.options.stateStorage.redisConfig.prefix ?? "magmastream:";
|
|
1305
|
-
const
|
|
1306
|
+
const patterns = [`${prefix}playerstore:*`, `${prefix}queue:*`];
|
|
1306
1307
|
try {
|
|
1307
|
-
const
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1308
|
+
for (const pattern of patterns) {
|
|
1309
|
+
const stream = this.redis.scanStream({
|
|
1310
|
+
match: pattern,
|
|
1311
|
+
count: 100,
|
|
1312
|
+
});
|
|
1313
|
+
let totalDeleted = 0;
|
|
1314
|
+
stream.on("data", async (keys) => {
|
|
1315
|
+
if (keys.length) {
|
|
1316
|
+
const pipeline = this.redis.pipeline();
|
|
1317
|
+
keys.forEach((key) => pipeline.unlink(key));
|
|
1318
|
+
await pipeline.exec();
|
|
1319
|
+
totalDeleted += keys.length;
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
stream.on("end", () => {
|
|
1323
|
+
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Cleared ${totalDeleted} Redis keys (pattern: ${pattern})`);
|
|
1324
|
+
});
|
|
1325
|
+
stream.on("error", (err) => {
|
|
1326
|
+
console.error(`[MANAGER] Error during Redis SCAN stream (${pattern}):`, err);
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1326
1329
|
}
|
|
1327
1330
|
catch (err) {
|
|
1328
|
-
console.error("[MANAGER] Failed to clear Redis
|
|
1331
|
+
console.error("[MANAGER] Failed to clear Redis keys:", err);
|
|
1329
1332
|
}
|
|
1330
1333
|
break;
|
|
1331
1334
|
}
|
|
@@ -1394,6 +1397,10 @@ class Manager extends events_1.EventEmitter {
|
|
|
1394
1397
|
return this.options.useNode === Enums_1.UseNodeOptions.LeastLoad ? this.leastLoadNode.first() : this.leastPlayersNode.first();
|
|
1395
1398
|
}
|
|
1396
1399
|
send(packet) {
|
|
1400
|
+
if (!this._send) {
|
|
1401
|
+
console.warn("[Manager.send] _send is not defined! Packet will not be sent.");
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1397
1404
|
return this._send(packet);
|
|
1398
1405
|
}
|
|
1399
1406
|
sendPacket(packet) {
|
package/dist/structures/Node.js
CHANGED
|
@@ -10,12 +10,6 @@ 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 validSponsorBlocks = Object.values(Enums_1.SponsorBlockSegment).map((v) => v.toLowerCase());
|
|
13
|
-
const sessionIdsFilePath = path_1.default.join(process.cwd(), "magmastream", "dist", "sessionData", "sessionIds.json");
|
|
14
|
-
let sessionIdsMap = new Map();
|
|
15
|
-
const configDir = path_1.default.dirname(sessionIdsFilePath);
|
|
16
|
-
if (!fs_1.default.existsSync(configDir)) {
|
|
17
|
-
fs_1.default.mkdirSync(configDir, { recursive: true });
|
|
18
|
-
}
|
|
19
13
|
class Node {
|
|
20
14
|
manager;
|
|
21
15
|
options;
|
|
@@ -35,6 +29,9 @@ class Node {
|
|
|
35
29
|
isNodeLink = false;
|
|
36
30
|
reconnectTimeout;
|
|
37
31
|
reconnectAttempts = 1;
|
|
32
|
+
redisPrefix;
|
|
33
|
+
sessionIdsFilePath;
|
|
34
|
+
sessionIdsMap = new Map();
|
|
38
35
|
/**
|
|
39
36
|
* Creates an instance of Node.
|
|
40
37
|
* @param manager - The manager for the node.
|
|
@@ -91,18 +88,33 @@ class Node {
|
|
|
91
88
|
this.manager.nodes.set(this.options.identifier, this);
|
|
92
89
|
this.manager.emit(Enums_1.ManagerEventTypes.NodeCreate, this);
|
|
93
90
|
this.rest = new Rest_1.Rest(this, this.manager);
|
|
94
|
-
this.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
switch (this.manager.options.stateStorage.type) {
|
|
92
|
+
case Enums_1.StateStorageType.JSON:
|
|
93
|
+
this.sessionIdsFilePath = path_1.default.join(process.cwd(), "magmastream", "dist", "sessionData", "sessionIds.json");
|
|
94
|
+
const configDir = path_1.default.dirname(this.sessionIdsFilePath);
|
|
95
|
+
if (!fs_1.default.existsSync(configDir)) {
|
|
96
|
+
fs_1.default.mkdirSync(configDir, { recursive: true });
|
|
97
|
+
}
|
|
98
|
+
this.createSessionIdsFile();
|
|
99
|
+
this.createReadmeFile();
|
|
100
|
+
break;
|
|
101
|
+
case Enums_1.StateStorageType.Redis:
|
|
102
|
+
this.redisPrefix = this.manager.options.stateStorage.redisConfig.prefix?.endsWith(":")
|
|
103
|
+
? this.manager.options.stateStorage.redisConfig.prefix
|
|
104
|
+
: this.manager.options.stateStorage.redisConfig.prefix ?? "magmastream:";
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
98
107
|
}
|
|
99
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Checks if the Node is currently connected.
|
|
110
|
+
* This method returns true if the Node has an active WebSocket connection, indicating it is ready to receive and process commands.
|
|
111
|
+
*/
|
|
100
112
|
get connected() {
|
|
101
113
|
if (!this.socket)
|
|
102
114
|
return false;
|
|
103
115
|
return this.socket.readyState === ws_1.default.OPEN;
|
|
104
116
|
}
|
|
105
|
-
/** Returns the address for this node. */
|
|
117
|
+
/** Returns the full address for this node, including the host and port. */
|
|
106
118
|
get address() {
|
|
107
119
|
return `${this.options.host}:${this.options.port}`;
|
|
108
120
|
}
|
|
@@ -112,11 +124,9 @@ class Node {
|
|
|
112
124
|
* the node when resuming a session.
|
|
113
125
|
*/
|
|
114
126
|
createSessionIdsFile() {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
// Create the file with an empty object as the content
|
|
119
|
-
fs_1.default.writeFileSync(sessionIdsFilePath, JSON.stringify({}), "utf-8");
|
|
127
|
+
if (!fs_1.default.existsSync(this.sessionIdsFilePath)) {
|
|
128
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Creating sessionId file at: ${this.sessionIdsFilePath}`);
|
|
129
|
+
fs_1.default.writeFileSync(this.sessionIdsFilePath, JSON.stringify({}), "utf-8");
|
|
120
130
|
}
|
|
121
131
|
}
|
|
122
132
|
/**
|
|
@@ -127,21 +137,48 @@ class Node {
|
|
|
127
137
|
* of the node identifier and cluster ID. This allows multiple clusters to
|
|
128
138
|
* be used with the same node identifier.
|
|
129
139
|
*/
|
|
130
|
-
loadSessionIds() {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
this.sessionId = sessionIdsMap.get(compositeKey);
|
|
140
|
+
async loadSessionIds() {
|
|
141
|
+
switch (this.manager.options.stateStorage.type) {
|
|
142
|
+
case Enums_1.StateStorageType.JSON: {
|
|
143
|
+
if (fs_1.default.existsSync(this.sessionIdsFilePath)) {
|
|
144
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Loading sessionIds from file: ${this.sessionIdsFilePath}`);
|
|
145
|
+
const sessionIdsData = fs_1.default.readFileSync(this.sessionIdsFilePath, "utf-8");
|
|
146
|
+
this.sessionIdsMap = new Map(Object.entries(JSON.parse(sessionIdsData)));
|
|
147
|
+
const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
|
|
148
|
+
if (this.sessionIdsMap.has(compositeKey)) {
|
|
149
|
+
this.sessionId = this.sessionIdsMap.get(compositeKey);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
144
153
|
}
|
|
154
|
+
case Enums_1.StateStorageType.Redis:
|
|
155
|
+
const key = `${this.redisPrefix}node:sessionIds`;
|
|
156
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Loading sessionIds from Redis key: ${key}`);
|
|
157
|
+
const currentRaw = await this.manager.redis.get(key);
|
|
158
|
+
if (currentRaw) {
|
|
159
|
+
try {
|
|
160
|
+
const sessionIds = JSON.parse(currentRaw);
|
|
161
|
+
if (typeof sessionIds !== "object" || Array.isArray(sessionIds)) {
|
|
162
|
+
throw new Error("[NODE] loadSessionIds invalid data type from Redis.");
|
|
163
|
+
}
|
|
164
|
+
this.sessionIdsMap = new Map(Object.entries(sessionIds));
|
|
165
|
+
const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
|
|
166
|
+
if (this.sessionIdsMap.has(compositeKey)) {
|
|
167
|
+
this.sessionId = this.sessionIdsMap.get(compositeKey) || null;
|
|
168
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Restored sessionId for ${compositeKey}: ${this.sessionId}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Failed to parse Redis sessionIds: ${err.message}`);
|
|
173
|
+
this.sessionIdsMap = new Map();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] No sessionIds found in Redis — creating new key.`);
|
|
178
|
+
await this.manager.redis.set(key, JSON.stringify({}));
|
|
179
|
+
this.sessionIdsMap = new Map();
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
145
182
|
}
|
|
146
183
|
}
|
|
147
184
|
/**
|
|
@@ -155,15 +192,43 @@ class Node {
|
|
|
155
192
|
* of the node identifier and cluster ID. This allows multiple clusters to
|
|
156
193
|
* be used with the same node identifier.
|
|
157
194
|
*/
|
|
158
|
-
updateSessionId() {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
195
|
+
async updateSessionId() {
|
|
196
|
+
switch (this.manager.options.stateStorage.type) {
|
|
197
|
+
case Enums_1.StateStorageType.JSON: {
|
|
198
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Updating sessionIds to file: ${this.sessionIdsFilePath}`);
|
|
199
|
+
const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
|
|
200
|
+
this.sessionIdsMap.set(compositeKey, this.sessionId);
|
|
201
|
+
fs_1.default.writeFileSync(this.sessionIdsFilePath, JSON.stringify(Object.fromEntries(this.sessionIdsMap)));
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
case Enums_1.StateStorageType.Redis: {
|
|
205
|
+
const key = `${this.redisPrefix}node:sessionIds`;
|
|
206
|
+
const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
|
|
207
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Updating sessionIds in Redis key: ${key}`);
|
|
208
|
+
const currentRaw = await this.manager.redis.get(key);
|
|
209
|
+
let sessionIds;
|
|
210
|
+
if (currentRaw) {
|
|
211
|
+
try {
|
|
212
|
+
sessionIds = JSON.parse(currentRaw);
|
|
213
|
+
if (typeof sessionIds !== "object" || Array.isArray(sessionIds)) {
|
|
214
|
+
throw new Error("Invalid data type in Redis");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Corrupted Redis sessionIds, reinitializing: ${err.message}`);
|
|
219
|
+
sessionIds = {};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Redis key not found — creating new sessionIds key.`);
|
|
224
|
+
sessionIds = {};
|
|
225
|
+
}
|
|
226
|
+
sessionIds[compositeKey] = this.sessionId;
|
|
227
|
+
this.sessionIdsMap = new Map(Object.entries(sessionIds));
|
|
228
|
+
await this.manager.redis.set(key, JSON.stringify(sessionIds));
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
167
232
|
}
|
|
168
233
|
/**
|
|
169
234
|
* Connects to the Node.
|
|
@@ -174,7 +239,8 @@ class Node {
|
|
|
174
239
|
* If the node has no session ID but the `enableSessionResumeOption` option is true, it will use the session ID
|
|
175
240
|
* stored in the sessionIds.json file if it exists.
|
|
176
241
|
*/
|
|
177
|
-
connect() {
|
|
242
|
+
async connect() {
|
|
243
|
+
await this.loadSessionIds();
|
|
178
244
|
if (this.connected)
|
|
179
245
|
return;
|
|
180
246
|
const headers = {
|
|
@@ -186,8 +252,8 @@ class Node {
|
|
|
186
252
|
if (this.sessionId) {
|
|
187
253
|
headers["Session-Id"] = this.sessionId;
|
|
188
254
|
}
|
|
189
|
-
else if (this.options.enableSessionResumeOption && sessionIdsMap.has(compositeKey)) {
|
|
190
|
-
this.sessionId = sessionIdsMap.get(compositeKey) || null;
|
|
255
|
+
else if (this.options.enableSessionResumeOption && this.sessionIdsMap.has(compositeKey)) {
|
|
256
|
+
this.sessionId = this.sessionIdsMap.get(compositeKey) || null;
|
|
191
257
|
headers["Session-Id"] = this.sessionId;
|
|
192
258
|
}
|
|
193
259
|
this.socket = new ws_1.default(`ws${this.options.useSSL ? "s" : ""}://${this.address}/v4/websocket`, { headers });
|
|
@@ -222,7 +288,6 @@ class Node {
|
|
|
222
288
|
async destroy() {
|
|
223
289
|
if (!this.connected)
|
|
224
290
|
return;
|
|
225
|
-
// Emit a debug event indicating that the node is being destroyed
|
|
226
291
|
const debugInfo = {
|
|
227
292
|
connected: this.connected,
|
|
228
293
|
identifier: this.options.identifier,
|
|
@@ -238,16 +303,11 @@ class Node {
|
|
|
238
303
|
await player.autoMoveNode();
|
|
239
304
|
}
|
|
240
305
|
}
|
|
241
|
-
// Close the WebSocket connection
|
|
242
306
|
this.socket.close(1000, "destroy");
|
|
243
|
-
// Remove all event listeners on the WebSocket
|
|
244
307
|
this.socket.removeAllListeners();
|
|
245
|
-
// Clear the reconnect timeout
|
|
246
308
|
this.reconnectAttempts = 1;
|
|
247
309
|
clearTimeout(this.reconnectTimeout);
|
|
248
|
-
// Emit a "nodeDestroy" event with the node as the argument
|
|
249
310
|
this.manager.emit(Enums_1.ManagerEventTypes.NodeDestroy, this);
|
|
250
|
-
// Remove the node from the manager
|
|
251
311
|
this.manager.nodes.delete(this.options.identifier);
|
|
252
312
|
}
|
|
253
313
|
/**
|
|
@@ -267,7 +327,6 @@ class Node {
|
|
|
267
327
|
* @emits {nodeDestroy} - Emits a nodeDestroy event if the maximum number of retry attempts is reached.
|
|
268
328
|
*/
|
|
269
329
|
async reconnect() {
|
|
270
|
-
// Collect debug information regarding the current state of the node
|
|
271
330
|
const debugInfo = {
|
|
272
331
|
identifier: this.options.identifier,
|
|
273
332
|
connected: this.connected,
|
|
@@ -275,24 +334,17 @@ class Node {
|
|
|
275
334
|
maxRetryAttempts: this.options.maxRetryAttempts,
|
|
276
335
|
retryDelayMs: this.options.retryDelayMs,
|
|
277
336
|
};
|
|
278
|
-
// Emit a debug event indicating the node is attempting to reconnect
|
|
279
337
|
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Reconnecting node: ${JSON.stringify(debugInfo)}`);
|
|
280
|
-
// Schedule the reconnection attempt after the specified retry delay
|
|
281
338
|
this.reconnectTimeout = setTimeout(async () => {
|
|
282
|
-
// Check if the maximum number of retry attempts has been reached
|
|
283
339
|
if (this.reconnectAttempts >= this.options.maxRetryAttempts) {
|
|
284
|
-
// Emit an error event and destroy the node if retries are exhausted
|
|
285
340
|
const error = new Error(`Unable to connect after ${this.options.maxRetryAttempts} attempts.`);
|
|
286
341
|
this.manager.emit(Enums_1.ManagerEventTypes.NodeError, this, error);
|
|
287
342
|
return await this.destroy();
|
|
288
343
|
}
|
|
289
|
-
// Remove all listeners from the current WebSocket and reset it
|
|
290
344
|
this.socket?.removeAllListeners();
|
|
291
345
|
this.socket = null;
|
|
292
|
-
// Emit a nodeReconnect event and attempt to connect again
|
|
293
346
|
this.manager.emit(Enums_1.ManagerEventTypes.NodeReconnect, this);
|
|
294
347
|
this.connect();
|
|
295
|
-
// Increment the reconnect attempts counter
|
|
296
348
|
this.reconnectAttempts++;
|
|
297
349
|
}, this.options.retryDelayMs);
|
|
298
350
|
}
|
|
@@ -313,17 +365,13 @@ class Node {
|
|
|
313
365
|
* with the node as the argument.
|
|
314
366
|
*/
|
|
315
367
|
open() {
|
|
316
|
-
// Clear any existing reconnect timeouts
|
|
317
368
|
if (this.reconnectTimeout)
|
|
318
369
|
clearTimeout(this.reconnectTimeout);
|
|
319
|
-
// Collect debug information regarding the current state of the node
|
|
320
370
|
const debugInfo = {
|
|
321
371
|
identifier: this.options.identifier,
|
|
322
372
|
connected: this.connected,
|
|
323
373
|
};
|
|
324
|
-
// Emit a debug event indicating the node is connected
|
|
325
374
|
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Connected node: ${JSON.stringify(debugInfo)}`);
|
|
326
|
-
// Emit a "nodeConnect" event with the node as the argument
|
|
327
375
|
this.manager.emit(Enums_1.ManagerEventTypes.NodeConnect, this);
|
|
328
376
|
}
|
|
329
377
|
/**
|
|
@@ -345,18 +393,14 @@ class Node {
|
|
|
345
393
|
code,
|
|
346
394
|
reason,
|
|
347
395
|
};
|
|
348
|
-
// Emit a "nodeDisconnect" event with the node and the close event as arguments
|
|
349
396
|
this.manager.emit(Enums_1.ManagerEventTypes.NodeDisconnect, this, { code, reason });
|
|
350
|
-
// Emit a debug event indicating the node is disconnected
|
|
351
397
|
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Disconnected node: ${JSON.stringify(debugInfo)}`);
|
|
352
|
-
// Try moving all players connected to that node to a useable one
|
|
353
398
|
if (this.manager.useableNode) {
|
|
354
399
|
const players = this.manager.players.filter((p) => p.node.options.identifier == this.options.identifier);
|
|
355
400
|
if (players.size) {
|
|
356
401
|
await Promise.all(Array.from(players.values(), (player) => player.autoMoveNode()));
|
|
357
402
|
}
|
|
358
403
|
}
|
|
359
|
-
// If the close event was not initiated by the user, attempt to reconnect
|
|
360
404
|
if (code !== 1000 || reason !== "destroy")
|
|
361
405
|
await this.reconnect();
|
|
362
406
|
}
|
|
@@ -371,14 +415,11 @@ class Node {
|
|
|
371
415
|
error(error) {
|
|
372
416
|
if (!error)
|
|
373
417
|
return;
|
|
374
|
-
// Collect debug information regarding the error
|
|
375
418
|
const debugInfo = {
|
|
376
419
|
identifier: this.options.identifier,
|
|
377
420
|
error: error.message,
|
|
378
421
|
};
|
|
379
|
-
// Emit a debug event indicating the error on the node
|
|
380
422
|
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Error on node: ${JSON.stringify(debugInfo)}`);
|
|
381
|
-
// Emit a "nodeError" event with the node and the error as arguments
|
|
382
423
|
this.manager.emit(Enums_1.ManagerEventTypes.NodeError, this, error);
|
|
383
424
|
}
|
|
384
425
|
/**
|
|
@@ -406,7 +447,7 @@ class Node {
|
|
|
406
447
|
this.stats = { ...payload };
|
|
407
448
|
break;
|
|
408
449
|
case "playerUpdate":
|
|
409
|
-
player =
|
|
450
|
+
player = this.manager.players.get(payload.guildId);
|
|
410
451
|
if (player && player.node.options.identifier !== this.options.identifier) {
|
|
411
452
|
return;
|
|
412
453
|
}
|
|
@@ -414,7 +455,7 @@ class Node {
|
|
|
414
455
|
player.position = payload.state.position || 0;
|
|
415
456
|
break;
|
|
416
457
|
case "event":
|
|
417
|
-
player =
|
|
458
|
+
player = this.manager.players.get(payload.guildId);
|
|
418
459
|
if (player && player.node.options.identifier !== this.options.identifier) {
|
|
419
460
|
return;
|
|
420
461
|
}
|
|
@@ -425,11 +466,9 @@ class Node {
|
|
|
425
466
|
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Node message: ${JSON.stringify(payload)}`);
|
|
426
467
|
this.rest.setSessionId(payload.sessionId);
|
|
427
468
|
this.sessionId = payload.sessionId;
|
|
428
|
-
this.updateSessionId();
|
|
469
|
+
await this.updateSessionId();
|
|
429
470
|
this.info = await this.fetchInfo();
|
|
430
|
-
// Log if the session was resumed successfully
|
|
431
471
|
if (payload.resumed) {
|
|
432
|
-
// Load player states from the JSON file
|
|
433
472
|
await this.manager.loadPlayerStates(this.options.identifier);
|
|
434
473
|
}
|
|
435
474
|
if (this.options.enableSessionResumeOption) {
|
|
@@ -453,7 +492,7 @@ class Node {
|
|
|
453
492
|
async handleEvent(payload) {
|
|
454
493
|
if (!payload.guildId)
|
|
455
494
|
return;
|
|
456
|
-
const player =
|
|
495
|
+
const player = this.manager.players.get(payload.guildId);
|
|
457
496
|
if (!player)
|
|
458
497
|
return;
|
|
459
498
|
const track = await player.queue.getCurrent();
|
|
@@ -560,18 +599,14 @@ class Node {
|
|
|
560
599
|
}
|
|
561
600
|
player.set("skipFlag", false);
|
|
562
601
|
const oldPlayer = player;
|
|
563
|
-
// Handle track end events
|
|
564
602
|
switch (reason) {
|
|
565
603
|
case Enums_1.TrackEndReasonTypes.LoadFailed:
|
|
566
604
|
case Enums_1.TrackEndReasonTypes.Cleanup:
|
|
567
|
-
// Handle the case when a track failed to load or was cleaned up
|
|
568
605
|
await this.handleFailedTrack(player, track, payload);
|
|
569
606
|
break;
|
|
570
607
|
case Enums_1.TrackEndReasonTypes.Replaced:
|
|
571
|
-
// Handle the case when a track was replaced
|
|
572
608
|
break;
|
|
573
609
|
case Enums_1.TrackEndReasonTypes.Stopped:
|
|
574
|
-
// If the track was forcibly replaced
|
|
575
610
|
if (await player.queue.size()) {
|
|
576
611
|
await this.playNextTrack(player, track, payload);
|
|
577
612
|
}
|
|
@@ -686,9 +721,7 @@ class Node {
|
|
|
686
721
|
}
|
|
687
722
|
// Move to the next track
|
|
688
723
|
await queue.setCurrent(await queue.dequeue());
|
|
689
|
-
// Emit track end event
|
|
690
724
|
this.manager.emit(Enums_1.ManagerEventTypes.TrackEnd, player, track, payload);
|
|
691
|
-
// If the track was stopped manually and no more tracks exist, end the queue
|
|
692
725
|
if (payload.reason === Enums_1.TrackEndReasonTypes.Stopped) {
|
|
693
726
|
const next = await queue.dequeue();
|
|
694
727
|
await queue.setCurrent(next ?? null);
|
|
@@ -697,7 +730,6 @@ class Node {
|
|
|
697
730
|
return;
|
|
698
731
|
}
|
|
699
732
|
}
|
|
700
|
-
// If autoplay is enabled, play the next track
|
|
701
733
|
if (playNextOnEnd)
|
|
702
734
|
await player.play();
|
|
703
735
|
}
|
|
@@ -715,9 +747,7 @@ class Node {
|
|
|
715
747
|
async playNextTrack(player, track, payload) {
|
|
716
748
|
// Shift the queue to set the next track as current
|
|
717
749
|
await player.queue.setCurrent(await player.queue.dequeue());
|
|
718
|
-
// Emit the track end event
|
|
719
750
|
this.manager.emit(Enums_1.ManagerEventTypes.TrackEnd, player, track, payload);
|
|
720
|
-
// If autoplay is enabled, play the next track
|
|
721
751
|
if (this.manager.options.playNextOnEnd)
|
|
722
752
|
await player.play();
|
|
723
753
|
}
|
|
@@ -745,7 +775,6 @@ class Node {
|
|
|
745
775
|
return;
|
|
746
776
|
attempt++;
|
|
747
777
|
}
|
|
748
|
-
// If all attempts fail, reset the player state and emit queueEnd
|
|
749
778
|
player.playing = false;
|
|
750
779
|
this.manager.emit(Enums_1.ManagerEventTypes.QueueEnd, player, track, payload);
|
|
751
780
|
}
|
|
@@ -690,6 +690,7 @@ class Player {
|
|
|
690
690
|
// Get and remove the most recent previous track
|
|
691
691
|
const lastTrack = await this.queue.popPrevious();
|
|
692
692
|
if (!lastTrack) {
|
|
693
|
+
await this.queue.clearPrevious();
|
|
693
694
|
throw new Error("No previous track available.");
|
|
694
695
|
}
|
|
695
696
|
// Capture the current state of the player before making changes.
|
package/dist/structures/Utils.js
CHANGED
|
@@ -263,9 +263,11 @@ class AutoPlayUtils {
|
|
|
263
263
|
*/
|
|
264
264
|
static async getRecommendedTracksFromSource(track, platform) {
|
|
265
265
|
const requester = track.requester;
|
|
266
|
+
const parsedURL = new URL(track.uri);
|
|
266
267
|
switch (platform) {
|
|
267
268
|
case Enums_1.AutoPlayPlatform.Spotify: {
|
|
268
|
-
|
|
269
|
+
const allowedSpotifyHosts = ["open.spotify.com", "www.spotify.com"];
|
|
270
|
+
if (!allowedSpotifyHosts.includes(parsedURL.host)) {
|
|
269
271
|
const resolvedTrack = await this.resolveFirstTrackFromQuery(`${track.author} - ${track.title}`, Enums_1.SearchPlatform.Spotify, requester);
|
|
270
272
|
if (!resolvedTrack)
|
|
271
273
|
return [];
|
|
@@ -282,7 +284,8 @@ class AutoPlayUtils {
|
|
|
282
284
|
return tracks;
|
|
283
285
|
}
|
|
284
286
|
case Enums_1.AutoPlayPlatform.Deezer: {
|
|
285
|
-
|
|
287
|
+
const allowedDeezerHosts = ["deezer.com", "www.deezer.com", "www.deezer.page.link"];
|
|
288
|
+
if (!allowedDeezerHosts.includes(parsedURL.host)) {
|
|
286
289
|
const resolvedTrack = await this.resolveFirstTrackFromQuery(`${track.author} - ${track.title}`, Enums_1.SearchPlatform.Deezer, requester);
|
|
287
290
|
if (!resolvedTrack)
|
|
288
291
|
return [];
|
|
@@ -294,7 +297,8 @@ class AutoPlayUtils {
|
|
|
294
297
|
return tracks;
|
|
295
298
|
}
|
|
296
299
|
case Enums_1.AutoPlayPlatform.SoundCloud: {
|
|
297
|
-
|
|
300
|
+
const allowedSoundCloudHosts = ["soundcloud.com", "www.soundcloud.com"];
|
|
301
|
+
if (!allowedSoundCloudHosts.includes(parsedURL.host)) {
|
|
298
302
|
const resolvedTrack = await this.resolveFirstTrackFromQuery(`${track.author} - ${track.title}`, Enums_1.SearchPlatform.SoundCloud, requester);
|
|
299
303
|
if (!resolvedTrack)
|
|
300
304
|
return [];
|
|
@@ -345,7 +349,8 @@ class AutoPlayUtils {
|
|
|
345
349
|
}
|
|
346
350
|
}
|
|
347
351
|
case Enums_1.AutoPlayPlatform.YouTube: {
|
|
348
|
-
const
|
|
352
|
+
const allowedYouTubeHosts = ["youtube.com", "youtu.be"];
|
|
353
|
+
const hasYouTubeURL = allowedYouTubeHosts.some((url) => track.uri.includes(url));
|
|
349
354
|
let videoID = null;
|
|
350
355
|
if (hasYouTubeURL) {
|
|
351
356
|
videoID = track.uri.split("=").pop();
|
|
@@ -370,7 +375,8 @@ class AutoPlayUtils {
|
|
|
370
375
|
return filteredTracks;
|
|
371
376
|
}
|
|
372
377
|
case Enums_1.AutoPlayPlatform.Tidal: {
|
|
373
|
-
|
|
378
|
+
const allowedTidalHosts = ["tidal.com", "www.tidal.com"];
|
|
379
|
+
if (!allowedTidalHosts.includes(parsedURL.host)) {
|
|
374
380
|
const resolvedTrack = await this.resolveFirstTrackFromQuery(`${track.author} - ${track.title}`, Enums_1.SearchPlatform.Tidal, requester);
|
|
375
381
|
if (!resolvedTrack)
|
|
376
382
|
return [];
|
|
@@ -382,7 +388,8 @@ class AutoPlayUtils {
|
|
|
382
388
|
return tracks;
|
|
383
389
|
}
|
|
384
390
|
case Enums_1.AutoPlayPlatform.VKMusic: {
|
|
385
|
-
|
|
391
|
+
const allowedVKHosts = ["vk.com", "www.vk.com", "vk.ru", "www.vk.ru"];
|
|
392
|
+
if (!allowedVKHosts.includes(parsedURL.host)) {
|
|
386
393
|
const resolvedTrack = await this.resolveFirstTrackFromQuery(`${track.author} - ${track.title}`, Enums_1.SearchPlatform.VKMusic, requester);
|
|
387
394
|
if (!resolvedTrack)
|
|
388
395
|
return [];
|
|
@@ -394,7 +401,8 @@ class AutoPlayUtils {
|
|
|
394
401
|
return tracks;
|
|
395
402
|
}
|
|
396
403
|
case Enums_1.AutoPlayPlatform.Qobuz: {
|
|
397
|
-
|
|
404
|
+
const allowedQobuzHosts = ["qobuz.com", "www.qobuz.com", "play.qobuz.com"];
|
|
405
|
+
if (!allowedQobuzHosts.includes(parsedURL.host)) {
|
|
398
406
|
const resolvedTrack = await this.resolveFirstTrackFromQuery(`${track.author} - ${track.title}`, Enums_1.SearchPlatform.Qobuz, requester);
|
|
399
407
|
if (!resolvedTrack)
|
|
400
408
|
return [];
|
|
@@ -522,24 +530,48 @@ class AutoPlayUtils {
|
|
|
522
530
|
// console.error("[Spotify] Failed to launch Playwright:", err);
|
|
523
531
|
// }
|
|
524
532
|
// }
|
|
533
|
+
static isPlaylistRawData(data) {
|
|
534
|
+
return typeof data === "object" && data !== null && Array.isArray(data.tracks);
|
|
535
|
+
}
|
|
536
|
+
static isTrackData(data) {
|
|
537
|
+
return typeof data === "object" && data !== null && "encoded" in data && "info" in data;
|
|
538
|
+
}
|
|
539
|
+
static isTrackDataArray(data) {
|
|
540
|
+
return (Array.isArray(data) &&
|
|
541
|
+
data.every((track) => typeof track === "object" && track !== null && "encoded" in track && "info" in track && typeof track.encoded === "string"));
|
|
542
|
+
}
|
|
525
543
|
static buildTracksFromResponse(recommendedResult, requester) {
|
|
526
544
|
if (!recommendedResult)
|
|
527
545
|
return [];
|
|
528
546
|
if (TrackUtils.isErrorOrEmptySearchResult(recommendedResult))
|
|
529
547
|
return [];
|
|
530
548
|
switch (recommendedResult.loadType) {
|
|
531
|
-
case Enums_1.LoadTypes.Search: {
|
|
532
|
-
const tracks = recommendedResult.data.map((t) => TrackUtils.build(t, requester));
|
|
533
|
-
return tracks;
|
|
534
|
-
}
|
|
535
549
|
case Enums_1.LoadTypes.Track: {
|
|
536
|
-
const
|
|
537
|
-
|
|
550
|
+
const data = recommendedResult.data;
|
|
551
|
+
if (!this.isTrackData(data)) {
|
|
552
|
+
throw new Error("[TrackBuilder] Invalid TrackData object.");
|
|
553
|
+
}
|
|
554
|
+
return [TrackUtils.build(data, requester)];
|
|
555
|
+
}
|
|
556
|
+
case Enums_1.LoadTypes.Short:
|
|
557
|
+
case Enums_1.LoadTypes.Search: {
|
|
558
|
+
const data = recommendedResult.data;
|
|
559
|
+
if (!this.isTrackDataArray(data)) {
|
|
560
|
+
throw new Error("[TrackBuilder] Invalid TrackData[] array for LoadTypes.Search or Short.");
|
|
561
|
+
}
|
|
562
|
+
return data.map((d) => TrackUtils.build(d, requester));
|
|
538
563
|
}
|
|
564
|
+
case Enums_1.LoadTypes.Album:
|
|
565
|
+
case Enums_1.LoadTypes.Artist:
|
|
566
|
+
case Enums_1.LoadTypes.Station:
|
|
567
|
+
case Enums_1.LoadTypes.Podcast:
|
|
568
|
+
case Enums_1.LoadTypes.Show:
|
|
539
569
|
case Enums_1.LoadTypes.Playlist: {
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
570
|
+
const data = recommendedResult.data;
|
|
571
|
+
if (this.isPlaylistRawData(data)) {
|
|
572
|
+
return data.tracks.map((d) => TrackUtils.build(d, requester));
|
|
573
|
+
}
|
|
574
|
+
throw new Error(`[TrackBuilder] Invalid playlist data for loadType: ${recommendedResult.loadType}`);
|
|
543
575
|
}
|
|
544
576
|
default:
|
|
545
577
|
throw new Error(`[TrackBuilder] Unsupported loadType: ${recommendedResult.loadType}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magmastream",
|
|
3
|
-
"version": "2.9.0-dev.
|
|
3
|
+
"version": "2.9.0-dev.39",
|
|
4
4
|
"description": "A user-friendly Lavalink client designed for NodeJS.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -95,4 +95,4 @@
|
|
|
95
95
|
"homepage": "https://docs.magmastream.com",
|
|
96
96
|
"author": "Abel Purnwasy",
|
|
97
97
|
"license": "Apache-2.0"
|
|
98
|
-
}
|
|
98
|
+
}
|