magmastream 2.10.3-alpha.4 → 2.10.3-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.
- package/dist/structures/Enums.d.ts +6 -0
- package/dist/structures/Enums.js +6 -0
- package/dist/structures/Filters.d.ts +36 -1
- package/dist/structures/Filters.js +88 -2
- package/dist/structures/Manager.js +24 -20
- package/dist/structures/Node.d.ts +1 -0
- package/dist/structures/Node.js +132 -25
- package/dist/structures/Player.d.ts +5 -1
- package/dist/structures/Player.js +100 -11
- package/dist/structures/Rest.d.ts +45 -2
- package/dist/structures/Rest.js +102 -1
- package/dist/structures/Types.d.ts +173 -42
- package/dist/structures/Utils.js +29 -11
- package/package.json +1 -1
|
@@ -43,6 +43,8 @@ export declare enum LoadTypes {
|
|
|
43
43
|
/** Nodelink */
|
|
44
44
|
Artist = "artist",
|
|
45
45
|
/** Nodelink */
|
|
46
|
+
Episode = "episode",
|
|
47
|
+
/** Nodelink */
|
|
46
48
|
Station = "station",
|
|
47
49
|
/** Nodelink */
|
|
48
50
|
Podcast = "podcast",
|
|
@@ -250,10 +252,14 @@ export declare enum AvailableFilters {
|
|
|
250
252
|
Party = "party",
|
|
251
253
|
Pop = "pop",
|
|
252
254
|
Radio = "radio",
|
|
255
|
+
PluginFilters = "pluginFilters",
|
|
256
|
+
SetChannelMix = "setChannelMix",
|
|
253
257
|
SetDistortion = "setDistortion",
|
|
254
258
|
SetKaraoke = "setKaraoke",
|
|
259
|
+
SetLowPass = "setLowPass",
|
|
255
260
|
SetRotation = "setRotation",
|
|
256
261
|
SetTimescale = "setTimescale",
|
|
262
|
+
SetTremolo = "setTremolo",
|
|
257
263
|
Slowmo = "slowmo",
|
|
258
264
|
Soft = "soft",
|
|
259
265
|
TrebleBass = "trebleBass",
|
package/dist/structures/Enums.js
CHANGED
|
@@ -50,6 +50,8 @@ var LoadTypes;
|
|
|
50
50
|
/** Nodelink */
|
|
51
51
|
LoadTypes["Artist"] = "artist";
|
|
52
52
|
/** Nodelink */
|
|
53
|
+
LoadTypes["Episode"] = "episode";
|
|
54
|
+
/** Nodelink */
|
|
53
55
|
LoadTypes["Station"] = "station";
|
|
54
56
|
/** Nodelink */
|
|
55
57
|
LoadTypes["Podcast"] = "podcast";
|
|
@@ -267,10 +269,14 @@ var AvailableFilters;
|
|
|
267
269
|
AvailableFilters["Party"] = "party";
|
|
268
270
|
AvailableFilters["Pop"] = "pop";
|
|
269
271
|
AvailableFilters["Radio"] = "radio";
|
|
272
|
+
AvailableFilters["PluginFilters"] = "pluginFilters";
|
|
273
|
+
AvailableFilters["SetChannelMix"] = "setChannelMix";
|
|
270
274
|
AvailableFilters["SetDistortion"] = "setDistortion";
|
|
271
275
|
AvailableFilters["SetKaraoke"] = "setKaraoke";
|
|
276
|
+
AvailableFilters["SetLowPass"] = "setLowPass";
|
|
272
277
|
AvailableFilters["SetRotation"] = "setRotation";
|
|
273
278
|
AvailableFilters["SetTimescale"] = "setTimescale";
|
|
279
|
+
AvailableFilters["SetTremolo"] = "setTremolo";
|
|
274
280
|
AvailableFilters["Slowmo"] = "slowmo";
|
|
275
281
|
AvailableFilters["Soft"] = "soft";
|
|
276
282
|
AvailableFilters["TrebleBass"] = "trebleBass";
|
|
@@ -2,14 +2,19 @@ import { Band } from "../utils/filtersEqualizers";
|
|
|
2
2
|
import { AvailableFilters } from "./Enums";
|
|
3
3
|
import { Manager } from "./Manager";
|
|
4
4
|
import { Player } from "./Player";
|
|
5
|
-
import { DistortionOptions, KaraokeOptions, ReverbOptions, RotationOptions, TimescaleOptions, VibratoOptions } from "./Types";
|
|
5
|
+
import { ChannelMixOptions, DistortionOptions, KaraokeOptions, LowPassOptions, ReverbOptions, RotationOptions, TimescaleOptions, TremoloOptions, VibratoOptions } from "./Types";
|
|
6
6
|
export declare class Filters {
|
|
7
|
+
private static readonly defaultPluginFilterPlugins;
|
|
7
8
|
distortion: DistortionOptions | null;
|
|
8
9
|
equalizer: Band[];
|
|
9
10
|
karaoke: KaraokeOptions | null;
|
|
10
11
|
rotation: RotationOptions | null;
|
|
11
12
|
timescale: TimescaleOptions | null;
|
|
13
|
+
tremoloOptions: TremoloOptions | null;
|
|
12
14
|
vibrato: VibratoOptions | null;
|
|
15
|
+
channelMix: ChannelMixOptions | null;
|
|
16
|
+
lowPass: LowPassOptions | null;
|
|
17
|
+
pluginFilters: Record<string, unknown>;
|
|
13
18
|
reverb: ReverbOptions | null;
|
|
14
19
|
volume: number;
|
|
15
20
|
bassBoostlevel: number;
|
|
@@ -42,6 +47,7 @@ export declare class Filters {
|
|
|
42
47
|
*/
|
|
43
48
|
private applyFilter;
|
|
44
49
|
private emitPlayersTasteUpdate;
|
|
50
|
+
private assertPluginFiltersSupported;
|
|
45
51
|
/**
|
|
46
52
|
* Sets the status of a specific filter.
|
|
47
53
|
*
|
|
@@ -112,6 +118,35 @@ export declare class Filters {
|
|
|
112
118
|
* @returns {Promise<this>} - Returns the current instance of the Filters class for method chaining.
|
|
113
119
|
*/
|
|
114
120
|
setVibrato(vibrato?: VibratoOptions): Promise<this>;
|
|
121
|
+
/**
|
|
122
|
+
* Sets the tremolo options on the audio.
|
|
123
|
+
*
|
|
124
|
+
* @param {TremoloOptions} [tremolo] - The tremolo settings to apply (frequency, depth).
|
|
125
|
+
* @returns {Promise<this>} - Returns the current instance of the Filters class for method chaining.
|
|
126
|
+
*/
|
|
127
|
+
setTremolo(tremolo?: TremoloOptions): Promise<this>;
|
|
128
|
+
/**
|
|
129
|
+
* Sets the stereo channel mix options on the audio.
|
|
130
|
+
*
|
|
131
|
+
* @param {ChannelMixOptions} [channelMix] - The channel mix settings to apply.
|
|
132
|
+
* @returns {Promise<this>} - Returns the current instance of the Filters class for method chaining.
|
|
133
|
+
*/
|
|
134
|
+
setChannelMix(channelMix?: ChannelMixOptions): Promise<this>;
|
|
135
|
+
/**
|
|
136
|
+
* Sets the low pass filter options on the audio.
|
|
137
|
+
*
|
|
138
|
+
* @param {LowPassOptions} [lowPass] - The low pass settings to apply.
|
|
139
|
+
* @returns {Promise<this>} - Returns the current instance of the Filters class for method chaining.
|
|
140
|
+
*/
|
|
141
|
+
setLowPass(lowPass?: LowPassOptions): Promise<this>;
|
|
142
|
+
/**
|
|
143
|
+
* Sets plugin-provided filter options on the audio.
|
|
144
|
+
*
|
|
145
|
+
* @param {Record<string, unknown>} [pluginFilters] - Plugin filter settings keyed by plugin filter name.
|
|
146
|
+
* @param {string | string[]} [requiredPluginNames] - The plugin name(s) that can handle the provided filters. Defaults to LavaDSPX.
|
|
147
|
+
* @returns {Promise<this>} - Returns the current instance of the Filters class for method chaining.
|
|
148
|
+
*/
|
|
149
|
+
setPluginFilters(pluginFilters?: Record<string, unknown>, requiredPluginNames?: string | string[]): Promise<this>;
|
|
115
150
|
/**
|
|
116
151
|
* Sets the own rotation options effect to the audio.
|
|
117
152
|
*
|
|
@@ -5,12 +5,17 @@ const filtersEqualizers_1 = require("../utils/filtersEqualizers");
|
|
|
5
5
|
const Enums_1 = require("./Enums");
|
|
6
6
|
const MagmastreamError_1 = require("./MagmastreamError");
|
|
7
7
|
class Filters {
|
|
8
|
+
static defaultPluginFilterPlugins = ["lavadspx-plugin", "LavaDSPX-Plugin"];
|
|
8
9
|
distortion;
|
|
9
10
|
equalizer;
|
|
10
11
|
karaoke;
|
|
11
12
|
rotation;
|
|
12
13
|
timescale;
|
|
14
|
+
tremoloOptions;
|
|
13
15
|
vibrato;
|
|
16
|
+
channelMix;
|
|
17
|
+
lowPass;
|
|
18
|
+
pluginFilters;
|
|
14
19
|
reverb;
|
|
15
20
|
volume;
|
|
16
21
|
bassBoostlevel;
|
|
@@ -23,7 +28,11 @@ class Filters {
|
|
|
23
28
|
this.karaoke = null;
|
|
24
29
|
this.rotation = null;
|
|
25
30
|
this.timescale = null;
|
|
31
|
+
this.tremoloOptions = null;
|
|
26
32
|
this.vibrato = null;
|
|
33
|
+
this.channelMix = null;
|
|
34
|
+
this.lowPass = null;
|
|
35
|
+
this.pluginFilters = {};
|
|
27
36
|
this.reverb = null;
|
|
28
37
|
this.volume = 1.0;
|
|
29
38
|
this.bassBoostlevel = 0;
|
|
@@ -48,7 +57,7 @@ class Filters {
|
|
|
48
57
|
* of the Filters class for method chaining.
|
|
49
58
|
*/
|
|
50
59
|
async updateFilters() {
|
|
51
|
-
const { distortion, equalizer, karaoke, rotation, timescale, vibrato, volume } = this;
|
|
60
|
+
const { channelMix, distortion, equalizer, karaoke, lowPass, pluginFilters, rotation, timescale, tremoloOptions, vibrato, volume } = this;
|
|
52
61
|
try {
|
|
53
62
|
await this.player.node.rest.updatePlayer({
|
|
54
63
|
data: {
|
|
@@ -56,10 +65,14 @@ class Filters {
|
|
|
56
65
|
distortion,
|
|
57
66
|
equalizer,
|
|
58
67
|
karaoke,
|
|
68
|
+
lowPass,
|
|
69
|
+
pluginFilters,
|
|
59
70
|
rotation,
|
|
60
71
|
timescale,
|
|
72
|
+
tremolo: tremoloOptions,
|
|
61
73
|
vibrato,
|
|
62
74
|
volume,
|
|
75
|
+
channelMix,
|
|
63
76
|
},
|
|
64
77
|
},
|
|
65
78
|
guildId: this.player.guildId,
|
|
@@ -101,6 +114,18 @@ class Filters {
|
|
|
101
114
|
details: { action: "change" },
|
|
102
115
|
});
|
|
103
116
|
}
|
|
117
|
+
assertPluginFiltersSupported(pluginNames) {
|
|
118
|
+
const requiredPlugins = Array.isArray(pluginNames) ? pluginNames : [pluginNames];
|
|
119
|
+
const availablePlugins = this.player.node.info?.plugins ?? [];
|
|
120
|
+
const hasRequiredPlugin = requiredPlugins.some((name) => availablePlugins.some((plugin) => plugin.name.toLowerCase() === name.toLowerCase()));
|
|
121
|
+
if (!hasRequiredPlugin) {
|
|
122
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
123
|
+
code: Enums_1.MagmaStreamErrorCode.NODE_PLUGIN_ERROR,
|
|
124
|
+
message: `One of the following plugins must be present in the lavalink node: ${requiredPlugins.map((name) => `"${name}"`).join(" or ")}.`,
|
|
125
|
+
context: { identifier: this.player.node.options.identifier },
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
104
129
|
/**
|
|
105
130
|
* Sets the status of a specific filter.
|
|
106
131
|
*
|
|
@@ -146,7 +171,11 @@ class Filters {
|
|
|
146
171
|
await this.setKaraoke(null);
|
|
147
172
|
await this.setRotation(null);
|
|
148
173
|
await this.setTimescale(null);
|
|
174
|
+
await this.setTremolo(null);
|
|
149
175
|
await this.setVibrato(null);
|
|
176
|
+
await this.setChannelMix(null);
|
|
177
|
+
await this.setLowPass(null);
|
|
178
|
+
await this.setPluginFilters({});
|
|
150
179
|
await this.updateFilters();
|
|
151
180
|
this.emitPlayersTasteUpdate(oldPlayer);
|
|
152
181
|
return this;
|
|
@@ -215,6 +244,63 @@ class Filters {
|
|
|
215
244
|
this.emitPlayersTasteUpdate(oldPlayer);
|
|
216
245
|
return this;
|
|
217
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Sets the tremolo options on the audio.
|
|
249
|
+
*
|
|
250
|
+
* @param {TremoloOptions} [tremolo] - The tremolo settings to apply (frequency, depth).
|
|
251
|
+
* @returns {Promise<this>} - Returns the current instance of the Filters class for method chaining.
|
|
252
|
+
*/
|
|
253
|
+
async setTremolo(tremolo) {
|
|
254
|
+
const oldPlayer = { ...this };
|
|
255
|
+
await this.applyFilter({ property: "tremoloOptions", value: tremolo ?? null });
|
|
256
|
+
this.setFilterStatus(Enums_1.AvailableFilters.SetTremolo, !!tremolo);
|
|
257
|
+
this.emitPlayersTasteUpdate(oldPlayer);
|
|
258
|
+
return this;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Sets the stereo channel mix options on the audio.
|
|
262
|
+
*
|
|
263
|
+
* @param {ChannelMixOptions} [channelMix] - The channel mix settings to apply.
|
|
264
|
+
* @returns {Promise<this>} - Returns the current instance of the Filters class for method chaining.
|
|
265
|
+
*/
|
|
266
|
+
async setChannelMix(channelMix) {
|
|
267
|
+
const oldPlayer = { ...this };
|
|
268
|
+
await this.applyFilter({ property: "channelMix", value: channelMix ?? null });
|
|
269
|
+
this.setFilterStatus(Enums_1.AvailableFilters.SetChannelMix, !!channelMix);
|
|
270
|
+
this.emitPlayersTasteUpdate(oldPlayer);
|
|
271
|
+
return this;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Sets the low pass filter options on the audio.
|
|
275
|
+
*
|
|
276
|
+
* @param {LowPassOptions} [lowPass] - The low pass settings to apply.
|
|
277
|
+
* @returns {Promise<this>} - Returns the current instance of the Filters class for method chaining.
|
|
278
|
+
*/
|
|
279
|
+
async setLowPass(lowPass) {
|
|
280
|
+
const oldPlayer = { ...this };
|
|
281
|
+
await this.applyFilter({ property: "lowPass", value: lowPass ?? null });
|
|
282
|
+
this.setFilterStatus(Enums_1.AvailableFilters.SetLowPass, !!lowPass);
|
|
283
|
+
this.emitPlayersTasteUpdate(oldPlayer);
|
|
284
|
+
return this;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Sets plugin-provided filter options on the audio.
|
|
288
|
+
*
|
|
289
|
+
* @param {Record<string, unknown>} [pluginFilters] - Plugin filter settings keyed by plugin filter name.
|
|
290
|
+
* @param {string | string[]} [requiredPluginNames] - The plugin name(s) that can handle the provided filters. Defaults to LavaDSPX.
|
|
291
|
+
* @returns {Promise<this>} - Returns the current instance of the Filters class for method chaining.
|
|
292
|
+
*/
|
|
293
|
+
async setPluginFilters(pluginFilters, requiredPluginNames = Filters.defaultPluginFilterPlugins) {
|
|
294
|
+
const oldPlayer = { ...this };
|
|
295
|
+
const nextPluginFilters = pluginFilters ?? {};
|
|
296
|
+
if (Object.keys(nextPluginFilters).length > 0) {
|
|
297
|
+
this.assertPluginFiltersSupported(requiredPluginNames);
|
|
298
|
+
}
|
|
299
|
+
await this.applyFilter({ property: "pluginFilters", value: nextPluginFilters });
|
|
300
|
+
this.setFilterStatus(Enums_1.AvailableFilters.PluginFilters, Object.keys(nextPluginFilters).length > 0);
|
|
301
|
+
this.emitPlayersTasteUpdate(oldPlayer);
|
|
302
|
+
return this;
|
|
303
|
+
}
|
|
218
304
|
/**
|
|
219
305
|
* Sets the own rotation options effect to the audio.
|
|
220
306
|
*
|
|
@@ -557,7 +643,7 @@ class Filters {
|
|
|
557
643
|
*/
|
|
558
644
|
async tremolo(status) {
|
|
559
645
|
const oldPlayer = { ...this };
|
|
560
|
-
await this.applyFilter({ property: "
|
|
646
|
+
await this.applyFilter({ property: "tremoloOptions", value: status ? { frequency: 5, depth: 0.5 } : null });
|
|
561
647
|
this.setFilterStatus(Enums_1.AvailableFilters.Tremolo, status);
|
|
562
648
|
this.emitPlayersTasteUpdate(oldPlayer);
|
|
563
649
|
return this;
|
|
@@ -231,6 +231,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
231
231
|
result = { loadType: lavalinkResponse.loadType, tracks };
|
|
232
232
|
break;
|
|
233
233
|
}
|
|
234
|
+
case Enums_1.LoadTypes.Episode:
|
|
234
235
|
case Enums_1.LoadTypes.Short:
|
|
235
236
|
case Enums_1.LoadTypes.Track: {
|
|
236
237
|
const track = Utils_1.TrackUtils.build(lavalinkResponse.data, requester);
|
|
@@ -250,7 +251,8 @@ class Manager extends events_1.EventEmitter {
|
|
|
250
251
|
tracks,
|
|
251
252
|
playlist: {
|
|
252
253
|
name: playlistData.info.name,
|
|
253
|
-
playlistInfo: playlistData.
|
|
254
|
+
playlistInfo: playlistData.info,
|
|
255
|
+
pluginInfo: playlistData.pluginInfo,
|
|
254
256
|
requester: requester,
|
|
255
257
|
tracks,
|
|
256
258
|
duration: tracks.reduce((acc, cur) => acc + (cur.duration || 0), 0),
|
|
@@ -263,7 +265,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
263
265
|
}
|
|
264
266
|
if (this.options.normalizeYouTubeTitles && "tracks" in result) {
|
|
265
267
|
const processTrack = (track) => {
|
|
266
|
-
if (!YOUTUBE_URL_PATTERN.test(track.uri))
|
|
268
|
+
if (!track.uri || !YOUTUBE_URL_PATTERN.test(track.uri))
|
|
267
269
|
return track;
|
|
268
270
|
const { cleanTitle, cleanAuthor } = this.parseYouTubeTitle(track.title, track.author);
|
|
269
271
|
track.title = cleanTitle;
|
|
@@ -406,23 +408,21 @@ class Manager extends events_1.EventEmitter {
|
|
|
406
408
|
*/
|
|
407
409
|
async decodeTracks(tracks) {
|
|
408
410
|
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Decoding tracks: ${Utils_1.JSONUtils.safe(tracks, 2)}`);
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
return resolve(decodedTrackData);
|
|
425
|
-
});
|
|
411
|
+
const node = this.useableNode;
|
|
412
|
+
if (!node) {
|
|
413
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
414
|
+
code: Enums_1.MagmaStreamErrorCode.MANAGER_NO_NODES,
|
|
415
|
+
message: "No available nodes to decode tracks.",
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
const decodedTrackData = await node.rest.decodeTracks(tracks);
|
|
419
|
+
if (!decodedTrackData) {
|
|
420
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
421
|
+
code: Enums_1.MagmaStreamErrorCode.REST_REQUEST_FAILED,
|
|
422
|
+
message: "No decoded tracks returned from node.",
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
return decodedTrackData;
|
|
426
426
|
}
|
|
427
427
|
/**
|
|
428
428
|
* Decodes a base64 encoded track and returns a TrackData.
|
|
@@ -510,7 +510,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
510
510
|
const player = this.create(playerOptions);
|
|
511
511
|
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Recreating player: ${guildId}`);
|
|
512
512
|
if (state.isAutoplay) {
|
|
513
|
-
const savedUser = state.data?.clientUser;
|
|
513
|
+
const savedUser = (state.data?.Internal_AutoplayUser || state.data?.clientUser);
|
|
514
514
|
if (savedUser) {
|
|
515
515
|
const autoPlayUser = await player.manager.resolveUser(savedUser);
|
|
516
516
|
player.setAutoplay(true, autoPlayUser, state.autoplayTries);
|
|
@@ -607,14 +607,18 @@ class Manager extends events_1.EventEmitter {
|
|
|
607
607
|
const filterActions = {
|
|
608
608
|
bassboost: () => player.filters.bassBoost(state.filters.bassBoostlevel),
|
|
609
609
|
distort: (isEnabled) => player.filters.distort(isEnabled),
|
|
610
|
+
pluginFilters: () => player.filters.setPluginFilters(state.filters.pluginFilters),
|
|
611
|
+
setChannelMix: () => player.filters.setChannelMix(state.filters.channelMix),
|
|
610
612
|
setDistortion: () => player.filters.setDistortion(state.filters.distortion),
|
|
611
613
|
eightD: (isEnabled) => player.filters.eightD(isEnabled),
|
|
612
614
|
setKaraoke: () => player.filters.setKaraoke(state.filters.karaoke),
|
|
615
|
+
setLowPass: () => player.filters.setLowPass(state.filters.lowPass),
|
|
613
616
|
nightcore: (isEnabled) => player.filters.nightcore(isEnabled),
|
|
614
617
|
slowmo: (isEnabled) => player.filters.slowmo(isEnabled),
|
|
615
618
|
soft: (isEnabled) => player.filters.soft(isEnabled),
|
|
616
619
|
trebleBass: (isEnabled) => player.filters.trebleBass(isEnabled),
|
|
617
620
|
setTimescale: () => player.filters.setTimescale(state.filters.timescale),
|
|
621
|
+
setTremolo: () => player.filters.setTremolo(state.filters.tremolo),
|
|
618
622
|
tv: (isEnabled) => player.filters.tv(isEnabled),
|
|
619
623
|
vibrato: () => player.filters.setVibrato(state.filters.vibrato),
|
|
620
624
|
vaporwave: (isEnabled) => player.filters.vaporwave(isEnabled),
|
|
@@ -158,6 +158,7 @@ export declare class Node {
|
|
|
158
158
|
* @private
|
|
159
159
|
*/
|
|
160
160
|
protected handleEvent(payload: PlayerEvent & PlayerEvents): Promise<void>;
|
|
161
|
+
private handleNodeLinkEvent;
|
|
161
162
|
/**
|
|
162
163
|
* Emitted when a new track starts playing.
|
|
163
164
|
* @param {Player} player The player that started playing the track.
|
package/dist/structures/Node.js
CHANGED
|
@@ -371,7 +371,10 @@ class Node {
|
|
|
371
371
|
* @param request - The incoming message.
|
|
372
372
|
*/
|
|
373
373
|
upgrade(request) {
|
|
374
|
-
|
|
374
|
+
const nodeLinkHeader = request.headers.iamnodelink ?? request.headers.isnodelink;
|
|
375
|
+
const isNodeLinkHeader = Array.isArray(nodeLinkHeader) ? nodeLinkHeader.some((value) => value === "true") : nodeLinkHeader === "true";
|
|
376
|
+
this.isNodeLink = Boolean(this.options.isNodeLink) || isNodeLinkHeader;
|
|
377
|
+
this.rest.isNodeLink = this.isNodeLink;
|
|
375
378
|
}
|
|
376
379
|
/**
|
|
377
380
|
* Handles the "open" event emitted by the WebSocket connection.
|
|
@@ -507,6 +510,8 @@ class Node {
|
|
|
507
510
|
this.sessionId = payload.sessionId;
|
|
508
511
|
await this.updateSessionId();
|
|
509
512
|
this.info = await this.fetchInfo();
|
|
513
|
+
this.isNodeLink = this.isNodeLink || Boolean(this.info.isNodelink);
|
|
514
|
+
this.rest.isNodeLink = this.isNodeLink;
|
|
510
515
|
if (payload.resumed || !hadPreviousSession) {
|
|
511
516
|
await this.manager.loadPlayerStates(this.options.identifier);
|
|
512
517
|
}
|
|
@@ -587,11 +592,71 @@ class Node {
|
|
|
587
592
|
this.lyricsLine(player, track, payload);
|
|
588
593
|
break;
|
|
589
594
|
default:
|
|
595
|
+
if (this.isNodeLink && this.handleNodeLinkEvent(player, payload))
|
|
596
|
+
return;
|
|
590
597
|
error = new Error(`Node#event unknown event '${type}'.`);
|
|
591
598
|
this.manager.emit(Enums_1.ManagerEventTypes.NodeError, this, error);
|
|
592
599
|
break;
|
|
593
600
|
}
|
|
594
601
|
}
|
|
602
|
+
handleNodeLinkEvent(player, payload) {
|
|
603
|
+
switch (payload.type) {
|
|
604
|
+
case "VolumeChangedEvent": {
|
|
605
|
+
if (typeof payload.volume === "number") {
|
|
606
|
+
const oldVolume = player.volume;
|
|
607
|
+
const oldPlayer = { ...player };
|
|
608
|
+
player.volume = payload.volume;
|
|
609
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, player, {
|
|
610
|
+
changeType: Enums_1.PlayerStateEventTypes.VolumeChange,
|
|
611
|
+
details: {
|
|
612
|
+
type: "volume",
|
|
613
|
+
action: "adjust",
|
|
614
|
+
previousVolume: oldVolume,
|
|
615
|
+
currentVolume: player.volume,
|
|
616
|
+
},
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
return true;
|
|
620
|
+
}
|
|
621
|
+
case "PauseEvent": {
|
|
622
|
+
if (typeof payload.paused === "boolean") {
|
|
623
|
+
const oldPaused = player.paused;
|
|
624
|
+
const oldPlayer = { ...player };
|
|
625
|
+
player.paused = payload.paused;
|
|
626
|
+
player.playing = !payload.paused;
|
|
627
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, player, {
|
|
628
|
+
changeType: Enums_1.PlayerStateEventTypes.PauseChange,
|
|
629
|
+
details: {
|
|
630
|
+
type: "pause",
|
|
631
|
+
action: payload.paused ? "pause" : "resume",
|
|
632
|
+
previousPause: oldPaused,
|
|
633
|
+
currentPause: player.paused,
|
|
634
|
+
},
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
return true;
|
|
638
|
+
}
|
|
639
|
+
case "SeekEvent":
|
|
640
|
+
if (typeof payload.position === "number")
|
|
641
|
+
player.position = payload.position;
|
|
642
|
+
return true;
|
|
643
|
+
case "PlayerCreatedEvent":
|
|
644
|
+
case "PlayerDestroyedEvent":
|
|
645
|
+
case "PlayerConnectedEvent":
|
|
646
|
+
case "PlayerReconnectingEvent":
|
|
647
|
+
case "ConnectionStatusEvent":
|
|
648
|
+
case "FiltersChangedEvent":
|
|
649
|
+
case "MixStartedEvent":
|
|
650
|
+
case "MixEndedEvent":
|
|
651
|
+
case "EternalBoxInfoEvent":
|
|
652
|
+
case "EternalBoxJumpEvent":
|
|
653
|
+
case "StreamMetadataEvent":
|
|
654
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] NodeLink player event for guild ${player.guildId}: ${Utils_1.JSONUtils.safe(payload, 2)}`);
|
|
655
|
+
return true;
|
|
656
|
+
default:
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
595
660
|
/**
|
|
596
661
|
* Emitted when a new track starts playing.
|
|
597
662
|
* @param {Player} player The player that started playing the track.
|
|
@@ -849,7 +914,7 @@ class Node {
|
|
|
849
914
|
});
|
|
850
915
|
}
|
|
851
916
|
if (this.isNodeLink) {
|
|
852
|
-
return (await this.rest.get(`/v4/loadlyrics?encodedTrack=${encodeURIComponent(track.track)}${language ? `&
|
|
917
|
+
return (await this.rest.get(`/v4/loadlyrics?encodedTrack=${encodeURIComponent(track.track)}${language ? `&lang=${encodeURIComponent(language)}` : ""}`));
|
|
853
918
|
}
|
|
854
919
|
const requiredPlugins = ["lavalyrics-plugin"];
|
|
855
920
|
for (const plugin of requiredPlugins) {
|
|
@@ -886,13 +951,8 @@ class Node {
|
|
|
886
951
|
context: { identifier: this.options.identifier },
|
|
887
952
|
});
|
|
888
953
|
}
|
|
889
|
-
if (this.isNodeLink)
|
|
890
|
-
|
|
891
|
-
code: Enums_1.MagmaStreamErrorCode.NODE_PROTOCOL_ERROR,
|
|
892
|
-
message: `The node is a NodeLink, cannot subscribe to lyrics.`,
|
|
893
|
-
context: { identifier: this.options.identifier },
|
|
894
|
-
});
|
|
895
|
-
}
|
|
954
|
+
if (this.isNodeLink)
|
|
955
|
+
return await this.rest.post(`/v4/sessions/${this.sessionId}/players/${guildId}/lyrics/subscribe?skipTrackSource=${skipTrackSource}`, {});
|
|
896
956
|
const requiredPlugins = ["lavalyrics-plugin"];
|
|
897
957
|
for (const plugin of requiredPlugins) {
|
|
898
958
|
if (!this.info.plugins.some((p) => p.name === plugin)) {
|
|
@@ -938,13 +998,8 @@ class Node {
|
|
|
938
998
|
context: { identifier: this.options.identifier },
|
|
939
999
|
});
|
|
940
1000
|
}
|
|
941
|
-
if (this.isNodeLink)
|
|
942
|
-
|
|
943
|
-
code: Enums_1.MagmaStreamErrorCode.NODE_PROTOCOL_ERROR,
|
|
944
|
-
message: `The node is a NodeLink, cannot unsubscribe from lyrics.`,
|
|
945
|
-
context: { identifier: this.options.identifier },
|
|
946
|
-
});
|
|
947
|
-
}
|
|
1001
|
+
if (this.isNodeLink)
|
|
1002
|
+
return await this.rest.delete(`/v4/sessions/${this.sessionId}/players/${guildId}/lyrics/subscribe`);
|
|
948
1003
|
if (!this.info.plugins.some((plugin) => plugin.name === "java-lyrics-plugin")) {
|
|
949
1004
|
throw new MagmastreamError_1.MagmaStreamError({
|
|
950
1005
|
code: Enums_1.MagmaStreamErrorCode.NODE_PLUGIN_ERROR,
|
|
@@ -1079,7 +1134,7 @@ class Node {
|
|
|
1079
1134
|
* @returns {Promise<LavalinkInfo>} A promise that resolves to the Lavalink node information.
|
|
1080
1135
|
*/
|
|
1081
1136
|
async fetchInfo() {
|
|
1082
|
-
return
|
|
1137
|
+
return await this.rest.getInfo();
|
|
1083
1138
|
}
|
|
1084
1139
|
/**
|
|
1085
1140
|
* Gets the current sponsorblock segments for a player.
|
|
@@ -1088,6 +1143,22 @@ class Node {
|
|
|
1088
1143
|
* @throws {RangeError} If the sponsorblock-plugin is not available in the Lavalink node.
|
|
1089
1144
|
*/
|
|
1090
1145
|
async getSponsorBlock(player) {
|
|
1146
|
+
if (this.isNodeLink) {
|
|
1147
|
+
try {
|
|
1148
|
+
const state = (await this.rest.get(`/v4/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock`));
|
|
1149
|
+
return state.categories.map((segment) => segment);
|
|
1150
|
+
}
|
|
1151
|
+
catch (err) {
|
|
1152
|
+
throw err instanceof MagmastreamError_1.MagmaStreamError
|
|
1153
|
+
? err
|
|
1154
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
1155
|
+
code: Enums_1.MagmaStreamErrorCode.NODE_PROTOCOL_ERROR,
|
|
1156
|
+
message: "Failed to fetch NodeLink SponsorBlock state.",
|
|
1157
|
+
cause: err instanceof Error ? err : undefined,
|
|
1158
|
+
context: { identifier: this.options.identifier, guildId: player.guildId },
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1091
1162
|
if (!this.info.plugins.some((plugin) => plugin.name === "sponsorblock-plugin")) {
|
|
1092
1163
|
throw new MagmastreamError_1.MagmaStreamError({
|
|
1093
1164
|
code: Enums_1.MagmaStreamErrorCode.NODE_PLUGIN_ERROR,
|
|
@@ -1124,13 +1195,6 @@ class Node {
|
|
|
1124
1195
|
* ```
|
|
1125
1196
|
*/
|
|
1126
1197
|
async setSponsorBlock(player, segments = [Enums_1.SponsorBlockSegment.Sponsor, Enums_1.SponsorBlockSegment.SelfPromo]) {
|
|
1127
|
-
if (!this.info.plugins.some((plugin) => plugin.name === "sponsorblock-plugin")) {
|
|
1128
|
-
throw new MagmastreamError_1.MagmaStreamError({
|
|
1129
|
-
code: Enums_1.MagmaStreamErrorCode.NODE_PLUGIN_ERROR,
|
|
1130
|
-
message: `The plugin "sponsorblock-plugin" must be present in the lavalink node to set SponsorBlock segments.`,
|
|
1131
|
-
context: { identifier: this.options.identifier, guildId: player.guildId },
|
|
1132
|
-
});
|
|
1133
|
-
}
|
|
1134
1198
|
if (!segments.length) {
|
|
1135
1199
|
throw new MagmastreamError_1.MagmaStreamError({
|
|
1136
1200
|
code: Enums_1.MagmaStreamErrorCode.NODE_PROTOCOL_ERROR,
|
|
@@ -1145,8 +1209,35 @@ class Node {
|
|
|
1145
1209
|
context: { identifier: this.options.identifier, guildId: player.guildId, invalidSegments: segments },
|
|
1146
1210
|
});
|
|
1147
1211
|
}
|
|
1212
|
+
const categories = segments.map((segment) => segment.toLowerCase());
|
|
1213
|
+
if (this.isNodeLink) {
|
|
1214
|
+
try {
|
|
1215
|
+
await this.rest.patch(`/v4/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock`, {
|
|
1216
|
+
enabled: true,
|
|
1217
|
+
categories,
|
|
1218
|
+
});
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
catch (err) {
|
|
1222
|
+
throw err instanceof MagmastreamError_1.MagmaStreamError
|
|
1223
|
+
? err
|
|
1224
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
1225
|
+
code: Enums_1.MagmaStreamErrorCode.NODE_PROTOCOL_ERROR,
|
|
1226
|
+
message: "Failed to set NodeLink SponsorBlock categories.",
|
|
1227
|
+
cause: err instanceof Error ? err : undefined,
|
|
1228
|
+
context: { identifier: this.options.identifier, guildId: player.guildId, segments },
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
if (!this.info.plugins.some((plugin) => plugin.name === "sponsorblock-plugin")) {
|
|
1233
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
1234
|
+
code: Enums_1.MagmaStreamErrorCode.NODE_PLUGIN_ERROR,
|
|
1235
|
+
message: `The plugin "sponsorblock-plugin" must be present in the lavalink node to set SponsorBlock segments.`,
|
|
1236
|
+
context: { identifier: this.options.identifier, guildId: player.guildId },
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1148
1239
|
try {
|
|
1149
|
-
await this.rest.put(`/v4/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`,
|
|
1240
|
+
await this.rest.put(`/v4/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`, categories);
|
|
1150
1241
|
}
|
|
1151
1242
|
catch (err) {
|
|
1152
1243
|
throw err instanceof MagmastreamError_1.MagmaStreamError
|
|
@@ -1166,6 +1257,22 @@ class Node {
|
|
|
1166
1257
|
* @throws {RangeError} If the sponsorblock-plugin is not available in the Lavalink node.
|
|
1167
1258
|
*/
|
|
1168
1259
|
async deleteSponsorBlock(player) {
|
|
1260
|
+
if (this.isNodeLink) {
|
|
1261
|
+
try {
|
|
1262
|
+
await this.rest.delete(`/v4/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock`);
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
catch (err) {
|
|
1266
|
+
throw err instanceof MagmastreamError_1.MagmaStreamError
|
|
1267
|
+
? err
|
|
1268
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
1269
|
+
code: Enums_1.MagmaStreamErrorCode.NODE_PROTOCOL_ERROR,
|
|
1270
|
+
message: "Failed to delete NodeLink SponsorBlock state.",
|
|
1271
|
+
cause: err instanceof Error ? err : undefined,
|
|
1272
|
+
context: { identifier: this.options.identifier, guildId: player.guildId },
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1169
1276
|
if (!this.info.plugins.some((plugin) => plugin.name === "sponsorblock-plugin")) {
|
|
1170
1277
|
throw new MagmastreamError_1.MagmaStreamError({
|
|
1171
1278
|
code: Enums_1.MagmaStreamErrorCode.NODE_PLUGIN_ERROR,
|
|
@@ -58,6 +58,7 @@ export declare class Player {
|
|
|
58
58
|
protected voiceReceiverReconnectTimeout?: NodeJS.Timeout;
|
|
59
59
|
protected voiceReceiverAttempt: number;
|
|
60
60
|
protected voiceReceiverReconnectTries: number;
|
|
61
|
+
private readonly voiceReceiverStreams;
|
|
61
62
|
/**
|
|
62
63
|
* Creates a new player, returns one if it already exists.
|
|
63
64
|
* @param options The player options.
|
|
@@ -329,10 +330,13 @@ export declare class Player {
|
|
|
329
330
|
private openVoiceReceiver;
|
|
330
331
|
/**
|
|
331
332
|
* Handles a voice receiver message.
|
|
332
|
-
* @param {
|
|
333
|
+
* @param {RawData} payload - The payload to handle.
|
|
333
334
|
* @returns {Promise<void>} - A promise that resolves when the voice receiver message is handled.
|
|
334
335
|
*/
|
|
335
336
|
private onVoiceReceiverMessage;
|
|
337
|
+
private toVoiceReceiverBuffer;
|
|
338
|
+
private parseNodeLinkVoiceFrame;
|
|
339
|
+
private handleNodeLinkVoiceFrame;
|
|
336
340
|
/**
|
|
337
341
|
* Handles a voice receiver error.
|
|
338
342
|
* @param {Error} error - The error to handle.
|