magmastream 2.9.0-dev.31 → 2.9.0-dev.33

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Collection } from '@discordjs/collection';
2
2
  import { GatewayVoiceStateUpdate } from 'discord-api-types/v10';
3
3
  import { EventEmitter } from 'events';
4
- import WebSocket from 'ws';
4
+ import WebSocket, { WebSocket as WebSocket$1 } from 'ws';
5
5
  import { User, ClientUser, Message, Client } from 'discord.js';
6
6
  import { Redis } from 'ioredis';
7
7
  import { Client as Client$1 } from 'eris';
@@ -28,6 +28,8 @@ declare class Rest {
28
28
  private readonly url;
29
29
  /** The Manager instance. */
30
30
  manager: Manager;
31
+ /** Whether the node is a NodeLink. */
32
+ isNodeLink: boolean;
31
33
  constructor(node: Node$1, manager: Manager);
32
34
  /**
33
35
  * Sets the session ID.
@@ -285,7 +287,12 @@ declare enum ManagerEventTypes {
285
287
  TrackEnd = "trackEnd",
286
288
  TrackError = "trackError",
287
289
  TrackStart = "trackStart",
288
- TrackStuck = "trackStuck"
290
+ TrackStuck = "trackStuck",
291
+ VoiceReceiverDisconnect = "voiceReceiverDisconnect",
292
+ VoiceReceiverConnect = "voiceReceiverConnect",
293
+ VoiceReceiverError = "voiceReceiverError",
294
+ VoiceReceiverStartSpeaking = "voiceReceiverStartSpeaking",
295
+ VoiceReceiverEndSpeaking = "voiceReceiverEndSpeaking"
289
296
  }
290
297
  /**
291
298
  * Track End Reason Enum
@@ -729,15 +736,40 @@ interface LavaPlayer {
729
736
  filters: Record<string, unknown>;
730
737
  }
731
738
  /**
732
- * Search Result
739
+ * Error or Empty Search Result
733
740
  */
734
- interface SearchResult {
741
+ interface ErrorOrEmptySearchResult {
735
742
  /** The load type of the result. */
736
- loadType: LoadTypes;
737
- /** The array of tracks from the result. */
743
+ loadType: LoadTypes.Empty | LoadTypes.Error;
744
+ }
745
+ /**
746
+ * Track Search Result
747
+ */
748
+ interface TrackSearchResult {
749
+ /** The load type is always 'track' */
750
+ loadType: LoadTypes.Track;
751
+ /** The track obtained */
752
+ tracks: [Track];
753
+ }
754
+ /**
755
+ * Search Result
756
+ */
757
+ interface SearchSearchResult {
758
+ /** The load type is always 'search' */
759
+ loadType: LoadTypes.Search;
760
+ /** The tracks of the search result */
761
+ tracks: Track[];
762
+ }
763
+ /**
764
+ * Playlist Search Result
765
+ */
766
+ interface PlaylistSearchResult {
767
+ /** The playlist load type */
768
+ loadType: LoadTypes.Playlist;
769
+ /** The tracks of the playlist */
738
770
  tracks: Track[];
739
- /** The playlist info if the load type is 'playlist'. */
740
- playlist?: PlaylistData;
771
+ /** The playlist info */
772
+ playlist: PlaylistData;
741
773
  }
742
774
  /**
743
775
  * Playlist Data
@@ -801,6 +833,11 @@ interface ManagerEvents {
801
833
  [ManagerEventTypes.TrackError]: [player: Player, track: Track, payload: TrackExceptionEvent];
802
834
  [ManagerEventTypes.TrackStart]: [player: Player, track: Track, payload: TrackStartEvent];
803
835
  [ManagerEventTypes.TrackStuck]: [player: Player, track: Track, payload: TrackStuckEvent];
836
+ [ManagerEventTypes.VoiceReceiverDisconnect]: [player: Player];
837
+ [ManagerEventTypes.VoiceReceiverConnect]: [player: Player];
838
+ [ManagerEventTypes.VoiceReceiverError]: [player: Player, error: Error];
839
+ [ManagerEventTypes.VoiceReceiverStartSpeaking]: [player: Player, data: unknown];
840
+ [ManagerEventTypes.VoiceReceiverEndSpeaking]: [player: Player, data: unknown];
804
841
  }
805
842
  /**
806
843
  * Voice Packet
@@ -997,6 +1034,8 @@ interface NodeOptions {
997
1034
  apiRequestTimeoutMs?: number;
998
1035
  /** Priority of the node. */
999
1036
  nodePriority?: number;
1037
+ /** Whether the node is a NodeLink. */
1038
+ isNodeLink?: boolean;
1000
1039
  }
1001
1040
  /**
1002
1041
  * NodeOptions interface
@@ -1139,6 +1178,100 @@ interface Lyrics {
1139
1178
  lines: LyricsLine[];
1140
1179
  plugin: object[];
1141
1180
  }
1181
+ /**
1182
+ * NodeLink Get Lyrics Multiple interface
1183
+ */
1184
+ interface NodeLinkGetLyricsMultiple {
1185
+ loadType: "lyricsMultiple";
1186
+ data: NodeLinkGetLyricsData[];
1187
+ }
1188
+ /**
1189
+ * NodeLink Get Lyrics Empty interface
1190
+ */
1191
+ interface NodeLinkGetLyricsEmpty {
1192
+ loadType: "empty";
1193
+ data: Record<never, never>;
1194
+ }
1195
+ /**
1196
+ * NodeLink Get Lyrics Data interface
1197
+ */
1198
+ interface NodeLinkGetLyricsData {
1199
+ name: string;
1200
+ synced: boolean;
1201
+ data: {
1202
+ startTime?: number;
1203
+ endTime?: number;
1204
+ text: string;
1205
+ }[];
1206
+ rtl: boolean;
1207
+ }
1208
+ /**
1209
+ * NodeLink Get Lyrics Single interface
1210
+ */
1211
+ interface NodeLinkGetLyricsSingle {
1212
+ loadType: "lyricsSingle";
1213
+ data: NodeLinkGetLyricsData;
1214
+ }
1215
+ /**
1216
+ * NodeLink Get Lyrics Error interface
1217
+ */
1218
+ interface NodeLinkGetLyricsError {
1219
+ loadType: "error";
1220
+ data: {
1221
+ message: string;
1222
+ severity: Severity;
1223
+ cause: string;
1224
+ trace?: string;
1225
+ };
1226
+ }
1227
+ interface StartSpeakingEventVoiceReceiverData {
1228
+ /**
1229
+ * The user ID of the user who started speaking.
1230
+ */
1231
+ userId: string;
1232
+ /**
1233
+ * The guild ID of the guild where the user started speaking.
1234
+ */
1235
+ guildId: string;
1236
+ }
1237
+ interface EndSpeakingEventVoiceReceiverData {
1238
+ /**
1239
+ * The user ID of the user who stopped speaking.
1240
+ */
1241
+ userId: string;
1242
+ /**
1243
+ * The guild ID of the guild where the user stopped speaking.
1244
+ */
1245
+ guildId: string;
1246
+ /**
1247
+ * The audio data received from the user in base64.
1248
+ */
1249
+ data: string;
1250
+ /**
1251
+ * The type of the audio data. Can be either opus or pcm. Older versions may include ogg/opus.
1252
+ */
1253
+ type: "opus" | "pcm";
1254
+ }
1255
+ /**
1256
+ * Base Voice Receiver Event interface
1257
+ */
1258
+ interface BaseVoiceReceiverEvent {
1259
+ op: "speak";
1260
+ }
1261
+ /**
1262
+ * Start Speaking Event Voice Receiver interface
1263
+ */
1264
+ interface StartSpeakingEventVoiceReceiver extends BaseVoiceReceiverEvent {
1265
+ type: "startSpeakingEvent";
1266
+ data: StartSpeakingEventVoiceReceiverData;
1267
+ }
1268
+ /**
1269
+ * End Speaking Event Voice Receiver interface
1270
+ */
1271
+ interface EndSpeakingEventVoiceReceiver extends BaseVoiceReceiverEvent {
1272
+ type: "endSpeakingEvent";
1273
+ data: EndSpeakingEventVoiceReceiverData;
1274
+ }
1142
1275
  /**
1143
1276
  * PlayerOptions interface
1144
1277
  */
@@ -1179,25 +1312,6 @@ interface EqualizerBand {
1179
1312
  /** The gain amount being -0.25 to 1.00, 0.25 being double. */
1180
1313
  gain: number;
1181
1314
  }
1182
- /**
1183
- * PlayerStore interface
1184
- */
1185
- interface PlayerStore {
1186
- get(guildId: string): Promise<Player | undefined>;
1187
- set(guildId: string, player: Player): Promise<void>;
1188
- delete(guildId: string): Promise<void>;
1189
- keys(): Promise<string[]>;
1190
- has(guildId: string): Promise<boolean>;
1191
- filter(predicate: (player: Player, guildId: string) => boolean | Promise<boolean>): Promise<Map<string, Player>>;
1192
- find(predicate: (player: Player, guildId: string) => boolean | Promise<boolean>): Promise<Player | undefined>;
1193
- map<T>(callback: (player: Player, guildId: string) => T | Promise<T>): Promise<T[]>;
1194
- forEach(callback: (player: Player, guildId: string) => void | Promise<void>): Promise<void>;
1195
- some(predicate: (player: Player, guildId: string) => boolean | Promise<boolean>): Promise<boolean>;
1196
- every(predicate: (player: Player, guildId: string) => boolean | Promise<boolean>): Promise<boolean>;
1197
- size(): Promise<number>;
1198
- clear(): Promise<void>;
1199
- entries(): AsyncIterableIterator<[string, Player]>;
1200
- }
1201
1315
  /**
1202
1316
  * Queue interface
1203
1317
  */
@@ -1270,6 +1384,18 @@ type PlayerEvents = TrackStartEvent | TrackEndEvent | TrackStuckEvent | TrackExc
1270
1384
  * Load Type Enum type
1271
1385
  */
1272
1386
  type LoadType = keyof typeof LoadTypes;
1387
+ /**
1388
+ * NodeLink Get Lyrics Enum type
1389
+ */
1390
+ type NodeLinkGetLyrics = NodeLinkGetLyricsSingle | NodeLinkGetLyricsMultiple | NodeLinkGetLyricsEmpty | NodeLinkGetLyricsError;
1391
+ /**
1392
+ * Voice Receiver Event Enum type
1393
+ */
1394
+ type VoiceReceiverEvent = StartSpeakingEventVoiceReceiver | EndSpeakingEventVoiceReceiver;
1395
+ /**
1396
+ * Search Result Enum type
1397
+ */
1398
+ type SearchResult = TrackSearchResult | SearchSearchResult | PlaylistSearchResult | ErrorOrEmptySearchResult;
1273
1399
 
1274
1400
  declare class Node$1 {
1275
1401
  manager: Manager;
@@ -1285,11 +1411,14 @@ declare class Node$1 {
1285
1411
  readonly rest: Rest;
1286
1412
  /** Actual Lavalink information of the node. */
1287
1413
  info: LavalinkInfo | null;
1414
+ /** Whether the node is a NodeLink. */
1415
+ isNodeLink: boolean;
1288
1416
  private reconnectTimeout?;
1289
1417
  private reconnectAttempts;
1290
1418
  /**
1291
1419
  * Creates an instance of Node.
1292
- * @param options
1420
+ * @param manager - The manager for the node.
1421
+ * @param options - The options for the node.
1293
1422
  */
1294
1423
  constructor(manager: Manager, options: NodeOptions);
1295
1424
  /** Returns if connected to the Node. */
@@ -1361,6 +1490,12 @@ declare class Node$1 {
1361
1490
  * @emits {nodeDestroy} - Emits a nodeDestroy event if the maximum number of retry attempts is reached.
1362
1491
  */
1363
1492
  private reconnect;
1493
+ /**
1494
+ * Upgrades the node to a NodeLink.
1495
+ *
1496
+ * @param request - The incoming message.
1497
+ */
1498
+ private upgrade;
1364
1499
  /**
1365
1500
  * Handles the "open" event emitted by the WebSocket connection.
1366
1501
  *
@@ -1492,9 +1627,9 @@ declare class Node$1 {
1492
1627
  *
1493
1628
  * @param {Track} track - The track to fetch the lyrics for.
1494
1629
  * @param {boolean} [skipTrackSource=false] - Whether to skip using the track's source URL.
1495
- * @returns {Promise<Lyrics>} A promise that resolves with the lyrics data.
1630
+ * @returns {Promise<Lyrics | NodeLinkGetLyrics>} A promise that resolves with the lyrics data.
1496
1631
  */
1497
- getLyrics(track: Track, skipTrackSource?: boolean): Promise<Lyrics>;
1632
+ getLyrics(track: Track, skipTrackSource?: boolean): Promise<Lyrics | NodeLinkGetLyrics>;
1498
1633
  /**
1499
1634
  * Handles the event when a track becomes stuck during playback.
1500
1635
  * Stops the current track and emits a `trackStuck` event.
@@ -1808,6 +1943,11 @@ declare class Manager extends EventEmitter {
1808
1943
  * This is done to prevent stale state files from accumulating on the file system.
1809
1944
  */
1810
1945
  private cleanupInactivePlayers;
1946
+ /**
1947
+ * Clears all player states from the file system.
1948
+ * This is done to prevent stale state files from accumulating on the file system.
1949
+ */
1950
+ private clearAllPlayerStates;
1811
1951
  /**
1812
1952
  * Returns the nodes that has the least load.
1813
1953
  * The load is calculated by dividing the lavalink load by the number of cores.
@@ -1884,6 +2024,12 @@ declare class Player {
1884
2024
  private dynamicLoopInterval;
1885
2025
  dynamicRepeatIntervalMs: number | null;
1886
2026
  private static _manager;
2027
+ /** Should only be used when the node is a NodeLink */
2028
+ protected voiceReceiverWsClient: WebSocket$1 | null;
2029
+ protected isConnectToVoiceReceiver: boolean;
2030
+ protected voiceReceiverReconnectTimeout: NodeJS.Timeout | null;
2031
+ protected voiceReceiverAttempt: number;
2032
+ protected voiceReceiverReconnectTries: number;
1887
2033
  /**
1888
2034
  * Creates a new player, returns one if it already exists.
1889
2035
  * @param options The player options.
@@ -2116,11 +2262,51 @@ declare class Player {
2116
2262
  */
2117
2263
  getCurrentLyrics(skipTrackSource?: boolean): Promise<Lyrics>;
2118
2264
  /**
2119
- * Sleeps for a specified amount of time.
2120
- * @param ms The amount of time to sleep in milliseconds.
2121
- * @returns A promise that resolves after the specified amount of time.
2265
+ * Sets up the voice receiver for the player.
2266
+ * @returns {Promise<void>} - A promise that resolves when the voice receiver is set up.
2267
+ * @throws {Error} - If the node is not a NodeLink.
2122
2268
  */
2123
- private sleep;
2269
+ setupVoiceReceiver(): Promise<void>;
2270
+ /**
2271
+ * Removes the voice receiver for the player.
2272
+ * @returns {Promise<void>} - A promise that resolves when the voice receiver is removed.
2273
+ * @throws {Error} - If the node is not a NodeLink.
2274
+ */
2275
+ removeVoiceReceiver(): Promise<void>;
2276
+ /**
2277
+ * Closes the voice receiver for the player.
2278
+ * @param {number} code - The code to close the voice receiver with.
2279
+ * @param {string} reason - The reason to close the voice receiver with.
2280
+ * @returns {Promise<void>} - A promise that resolves when the voice receiver is closed.
2281
+ */
2282
+ private closeVoiceReceiver;
2283
+ /**
2284
+ * Reconnects the voice receiver for the player.
2285
+ * @returns {Promise<void>} - A promise that resolves when the voice receiver is reconnected.
2286
+ */
2287
+ private reconnectVoiceReceiver;
2288
+ /**
2289
+ * Disconnects the voice receiver for the player.
2290
+ * @returns {Promise<void>} - A promise that resolves when the voice receiver is disconnected.
2291
+ */
2292
+ private disconnectVoiceReceiver;
2293
+ /**
2294
+ * Opens the voice receiver for the player.
2295
+ * @returns {Promise<void>} - A promise that resolves when the voice receiver is opened.
2296
+ */
2297
+ private openVoiceReceiver;
2298
+ /**
2299
+ * Handles a voice receiver message.
2300
+ * @param {string} payload - The payload to handle.
2301
+ * @returns {Promise<void>} - A promise that resolves when the voice receiver message is handled.
2302
+ */
2303
+ private onVoiceReceiverMessage;
2304
+ /**
2305
+ * Handles a voice receiver error.
2306
+ * @param {Error} error - The error to handle.
2307
+ * @returns {Promise<void>} - A promise that resolves when the voice receiver error is handled.
2308
+ */
2309
+ private onVoiceReceiverError;
2124
2310
  }
2125
2311
 
2126
2312
  declare class Filters {
@@ -2575,6 +2761,12 @@ declare abstract class TrackUtils {
2575
2761
  * @returns The built Track.
2576
2762
  */
2577
2763
  static build<T = User | ClientUser>(data: TrackData, requester?: T): Track;
2764
+ /**
2765
+ * Validates a search result.
2766
+ * @param result The search result to validate.
2767
+ * @returns Whether the search result is valid.
2768
+ */
2769
+ static isErrorOrEmptySearchResult(result: SearchResult): result is ErrorOrEmptySearchResult;
2578
2770
  }
2579
2771
  declare abstract class AutoPlayUtils {
2580
2772
  private static manager;
@@ -2583,7 +2775,7 @@ declare abstract class AutoPlayUtils {
2583
2775
  * @param manager The manager instance to use.
2584
2776
  * @hidden
2585
2777
  */
2586
- static init(manager: Manager): void;
2778
+ static init(manager: Manager): Promise<void>;
2587
2779
  /**
2588
2780
  * Gets recommended tracks for the given track.
2589
2781
  * @param track The track to get recommended tracks for.
@@ -2603,10 +2795,8 @@ declare abstract class AutoPlayUtils {
2603
2795
  * @param platform The source to get recommended tracks from.
2604
2796
  * @returns An array of recommended tracks.
2605
2797
  */
2606
- static getRecommendedTracksFromSource(track: Track, platform: string): Promise<Track[]>;
2607
- static fetchSecretArray(): Promise<Buffer<ArrayBuffer>>;
2608
- static transformSecret(buffer: Buffer): Buffer<ArrayBuffer>;
2609
- static generateTotp(secretBuffer: Buffer): string;
2798
+ static getRecommendedTracksFromSource(track: Track, platform: AutoPlayPlatform): Promise<Track[]>;
2799
+ static buildTracksFromResponse<T>(recommendedResult: LavalinkResponse, requester?: T): Track[];
2610
2800
  }
2611
2801
  /** Gets or extends structures to extend the built in, or already extended, classes to add more functionality. */
2612
2802
  declare abstract class Structure {
@@ -2660,4 +2850,4 @@ declare class OceanicManager extends Manager {
2660
2850
  }
2661
2851
 
2662
2852
  export { AutoPlayPlatform, AutoPlayUtils, AvailableFilters, DetritusManager, DiscordJSManager, ErisManager, Filters, LoadTypes, Manager, ManagerEventTypes, Node$1 as Node, OceanicManager, Player, PlayerStateEventTypes, Plugin$1 as Plugin, Queue, Rest, SearchPlatform, SeverityTypes, SponsorBlockSegment, StateStorageType, StateTypes, Structure, TrackEndReasonTypes, TrackPartial, TrackSourceTypes, TrackUtils, UseNodeOptions };
2663
- export type { CPUStats, DiscordPacket, EqualizerBand, Exception, Extendable, FrameStats, IQueue, LavaPlayer, LavalinkInfo, LavalinkResponse, LoadType, Lyrics, LyricsLine, ManagerEvents, ManagerInitOptions, ManagerOptions, MemoryStats, NodeMessage, NodeOptions, NodeStats, Payload, PlayOptions, PlayerEvent, PlayerEventType, PlayerEvents, PlayerOptions, PlayerStateUpdateEvent, PlayerStore, PlayerUpdate, PlayerUpdateVoiceState, PlaylistData, PlaylistInfoData, PlaylistRawData, RedisConfig, SearchQuery, SearchResult, Severity, Sizes, SponsorBlockChapterStarted, SponsorBlockChaptersLoaded, SponsorBlockSegmentEventType, SponsorBlockSegmentEvents, SponsorBlockSegmentSkipped, SponsorBlockSegmentsLoaded, StateStorageOptions, Track, TrackData, TrackDataInfo, TrackEndEvent, TrackEndReason, TrackExceptionEvent, TrackPluginInfo, TrackSourceName, TrackStartEvent, TrackStuckEvent, UseNodeOption, VoicePacket, VoiceServer, VoiceServerUpdate, VoiceState, WebSocketClosedEvent };
2853
+ export type { CPUStats, DiscordPacket, EndSpeakingEventVoiceReceiver, EndSpeakingEventVoiceReceiverData, EqualizerBand, ErrorOrEmptySearchResult, Exception, Extendable, FrameStats, IQueue, LavaPlayer, LavalinkInfo, LavalinkResponse, LoadType, Lyrics, LyricsLine, ManagerEvents, ManagerInitOptions, ManagerOptions, MemoryStats, NodeLinkGetLyrics, NodeLinkGetLyricsEmpty, NodeLinkGetLyricsError, NodeLinkGetLyricsMultiple, NodeLinkGetLyricsSingle, NodeMessage, NodeOptions, NodeStats, Payload, PlayOptions, PlayerEvent, PlayerEventType, PlayerEvents, PlayerOptions, PlayerStateUpdateEvent, PlayerUpdate, PlayerUpdateVoiceState, PlaylistData, PlaylistInfoData, PlaylistRawData, PlaylistSearchResult, RedisConfig, SearchQuery, SearchResult, SearchSearchResult, Severity, Sizes, SponsorBlockChapterStarted, SponsorBlockChaptersLoaded, SponsorBlockSegmentEventType, SponsorBlockSegmentEvents, SponsorBlockSegmentSkipped, SponsorBlockSegmentsLoaded, StartSpeakingEventVoiceReceiver, StartSpeakingEventVoiceReceiverData, StateStorageOptions, Track, TrackData, TrackDataInfo, TrackEndEvent, TrackEndReason, TrackExceptionEvent, TrackPluginInfo, TrackSearchResult, TrackSourceName, TrackStartEvent, TrackStuckEvent, UseNodeOption, VoicePacket, VoiceReceiverEvent, VoiceServer, VoiceServerUpdate, VoiceState, WebSocketClosedEvent };
@@ -163,6 +163,11 @@ var ManagerEventTypes;
163
163
  ManagerEventTypes["TrackError"] = "trackError";
164
164
  ManagerEventTypes["TrackStart"] = "trackStart";
165
165
  ManagerEventTypes["TrackStuck"] = "trackStuck";
166
+ ManagerEventTypes["VoiceReceiverDisconnect"] = "voiceReceiverDisconnect";
167
+ ManagerEventTypes["VoiceReceiverConnect"] = "voiceReceiverConnect";
168
+ ManagerEventTypes["VoiceReceiverError"] = "voiceReceiverError";
169
+ ManagerEventTypes["VoiceReceiverStartSpeaking"] = "voiceReceiverStartSpeaking";
170
+ ManagerEventTypes["VoiceReceiverEndSpeaking"] = "voiceReceiverEndSpeaking";
166
171
  })(ManagerEventTypes || (exports.ManagerEventTypes = ManagerEventTypes = {}));
167
172
  /**
168
173
  * Track End Reason Enum
@@ -47,7 +47,6 @@ class Manager extends events_1.EventEmitter {
47
47
  // Initialize structures
48
48
  Utils_1.Structure.get("Player").init(this);
49
49
  Utils_1.TrackUtils.init(this);
50
- Utils_1.AutoPlayUtils.init(this);
51
50
  if (options.trackPartial) {
52
51
  Utils_1.TrackUtils.setTrackPartial(options.trackPartial);
53
52
  delete options.trackPartial;
@@ -77,6 +76,7 @@ class Manager extends events_1.EventEmitter {
77
76
  stateStorage: { type: Enums_1.StateStorageType.Collection },
78
77
  ...options,
79
78
  };
79
+ Utils_1.AutoPlayUtils.init(this);
80
80
  if (this.options.nodes) {
81
81
  for (const nodeOptions of this.options.nodes)
82
82
  new Node_1.Node(this, nodeOptions);
@@ -216,7 +216,20 @@ class Manager extends events_1.EventEmitter {
216
216
  tracks = tracks.map(processTrack);
217
217
  }
218
218
  }
219
- const result = { loadType: res.loadType, tracks, playlist };
219
+ let result;
220
+ switch (res.loadType) {
221
+ case Enums_1.LoadTypes.Playlist:
222
+ result = { loadType: res.loadType, tracks, playlist };
223
+ break;
224
+ case Enums_1.LoadTypes.Search:
225
+ result = { loadType: res.loadType, tracks };
226
+ break;
227
+ case Enums_1.LoadTypes.Track:
228
+ result = { loadType: res.loadType, tracks: [tracks[0]] };
229
+ break;
230
+ default:
231
+ return { loadType: res.loadType };
232
+ }
220
233
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Result ${_source} search for: ${_query.query}: ${JSON.stringify(result)}`);
221
234
  return result;
222
235
  }
@@ -791,6 +804,9 @@ class Manager extends events_1.EventEmitter {
791
804
  async handleShutdown() {
792
805
  console.warn("\x1b[31m%s\x1b[0m", "MAGMASTREAM WARNING: Shutting down! Please wait, saving active players...");
793
806
  try {
807
+ if (this.options.stateStorage.type === Enums_1.StateStorageType.Collection) {
808
+ await this.clearAllPlayerStates();
809
+ }
794
810
  const savePromises = Array.from(this.players.keys()).map(async (guildId) => {
795
811
  try {
796
812
  await this.savePlayerState(guildId);
@@ -925,6 +941,7 @@ class Manager extends events_1.EventEmitter {
925
941
  guildId: player.guildId,
926
942
  data: { voice: { token, endpoint, sessionId } },
927
943
  });
944
+ this.emit(Enums_1.ManagerEventTypes.Debug, `Updated voice server for player ${player.guildId} with token ${token} and endpoint ${endpoint} and sessionId ${sessionId}`);
928
945
  return;
929
946
  }
930
947
  /**
@@ -935,6 +952,7 @@ class Manager extends events_1.EventEmitter {
935
952
  * @emits {playerDisconnect} - Emits a player disconnect event if the channel ID is null.
936
953
  */
937
954
  async handleVoiceStateUpdate(player, update) {
955
+ this.emit(Enums_1.ManagerEventTypes.Debug, `Updated voice state for player ${player.guildId} with channel id ${update.channel_id} and session id ${update.session_id}`);
938
956
  if (update.channel_id) {
939
957
  if (player.voiceChannelId !== update.channel_id) {
940
958
  this.emit(Enums_1.ManagerEventTypes.PlayerMove, player, player.voiceChannelId, update.channel_id);
@@ -1052,6 +1070,64 @@ class Manager extends events_1.EventEmitter {
1052
1070
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error cleaning up inactive players: ${error}`);
1053
1071
  }
1054
1072
  }
1073
+ /**
1074
+ * Clears all player states from the file system.
1075
+ * This is done to prevent stale state files from accumulating on the file system.
1076
+ */
1077
+ async clearAllPlayerStates() {
1078
+ switch (this.options.stateStorage.type) {
1079
+ case Enums_1.StateStorageType.Collection: {
1080
+ const configDir = path_1.default.join(process.cwd(), "magmastream", "dist", "sessionData", "players");
1081
+ try {
1082
+ // Check if the directory exists, and create it if it doesn't
1083
+ await promises_1.default.access(configDir).catch(async () => {
1084
+ await promises_1.default.mkdir(configDir, { recursive: true });
1085
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Created directory: ${configDir}`);
1086
+ });
1087
+ const files = await promises_1.default.readdir(configDir);
1088
+ await Promise.all(files.map((file) => promises_1.default.unlink(path_1.default.join(configDir, file)).catch((err) => console.warn(`Failed to delete file ${file}:`, err))));
1089
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Cleared all player state files in ${configDir}`);
1090
+ }
1091
+ catch (err) {
1092
+ console.error("Error clearing player state files:", err);
1093
+ }
1094
+ break;
1095
+ }
1096
+ case Enums_1.StateStorageType.Redis: {
1097
+ const prefix = this.options.stateStorage.redisConfig.prefix?.endsWith(":")
1098
+ ? this.options.stateStorage.redisConfig.prefix
1099
+ : this.options.stateStorage.redisConfig.prefix ?? "magmastream:";
1100
+ const pattern = `${prefix}playerstore:*`;
1101
+ try {
1102
+ const stream = this.redis.scanStream({
1103
+ match: pattern,
1104
+ count: 100,
1105
+ });
1106
+ let totalDeleted = 0;
1107
+ stream.on("data", async (keys) => {
1108
+ if (keys.length) {
1109
+ const pipeline = this.redis.pipeline();
1110
+ keys.forEach((key) => pipeline.unlink(key));
1111
+ await pipeline.exec();
1112
+ totalDeleted += keys.length;
1113
+ }
1114
+ });
1115
+ stream.on("end", () => {
1116
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Cleared ${totalDeleted} Redis player state keys (pattern: ${pattern})`);
1117
+ });
1118
+ stream.on("error", (err) => {
1119
+ console.error("Error during Redis SCAN stream:", err);
1120
+ });
1121
+ }
1122
+ catch (err) {
1123
+ console.error("Failed to clear Redis player state keys:", err);
1124
+ }
1125
+ break;
1126
+ }
1127
+ default:
1128
+ console.warn("[MANAGER] No valid stateStorage.type set, skipping state clearing.");
1129
+ }
1130
+ }
1055
1131
  /**
1056
1132
  * Returns the nodes that has the least load.
1057
1133
  * The load is calculated by dividing the lavalink load by the number of cores.
@@ -31,11 +31,14 @@ class Node {
31
31
  rest;
32
32
  /** Actual Lavalink information of the node. */
33
33
  info = null;
34
+ /** Whether the node is a NodeLink. */
35
+ isNodeLink = false;
34
36
  reconnectTimeout;
35
37
  reconnectAttempts = 1;
36
38
  /**
37
39
  * Creates an instance of Node.
38
- * @param options
40
+ * @param manager - The manager for the node.
41
+ * @param options - The options for the node.
39
42
  */
40
43
  constructor(manager, options) {
41
44
  this.manager = manager;
@@ -185,6 +188,7 @@ class Node {
185
188
  this.socket = new ws_1.default(`ws${this.options.useSSL ? "s" : ""}://${this.address}/v4/websocket`, { headers });
186
189
  this.socket.on("open", this.open.bind(this));
187
190
  this.socket.on("close", this.close.bind(this));
191
+ this.socket.on("upgrade", (request) => this.upgrade(request));
188
192
  this.socket.on("message", this.message.bind(this));
189
193
  this.socket.on("error", this.error.bind(this));
190
194
  const debugInfo = {
@@ -287,6 +291,14 @@ class Node {
287
291
  this.reconnectAttempts++;
288
292
  }, this.options.retryDelayMs);
289
293
  }
294
+ /**
295
+ * Upgrades the node to a NodeLink.
296
+ *
297
+ * @param request - The incoming message.
298
+ */
299
+ upgrade(request) {
300
+ this.isNodeLink = this.options.isNodeLink ?? Boolean(request.headers.isnodelink) ?? false;
301
+ }
290
302
  /**
291
303
  * Handles the "open" event emitted by the WebSocket connection.
292
304
  *
@@ -727,13 +739,14 @@ class Node {
727
739
  *
728
740
  * @param {Track} track - The track to fetch the lyrics for.
729
741
  * @param {boolean} [skipTrackSource=false] - Whether to skip using the track's source URL.
730
- * @returns {Promise<Lyrics>} A promise that resolves with the lyrics data.
742
+ * @returns {Promise<Lyrics | NodeLinkGetLyrics>} A promise that resolves with the lyrics data.
731
743
  */
732
744
  async getLyrics(track, skipTrackSource = false) {
745
+ if (this.isNodeLink) {
746
+ return (await this.rest.get(`/v4/lyrics?track=${encodeURIComponent(track.track)}&skipTrackSource=${skipTrackSource}`));
747
+ }
733
748
  if (!this.info.plugins.some((plugin) => plugin.name === "lavalyrics-plugin"))
734
749
  throw new RangeError(`there is no lavalyrics-plugin available in the lavalink node: ${this.options.identifier}`);
735
- // Make a GET request to the Lavalink node to fetch the lyrics
736
- // The request includes the track URL and the skipTrackSource parameter
737
750
  return ((await this.rest.get(`/v4/lyrics?track=${encodeURIComponent(track.track)}&skipTrackSource=${skipTrackSource}`)) || {
738
751
  source: null,
739
752
  provider: null,