magmastream 2.10.6-alpha.4 → 2.10.6-alpha.6
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.
|
@@ -38,6 +38,11 @@ export declare class Manager extends EventEmitter {
|
|
|
38
38
|
*/
|
|
39
39
|
constructor(options: StandaloneManagerOptions);
|
|
40
40
|
constructor(options: ManagerOptions, isWrapper: true);
|
|
41
|
+
/**
|
|
42
|
+
* Waits until the Redis client is connected, at most `timeoutMs`. Never throws: when Redis is not
|
|
43
|
+
* ready in time, init continues and commands fail later with Redis's own error.
|
|
44
|
+
*/
|
|
45
|
+
private waitForRedis;
|
|
41
46
|
/**
|
|
42
47
|
* Initiates the Manager.
|
|
43
48
|
* @param clientId - The Discord client ID (only required when not using any of the magmastream wrappers).
|
|
@@ -179,6 +179,30 @@ class Manager extends events_1.EventEmitter {
|
|
|
179
179
|
});
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Waits until the Redis client is connected, at most `timeoutMs`. Never throws: when Redis is not
|
|
184
|
+
* ready in time, init continues and commands fail later with Redis's own error.
|
|
185
|
+
*/
|
|
186
|
+
async waitForRedis(timeoutMs = 10_000) {
|
|
187
|
+
const redis = this.redis;
|
|
188
|
+
if (!redis || redis.status === "ready")
|
|
189
|
+
return;
|
|
190
|
+
await new Promise((resolve) => {
|
|
191
|
+
const done = (message) => {
|
|
192
|
+
clearTimeout(timer);
|
|
193
|
+
redis.off("ready", onReady);
|
|
194
|
+
redis.off("end", onEnd);
|
|
195
|
+
if (message)
|
|
196
|
+
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] ${message}`);
|
|
197
|
+
resolve();
|
|
198
|
+
};
|
|
199
|
+
const onReady = () => done();
|
|
200
|
+
const onEnd = () => done("Redis connection ended before it was ready.");
|
|
201
|
+
const timer = setTimeout(() => done(`Redis was not ready after ${timeoutMs}ms, continuing without waiting.`), timeoutMs);
|
|
202
|
+
redis.once("ready", onReady);
|
|
203
|
+
redis.once("end", onEnd);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
182
206
|
/**
|
|
183
207
|
* Initiates the Manager.
|
|
184
208
|
* @param clientId - The Discord client ID (only required when not using any of the magmastream wrappers).
|
|
@@ -209,6 +233,9 @@ class Manager extends events_1.EventEmitter {
|
|
|
209
233
|
}
|
|
210
234
|
if (this.options.stateStorage.type === Enums_1.StateStorageType.Redis) {
|
|
211
235
|
this.redis = new ioredis_1.default(lodash_1.default.omit(this.options.stateStorage.redisConfig, "prefix"));
|
|
236
|
+
// Nodes read and write their session ids as soon as they connect. With enableOfflineQueue: false,
|
|
237
|
+
// any command sent before Redis is ready fails, so wait for the connection first.
|
|
238
|
+
await this.waitForRedis();
|
|
212
239
|
}
|
|
213
240
|
const results = await Promise.allSettled([...this.nodes.values()].map(async (node) => {
|
|
214
241
|
await node.connect();
|
|
@@ -242,8 +242,8 @@ export declare class Node {
|
|
|
242
242
|
*
|
|
243
243
|
* If the node is a NodeLink, it will use the `NodeLinkGetLyrics` method to fetch the lyrics.
|
|
244
244
|
*
|
|
245
|
-
* Requires the `lavalyrics-plugin` to be present in the Lavalink node.
|
|
246
|
-
*
|
|
245
|
+
* Requires the `lavalyrics-plugin` to be present in the Lavalink node. The lyrics sources come from
|
|
246
|
+
* whichever plugins register them with LavaLyrics (LavaSrc, PulseLink, java-lyrics, ...).
|
|
247
247
|
*
|
|
248
248
|
* @param {Track} track - The track to fetch the lyrics for.
|
|
249
249
|
* @param {boolean} [skipTrackSource=false] - Whether to skip using the track's source URL.
|
|
@@ -283,14 +283,14 @@ export declare class Node {
|
|
|
283
283
|
* @param {string} guildId - The ID of the guild to subscribe to lyrics for.
|
|
284
284
|
* @param {boolean} [skipTrackSource=false] - Whether to skip using the track's source URL.
|
|
285
285
|
* @returns {Promise<unknown>} A promise that resolves when the subscription is complete.
|
|
286
|
-
* @throws {
|
|
286
|
+
* @throws {MagmaStreamError} If the node is not connected to the lavalink server or if the lavalyrics-plugin is not available.
|
|
287
287
|
*/
|
|
288
288
|
lyricsSubscribe(guildId: string, skipTrackSource?: boolean): Promise<unknown>;
|
|
289
289
|
/**
|
|
290
290
|
* Unsubscribes from lyrics for a player.
|
|
291
291
|
* @param {string} guildId - The ID of the guild to unsubscribe from lyrics for.
|
|
292
292
|
* @returns {Promise<unknown>} A promise that resolves when the unsubscription is complete.
|
|
293
|
-
* @throws {
|
|
293
|
+
* @throws {MagmaStreamError} If the node is not connected to the lavalink server, or if neither the lavalyrics-plugin nor the java-lyrics-plugin is available.
|
|
294
294
|
*/
|
|
295
295
|
lyricsUnsubscribe(guildId: string): Promise<unknown>;
|
|
296
296
|
/**
|
|
@@ -454,6 +454,8 @@ export declare class Node {
|
|
|
454
454
|
* @returns {Promise<void>}
|
|
455
455
|
*/
|
|
456
456
|
removeNodeLinkMix(player: Player, mixId: string): Promise<void>;
|
|
457
|
+
/** Throws NODE_PLUGIN_ERROR unless the node reports at least one of the given plugins. */
|
|
458
|
+
private requireAnyPlugin;
|
|
457
459
|
private assertNodeLinkFeature;
|
|
458
460
|
private assertMixVolume;
|
|
459
461
|
private assertNodeLinkSponsorBlockUpdate;
|
package/dist/structures/Node.js
CHANGED
|
@@ -496,7 +496,14 @@ class Node {
|
|
|
496
496
|
const hadPreviousSession = this.pendingResumeSessionId && this.pendingResumeSessionId !== payload.sessionId;
|
|
497
497
|
this.pendingResumeSessionId = null;
|
|
498
498
|
this.sessionId = payload.sessionId;
|
|
499
|
-
|
|
499
|
+
// Storing the session id only matters for resuming after a restart. A storage failure must not stop
|
|
500
|
+
// the node from loading its info and player states.
|
|
501
|
+
try {
|
|
502
|
+
await this.updateSessionId();
|
|
503
|
+
}
|
|
504
|
+
catch (err) {
|
|
505
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Could not store sessionId for node ${this.options.identifier}, session resume may not work after a restart: ${err instanceof Error ? (err.cause instanceof Error ? err.cause.message : err.message) : String(err)}`);
|
|
506
|
+
}
|
|
500
507
|
this.info = await this.fetchInfo();
|
|
501
508
|
this.isNodeLink = this.isNodeLink || Boolean(this.info.isNodelink);
|
|
502
509
|
this.rest.isNodeLink = this.isNodeLink;
|
|
@@ -1034,8 +1041,8 @@ class Node {
|
|
|
1034
1041
|
*
|
|
1035
1042
|
* If the node is a NodeLink, it will use the `NodeLinkGetLyrics` method to fetch the lyrics.
|
|
1036
1043
|
*
|
|
1037
|
-
* Requires the `lavalyrics-plugin` to be present in the Lavalink node.
|
|
1038
|
-
*
|
|
1044
|
+
* Requires the `lavalyrics-plugin` to be present in the Lavalink node. The lyrics sources come from
|
|
1045
|
+
* whichever plugins register them with LavaLyrics (LavaSrc, PulseLink, java-lyrics, ...).
|
|
1039
1046
|
*
|
|
1040
1047
|
* @param {Track} track - The track to fetch the lyrics for.
|
|
1041
1048
|
* @param {boolean} [skipTrackSource=false] - Whether to skip using the track's source URL.
|
|
@@ -1053,23 +1060,7 @@ class Node {
|
|
|
1053
1060
|
if (this.isNodeLink) {
|
|
1054
1061
|
return (await this.rest.get(`/v4/loadlyrics?encodedTrack=${encodeURIComponent(track.track)}${language ? `&lang=${encodeURIComponent(language)}` : ""}`));
|
|
1055
1062
|
}
|
|
1056
|
-
|
|
1057
|
-
for (const plugin of requiredPlugins) {
|
|
1058
|
-
if (!(this.info?.plugins ?? []).some((p) => p.name === plugin)) {
|
|
1059
|
-
throw new MagmastreamError_1.MagmaStreamError({
|
|
1060
|
-
code: Enums_1.MagmaStreamErrorCode.NODE_PLUGIN_ERROR,
|
|
1061
|
-
message: `The plugin "${plugin}" must be present in the lavalink node.`,
|
|
1062
|
-
context: { identifier: this.options.identifier },
|
|
1063
|
-
});
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
if (!(this.info?.plugins ?? []).some((p) => p.name === "lavasrc-plugin" || p.name === "java-lyrics-plugin")) {
|
|
1067
|
-
throw new MagmastreamError_1.MagmaStreamError({
|
|
1068
|
-
code: Enums_1.MagmaStreamErrorCode.NODE_PLUGIN_ERROR,
|
|
1069
|
-
message: `One of the following plugins must also be present in the lavalink node: "lavasrc-plugin" or "java-lyrics-plugin".`,
|
|
1070
|
-
context: { identifier: this.options.identifier },
|
|
1071
|
-
});
|
|
1072
|
-
}
|
|
1063
|
+
this.requireAnyPlugin(["lavalyrics-plugin"], "fetch lyrics");
|
|
1073
1064
|
const lyrics = (await this.rest.get(`/v4/lyrics?track=${encodeURIComponent(track.track)}&skipTrackSource=${skipTrackSource}`));
|
|
1074
1065
|
return lyrics || { sourceName: null, source: null, provider: null, text: null, lines: [], plugin: [] };
|
|
1075
1066
|
}
|
|
@@ -1170,7 +1161,7 @@ class Node {
|
|
|
1170
1161
|
* @param {string} guildId - The ID of the guild to subscribe to lyrics for.
|
|
1171
1162
|
* @param {boolean} [skipTrackSource=false] - Whether to skip using the track's source URL.
|
|
1172
1163
|
* @returns {Promise<unknown>} A promise that resolves when the subscription is complete.
|
|
1173
|
-
* @throws {
|
|
1164
|
+
* @throws {MagmaStreamError} If the node is not connected to the lavalink server or if the lavalyrics-plugin is not available.
|
|
1174
1165
|
*/
|
|
1175
1166
|
async lyricsSubscribe(guildId, skipTrackSource = false) {
|
|
1176
1167
|
if (!this.connected) {
|
|
@@ -1182,23 +1173,7 @@ class Node {
|
|
|
1182
1173
|
}
|
|
1183
1174
|
if (this.isNodeLink)
|
|
1184
1175
|
return await this.rest.post(`/v4/sessions/${this.sessionId}/players/${guildId}/lyrics/subscribe?skipTrackSource=${skipTrackSource}`, {});
|
|
1185
|
-
|
|
1186
|
-
for (const plugin of requiredPlugins) {
|
|
1187
|
-
if (!(this.info?.plugins ?? []).some((p) => p.name === plugin)) {
|
|
1188
|
-
throw new MagmastreamError_1.MagmaStreamError({
|
|
1189
|
-
code: Enums_1.MagmaStreamErrorCode.NODE_PLUGIN_ERROR,
|
|
1190
|
-
message: `The plugin "${plugin}" must be present in the lavalink node.`,
|
|
1191
|
-
context: { identifier: this.options.identifier },
|
|
1192
|
-
});
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
if (!(this.info?.plugins ?? []).some((p) => p.name === "lavasrc-plugin" || p.name === "java-lyrics-plugin")) {
|
|
1196
|
-
throw new MagmastreamError_1.MagmaStreamError({
|
|
1197
|
-
code: Enums_1.MagmaStreamErrorCode.NODE_PLUGIN_ERROR,
|
|
1198
|
-
message: `One of the following plugins must also be present in the lavalink node: "lavasrc-plugin" or "java-lyrics-plugin".`,
|
|
1199
|
-
context: { identifier: this.options.identifier },
|
|
1200
|
-
});
|
|
1201
|
-
}
|
|
1176
|
+
this.requireAnyPlugin(["lavalyrics-plugin"], "subscribe to lyrics");
|
|
1202
1177
|
try {
|
|
1203
1178
|
return await this.rest.post(`/v4/sessions/${this.sessionId}/players/${guildId}/lyrics/subscribe?skipTrackSource=${skipTrackSource}`, {});
|
|
1204
1179
|
}
|
|
@@ -1217,7 +1192,7 @@ class Node {
|
|
|
1217
1192
|
* Unsubscribes from lyrics for a player.
|
|
1218
1193
|
* @param {string} guildId - The ID of the guild to unsubscribe from lyrics for.
|
|
1219
1194
|
* @returns {Promise<unknown>} A promise that resolves when the unsubscription is complete.
|
|
1220
|
-
* @throws {
|
|
1195
|
+
* @throws {MagmaStreamError} If the node is not connected to the lavalink server, or if neither the lavalyrics-plugin nor the java-lyrics-plugin is available.
|
|
1221
1196
|
*/
|
|
1222
1197
|
async lyricsUnsubscribe(guildId) {
|
|
1223
1198
|
if (!this.connected) {
|
|
@@ -1229,13 +1204,7 @@ class Node {
|
|
|
1229
1204
|
}
|
|
1230
1205
|
if (this.isNodeLink)
|
|
1231
1206
|
return await this.rest.delete(`/v4/sessions/${this.sessionId}/players/${guildId}/lyrics/subscribe`);
|
|
1232
|
-
|
|
1233
|
-
throw new MagmastreamError_1.MagmaStreamError({
|
|
1234
|
-
code: Enums_1.MagmaStreamErrorCode.NODE_PLUGIN_ERROR,
|
|
1235
|
-
message: `The plugin "java-lyrics-plugin" must be present in the lavalink node to unsubscribe.`,
|
|
1236
|
-
context: { identifier: this.options.identifier },
|
|
1237
|
-
});
|
|
1238
|
-
}
|
|
1207
|
+
this.requireAnyPlugin(["lavalyrics-plugin", "java-lyrics-plugin"], "unsubscribe from lyrics");
|
|
1239
1208
|
try {
|
|
1240
1209
|
return await this.rest.delete(`/v4/sessions/${this.sessionId}/players/${guildId}/lyrics/subscribe`);
|
|
1241
1210
|
}
|
|
@@ -1519,6 +1488,20 @@ class Node {
|
|
|
1519
1488
|
async removeNodeLinkMix(player, mixId) {
|
|
1520
1489
|
return this.removeMix(player, mixId);
|
|
1521
1490
|
}
|
|
1491
|
+
/** Throws NODE_PLUGIN_ERROR unless the node reports at least one of the given plugins. */
|
|
1492
|
+
requireAnyPlugin(plugins, action) {
|
|
1493
|
+
const installed = this.info?.plugins ?? [];
|
|
1494
|
+
if (installed.some((plugin) => plugins.includes(plugin.name)))
|
|
1495
|
+
return;
|
|
1496
|
+
const names = plugins.map((plugin) => `"${plugin}"`).join(" or ");
|
|
1497
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
1498
|
+
code: Enums_1.MagmaStreamErrorCode.NODE_PLUGIN_ERROR,
|
|
1499
|
+
message: plugins.length === 1
|
|
1500
|
+
? `The plugin ${names} must be present in the lavalink node to ${action}.`
|
|
1501
|
+
: `One of the following plugins must be present in the lavalink node to ${action}: ${names}.`,
|
|
1502
|
+
context: { identifier: this.options.identifier },
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1522
1505
|
assertNodeLinkFeature(action) {
|
|
1523
1506
|
if (this.isNodeLink && this.connected)
|
|
1524
1507
|
return;
|
package/dist/structures/Utils.js
CHANGED
|
@@ -238,7 +238,17 @@ class AutoPlayUtils {
|
|
|
238
238
|
if (apiKey) {
|
|
239
239
|
return await this.getRecommendedTracksFromLastFm(track, apiKey);
|
|
240
240
|
}
|
|
241
|
-
|
|
241
|
+
// node.info is only set once the node's "ready" message is handled, so a just-connected node has none yet
|
|
242
|
+
let info = node.info;
|
|
243
|
+
if (!info) {
|
|
244
|
+
try {
|
|
245
|
+
info = node.info = await node.fetchInfo();
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[AUTOPLAY] Node ${node.options.identifier} has no info yet: ${error instanceof Error ? error.message : String(error)}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const enabledSources = info?.sourceManagers ?? [];
|
|
242
252
|
const autoPlaySearchPlatforms = this.manager.options.autoPlaySearchPlatforms;
|
|
243
253
|
// Iterate over autoplay platforms in order of priority
|
|
244
254
|
for (const platform of autoPlaySearchPlatforms) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magmastream",
|
|
3
|
-
"version": "2.10.6-alpha.
|
|
3
|
+
"version": "2.10.6-alpha.6",
|
|
4
4
|
"description": "A user-friendly Lavalink client designed for NodeJS.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"prepare": "npm run build",
|
|
12
12
|
"build": "tsc",
|
|
13
13
|
"types": "rtb --dist dist",
|
|
14
|
-
"test": "npm run build && node --test tests/player-state.test.cjs tests/hot-paths.test.cjs tests/restore-performance.test.cjs tests/lyrics.test.cjs",
|
|
14
|
+
"test": "npm run build && node --test tests/player-state.test.cjs tests/hot-paths.test.cjs tests/restore-performance.test.cjs tests/lyrics.test.cjs tests/autoplay.test.cjs tests/session-storage.test.cjs",
|
|
15
15
|
"format": "prettier --write .",
|
|
16
16
|
"format:check": "prettier --check .",
|
|
17
17
|
"lint": "eslint \"src/**/*.{ts,js}\"",
|