magmastream 2.9.0-dev.32 → 2.9.0-dev.34

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
@@ -266,6 +266,9 @@ declare enum ManagerEventTypes {
266
266
  ChapterStarted = "chapterStarted",
267
267
  ChaptersLoaded = "chaptersLoaded",
268
268
  Debug = "debug",
269
+ LyricsFoundEvent = "lyricsFoundEvent",
270
+ LyricsLineEvent = "lyricsLineEvent",
271
+ LyricsNotFoundEvent = "lyricsNotFoundEvent",
269
272
  NodeConnect = "nodeConnect",
270
273
  NodeCreate = "nodeCreate",
271
274
  NodeDestroy = "nodeDestroy",
@@ -736,15 +739,40 @@ interface LavaPlayer {
736
739
  filters: Record<string, unknown>;
737
740
  }
738
741
  /**
739
- * Search Result
742
+ * Error or Empty Search Result
740
743
  */
741
- interface SearchResult {
744
+ interface ErrorOrEmptySearchResult {
742
745
  /** The load type of the result. */
743
- loadType: LoadTypes;
744
- /** The array of tracks from the result. */
746
+ loadType: LoadTypes.Empty | LoadTypes.Error;
747
+ }
748
+ /**
749
+ * Track Search Result
750
+ */
751
+ interface TrackSearchResult {
752
+ /** The load type is always 'track' */
753
+ loadType: LoadTypes.Track;
754
+ /** The track obtained */
755
+ tracks: [Track];
756
+ }
757
+ /**
758
+ * Search Result
759
+ */
760
+ interface SearchSearchResult {
761
+ /** The load type is always 'search' */
762
+ loadType: LoadTypes.Search;
763
+ /** The tracks of the search result */
764
+ tracks: Track[];
765
+ }
766
+ /**
767
+ * Playlist Search Result
768
+ */
769
+ interface PlaylistSearchResult {
770
+ /** The playlist load type */
771
+ loadType: LoadTypes.Playlist;
772
+ /** The tracks of the playlist */
745
773
  tracks: Track[];
746
- /** The playlist info if the load type is 'playlist'. */
747
- playlist?: PlaylistData;
774
+ /** The playlist info */
775
+ playlist: PlaylistData;
748
776
  }
749
777
  /**
750
778
  * Playlist Data
@@ -783,6 +811,9 @@ interface ManagerEvents {
783
811
  [ManagerEventTypes.ChapterStarted]: [player: Player, track: Track, payload: SponsorBlockChapterStarted];
784
812
  [ManagerEventTypes.ChaptersLoaded]: [player: Player, track: Track, payload: SponsorBlockChaptersLoaded];
785
813
  [ManagerEventTypes.Debug]: [info: string];
814
+ [ManagerEventTypes.LyricsFoundEvent]: [player: Player, track: Track, payload: LyricsFoundEvent];
815
+ [ManagerEventTypes.LyricsLineEvent]: [player: Player, track: Track, payload: LyricsLineEvent];
816
+ [ManagerEventTypes.LyricsNotFoundEvent]: [player: Player, track: Track, payload: LyricsNotFoundEvent];
786
817
  [ManagerEventTypes.NodeConnect]: [node: Node];
787
818
  [ManagerEventTypes.NodeCreate]: [node: Node];
788
819
  [ManagerEventTypes.NodeDestroy]: [node: Node];
@@ -1153,6 +1184,31 @@ interface Lyrics {
1153
1184
  lines: LyricsLine[];
1154
1185
  plugin: object[];
1155
1186
  }
1187
+ /**
1188
+ * LyricsFoundEvent interface
1189
+ */
1190
+ interface LyricsFoundEvent extends PlayerEvent {
1191
+ type: "LyricsFoundEvent";
1192
+ guildId: string;
1193
+ lyrics: Lyrics;
1194
+ }
1195
+ /**
1196
+ * LyricsNotFoundEvent interface
1197
+ */
1198
+ interface LyricsNotFoundEvent extends PlayerEvent {
1199
+ type: "LyricsNotFoundEvent";
1200
+ guildId: string;
1201
+ }
1202
+ /**
1203
+ * LyricsLineEvent interface
1204
+ */
1205
+ interface LyricsLineEvent extends PlayerEvent {
1206
+ type: "LyricsLineEvent";
1207
+ guildId: string;
1208
+ lineIndex: number;
1209
+ line: LyricsLine;
1210
+ skipped: boolean;
1211
+ }
1156
1212
  /**
1157
1213
  * NodeLink Get Lyrics Multiple interface
1158
1214
  */
@@ -1338,7 +1394,7 @@ type TrackEndReason = keyof typeof TrackEndReasonTypes;
1338
1394
  /**
1339
1395
  * Player Event Type Enum type
1340
1396
  */
1341
- type PlayerEventType = "TrackStartEvent" | "TrackEndEvent" | "TrackExceptionEvent" | "TrackStuckEvent" | "WebSocketClosedEvent" | "SegmentSkipped" | "SegmentsLoaded" | "ChaptersLoaded" | "ChapterStarted";
1397
+ type PlayerEventType = "TrackStartEvent" | "TrackEndEvent" | "TrackExceptionEvent" | "TrackStuckEvent" | "WebSocketClosedEvent" | "SegmentSkipped" | "SegmentsLoaded" | "ChaptersLoaded" | "ChapterStarted" | "LyricsFoundEvent" | "LyricsNotFoundEvent" | "LyricsLineEvent";
1342
1398
  /**
1343
1399
  * Severity Types Enum type
1344
1400
  */
@@ -1354,7 +1410,7 @@ type SponsorBlockSegmentEventType = "SegmentSkipped" | "SegmentsLoaded" | "Chapt
1354
1410
  /**
1355
1411
  * Player Events Enum type
1356
1412
  */
1357
- type PlayerEvents = TrackStartEvent | TrackEndEvent | TrackStuckEvent | TrackExceptionEvent | WebSocketClosedEvent | SponsorBlockSegmentEvents;
1413
+ type PlayerEvents = TrackStartEvent | TrackEndEvent | TrackStuckEvent | TrackExceptionEvent | WebSocketClosedEvent | SponsorBlockSegmentEvents | LyricsEvent;
1358
1414
  /**
1359
1415
  * Load Type Enum type
1360
1416
  */
@@ -1367,6 +1423,18 @@ type NodeLinkGetLyrics = NodeLinkGetLyricsSingle | NodeLinkGetLyricsMultiple | N
1367
1423
  * Voice Receiver Event Enum type
1368
1424
  */
1369
1425
  type VoiceReceiverEvent = StartSpeakingEventVoiceReceiver | EndSpeakingEventVoiceReceiver;
1426
+ /**
1427
+ * Search Result Enum type
1428
+ */
1429
+ type SearchResult = TrackSearchResult | SearchSearchResult | PlaylistSearchResult | ErrorOrEmptySearchResult;
1430
+ /**
1431
+ * Lyrics Event Enum type
1432
+ */
1433
+ type LyricsEvent = LyricsFoundEvent | LyricsNotFoundEvent | LyricsLineEvent;
1434
+ /**
1435
+ * Lyrics Event Type Enum type
1436
+ */
1437
+ type LyricsEventType = "LyricsFoundEvent" | "LyricsNotFoundEvent" | "LyricsLineEvent";
1370
1438
 
1371
1439
  declare class Node$1 {
1372
1440
  manager: Manager;
@@ -1593,14 +1661,32 @@ declare class Node$1 {
1593
1661
  queueEnd(player: Player, track: Track, payload: TrackEndEvent): Promise<void>;
1594
1662
  /**
1595
1663
  * Fetches the lyrics of a track from the Lavalink node.
1596
- * This method uses the `lavalyrics-plugin` to fetch the lyrics.
1597
- * If the plugin is not available, it will throw a RangeError.
1664
+ *
1665
+ * If the node is a NodeLink, it will use the `NodeLinkGetLyrics` method to fetch the lyrics.
1666
+ *
1667
+ * Requires the `lavalyrics-plugin` to be present in the Lavalink node.
1668
+ * Requires the `lavasrc-plugin` or `java-lyrics-plugin` to be present in the Lavalink node.
1598
1669
  *
1599
1670
  * @param {Track} track - The track to fetch the lyrics for.
1600
1671
  * @param {boolean} [skipTrackSource=false] - Whether to skip using the track's source URL.
1601
1672
  * @returns {Promise<Lyrics | NodeLinkGetLyrics>} A promise that resolves with the lyrics data.
1602
1673
  */
1603
1674
  getLyrics(track: Track, skipTrackSource?: boolean): Promise<Lyrics | NodeLinkGetLyrics>;
1675
+ /**
1676
+ * Subscribes to lyrics for a player.
1677
+ * @param {string} guildId - The ID of the guild to subscribe to lyrics for.
1678
+ * @param {boolean} [skipTrackSource=false] - Whether to skip using the track's source URL.
1679
+ * @returns {Promise<unknown>} A promise that resolves when the subscription is complete.
1680
+ * @throws {RangeError} If the node is not connected to the lavalink server or if the java-lyrics-plugin is not available.
1681
+ */
1682
+ lyricsSubscribe(guildId: string, skipTrackSource?: boolean): Promise<unknown>;
1683
+ /**
1684
+ * Unsubscribes from lyrics for a player.
1685
+ * @param {string} guildId - The ID of the guild to unsubscribe from lyrics for.
1686
+ * @returns {Promise<unknown>} A promise that resolves when the unsubscription is complete.
1687
+ * @throws {RangeError} If the node is not connected to the lavalink server or if the java-lyrics-plugin is not available.
1688
+ */
1689
+ lyricsUnsubscribe(guildId: string): Promise<unknown>;
1604
1690
  /**
1605
1691
  * Handles the event when a track becomes stuck during playback.
1606
1692
  * Stops the current track and emits a `trackStuck` event.
@@ -1662,6 +1748,30 @@ declare class Node$1 {
1662
1748
  * @param {SponsorBlockChapterStarted} payload - The event payload containing additional data about the chapter started event.
1663
1749
  */
1664
1750
  private sponsorBlockChapterStarted;
1751
+ /**
1752
+ * Emitted when lyrics for a track are found.
1753
+ * The payload of the event will contain the lyrics.
1754
+ * @param {Player} player - The player associated with the lyrics.
1755
+ * @param {Track} track - The track associated with the lyrics.
1756
+ * @param {LyricsFoundEvent} payload - The event payload containing additional data about the lyrics found event.
1757
+ */
1758
+ private lyricsFound;
1759
+ /**
1760
+ * Emitted when lyrics for a track are not found.
1761
+ * The payload of the event will contain the track.
1762
+ * @param {Player} player - The player associated with the lyrics.
1763
+ * @param {Track} track - The track associated with the lyrics.
1764
+ * @param {LyricsNotFoundEvent} payload - The event payload containing additional data about the lyrics not found event.
1765
+ */
1766
+ private lyricsNotFound;
1767
+ /**
1768
+ * Emitted when a line of lyrics for a track is received.
1769
+ * The payload of the event will contain the lyrics line.
1770
+ * @param {Player} player - The player associated with the lyrics line.
1771
+ * @param {Track} track - The track associated with the lyrics line.
1772
+ * @param {LyricsLineEvent} payload - The event payload containing additional data about the lyrics line event.
1773
+ */
1774
+ private lyricsLine;
1665
1775
  /**
1666
1776
  * Fetches Lavalink node information.
1667
1777
  * @returns {Promise<LavalinkInfo>} A promise that resolves to the Lavalink node information.
@@ -2732,6 +2842,12 @@ declare abstract class TrackUtils {
2732
2842
  * @returns The built Track.
2733
2843
  */
2734
2844
  static build<T = User | ClientUser>(data: TrackData, requester?: T): Track;
2845
+ /**
2846
+ * Validates a search result.
2847
+ * @param result The search result to validate.
2848
+ * @returns Whether the search result is valid.
2849
+ */
2850
+ static isErrorOrEmptySearchResult(result: SearchResult): result is ErrorOrEmptySearchResult;
2735
2851
  }
2736
2852
  declare abstract class AutoPlayUtils {
2737
2853
  private static manager;
@@ -2740,7 +2856,7 @@ declare abstract class AutoPlayUtils {
2740
2856
  * @param manager The manager instance to use.
2741
2857
  * @hidden
2742
2858
  */
2743
- static init(manager: Manager): void;
2859
+ static init(manager: Manager): Promise<void>;
2744
2860
  /**
2745
2861
  * Gets recommended tracks for the given track.
2746
2862
  * @param track The track to get recommended tracks for.
@@ -2760,8 +2876,8 @@ declare abstract class AutoPlayUtils {
2760
2876
  * @param platform The source to get recommended tracks from.
2761
2877
  * @returns An array of recommended tracks.
2762
2878
  */
2763
- static getRecommendedTracksFromSource(track: Track, platform: string): Promise<Track[]>;
2764
- static generateTotp(): (string | number)[];
2879
+ static getRecommendedTracksFromSource(track: Track, platform: AutoPlayPlatform): Promise<Track[]>;
2880
+ static buildTracksFromResponse<T>(recommendedResult: LavalinkResponse, requester?: T): Track[];
2765
2881
  }
2766
2882
  /** Gets or extends structures to extend the built in, or already extended, classes to add more functionality. */
2767
2883
  declare abstract class Structure {
@@ -2815,4 +2931,4 @@ declare class OceanicManager extends Manager {
2815
2931
  }
2816
2932
 
2817
2933
  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 };
2818
- export type { CPUStats, DiscordPacket, EndSpeakingEventVoiceReceiver, EndSpeakingEventVoiceReceiverData, EqualizerBand, 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, RedisConfig, SearchQuery, SearchResult, Severity, Sizes, SponsorBlockChapterStarted, SponsorBlockChaptersLoaded, SponsorBlockSegmentEventType, SponsorBlockSegmentEvents, SponsorBlockSegmentSkipped, SponsorBlockSegmentsLoaded, StartSpeakingEventVoiceReceiver, StartSpeakingEventVoiceReceiverData, StateStorageOptions, Track, TrackData, TrackDataInfo, TrackEndEvent, TrackEndReason, TrackExceptionEvent, TrackPluginInfo, TrackSourceName, TrackStartEvent, TrackStuckEvent, UseNodeOption, VoicePacket, VoiceReceiverEvent, VoiceServer, VoiceServerUpdate, VoiceState, WebSocketClosedEvent };
2934
+ export type { CPUStats, DiscordPacket, EndSpeakingEventVoiceReceiver, EndSpeakingEventVoiceReceiverData, EqualizerBand, ErrorOrEmptySearchResult, Exception, Extendable, FrameStats, IQueue, LavaPlayer, LavalinkInfo, LavalinkResponse, LoadType, Lyrics, LyricsEvent, LyricsEventType, LyricsFoundEvent, LyricsLine, LyricsLineEvent, LyricsNotFoundEvent, 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 };
@@ -141,6 +141,9 @@ var ManagerEventTypes;
141
141
  ManagerEventTypes["ChapterStarted"] = "chapterStarted";
142
142
  ManagerEventTypes["ChaptersLoaded"] = "chaptersLoaded";
143
143
  ManagerEventTypes["Debug"] = "debug";
144
+ ManagerEventTypes["LyricsFoundEvent"] = "lyricsFoundEvent";
145
+ ManagerEventTypes["LyricsLineEvent"] = "lyricsLineEvent";
146
+ ManagerEventTypes["LyricsNotFoundEvent"] = "lyricsNotFoundEvent";
144
147
  ManagerEventTypes["NodeConnect"] = "nodeConnect";
145
148
  ManagerEventTypes["NodeCreate"] = "nodeCreate";
146
149
  ManagerEventTypes["NodeDestroy"] = "nodeDestroy";
@@ -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
  }
@@ -485,6 +485,15 @@ class Node {
485
485
  case "ChapterStarted":
486
486
  this.sponsorBlockChapterStarted(player, track, payload);
487
487
  break;
488
+ case "LyricsFoundEvent":
489
+ this.lyricsFound(player, track, payload);
490
+ break;
491
+ case "LyricsNotFoundEvent":
492
+ this.lyricsNotFound(player, track, payload);
493
+ break;
494
+ case "LyricsLineEvent":
495
+ this.lyricsLine(player, track, payload);
496
+ break;
488
497
  default:
489
498
  error = new Error(`Node#event unknown event '${type}'.`);
490
499
  this.manager.emit(Enums_1.ManagerEventTypes.NodeError, this, error);
@@ -734,19 +743,28 @@ class Node {
734
743
  }
735
744
  /**
736
745
  * Fetches the lyrics of a track from the Lavalink node.
737
- * This method uses the `lavalyrics-plugin` to fetch the lyrics.
738
- * If the plugin is not available, it will throw a RangeError.
746
+ *
747
+ * If the node is a NodeLink, it will use the `NodeLinkGetLyrics` method to fetch the lyrics.
748
+ *
749
+ * Requires the `lavalyrics-plugin` to be present in the Lavalink node.
750
+ * Requires the `lavasrc-plugin` or `java-lyrics-plugin` to be present in the Lavalink node.
739
751
  *
740
752
  * @param {Track} track - The track to fetch the lyrics for.
741
753
  * @param {boolean} [skipTrackSource=false] - Whether to skip using the track's source URL.
742
754
  * @returns {Promise<Lyrics | NodeLinkGetLyrics>} A promise that resolves with the lyrics data.
743
755
  */
744
756
  async getLyrics(track, skipTrackSource = false) {
757
+ if (!this.connected)
758
+ throw new RangeError(`The node is not connected to the lavalink server: ${this.options.identifier}`);
745
759
  if (this.isNodeLink) {
746
760
  return (await this.rest.get(`/v4/lyrics?track=${encodeURIComponent(track.track)}&skipTrackSource=${skipTrackSource}`));
747
761
  }
748
- if (!this.info.plugins.some((plugin) => plugin.name === "lavalyrics-plugin"))
749
- throw new RangeError(`there is no lavalyrics-plugin available in the lavalink node: ${this.options.identifier}`);
762
+ if (!this.info.plugins.some((plugin) => plugin.name === "lavalyrics-plugin")) {
763
+ throw new RangeError(`The plugin "lavalyrics-plugin" must be present in the lavalink node: ${this.options.identifier}`);
764
+ }
765
+ if (!this.info.plugins.some((plugin) => plugin.name === "lavasrc-plugin" || plugin.name === "java-lyrics-plugin")) {
766
+ throw new RangeError(`One of the following plugins must also be present in the lavalink node: "lavasrc-plugin" or "java-lyrics-plugin" (Node: ${this.options.identifier})`);
767
+ }
750
768
  return ((await this.rest.get(`/v4/lyrics?track=${encodeURIComponent(track.track)}&skipTrackSource=${skipTrackSource}`)) || {
751
769
  source: null,
752
770
  provider: null,
@@ -755,6 +773,42 @@ class Node {
755
773
  plugin: [],
756
774
  });
757
775
  }
776
+ /**
777
+ * Subscribes to lyrics for a player.
778
+ * @param {string} guildId - The ID of the guild to subscribe to lyrics for.
779
+ * @param {boolean} [skipTrackSource=false] - Whether to skip using the track's source URL.
780
+ * @returns {Promise<unknown>} A promise that resolves when the subscription is complete.
781
+ * @throws {RangeError} If the node is not connected to the lavalink server or if the java-lyrics-plugin is not available.
782
+ */
783
+ async lyricsSubscribe(guildId, skipTrackSource = false) {
784
+ if (!this.connected)
785
+ throw new RangeError(`The node is not connected to the lavalink server: ${this.options.identifier}`);
786
+ if (this.isNodeLink)
787
+ throw new RangeError(`The node is a node link: ${this.options.identifier}`);
788
+ if (!this.info.plugins.some((plugin) => plugin.name === "lavalyrics-plugin")) {
789
+ throw new RangeError(`The plugin "lavalyrics-plugin" must be present in the lavalink node: ${this.options.identifier}`);
790
+ }
791
+ if (!this.info.plugins.some((plugin) => plugin.name === "lavasrc-plugin" || plugin.name === "java-lyrics-plugin")) {
792
+ throw new RangeError(`One of the following plugins must also be present in the lavalink node: "lavasrc-plugin" or "java-lyrics-plugin" (Node: ${this.options.identifier})`);
793
+ }
794
+ return await this.rest.post(`/v4/sessions/${this.sessionId}/players/${guildId}/lyrics/subscribe?skipTrackSource=${skipTrackSource}`, {});
795
+ }
796
+ /**
797
+ * Unsubscribes from lyrics for a player.
798
+ * @param {string} guildId - The ID of the guild to unsubscribe from lyrics for.
799
+ * @returns {Promise<unknown>} A promise that resolves when the unsubscription is complete.
800
+ * @throws {RangeError} If the node is not connected to the lavalink server or if the java-lyrics-plugin is not available.
801
+ */
802
+ async lyricsUnsubscribe(guildId) {
803
+ if (!this.connected)
804
+ throw new RangeError(`The node is not connected to the lavalink server: ${this.options.identifier}`);
805
+ if (this.isNodeLink)
806
+ throw new RangeError(`The node is a node link: ${this.options.identifier}`);
807
+ if (!this.info.plugins.some((plugin) => plugin.name === "java-lyrics-plugin")) {
808
+ throw new RangeError(`there is no java-lyrics-plugin available in the lavalink node: ${this.options.identifier}`);
809
+ }
810
+ return await this.rest.delete(`/v4/sessions/${this.sessionId}/players/${guildId}/lyrics/subscribe`);
811
+ }
758
812
  /**
759
813
  * Handles the event when a track becomes stuck during playback.
760
814
  * Stops the current track and emits a `trackStuck` event.
@@ -833,6 +887,36 @@ class Node {
833
887
  sponsorBlockChapterStarted(player, track, payload) {
834
888
  return this.manager.emit(Enums_1.ManagerEventTypes.ChapterStarted, player, track, payload);
835
889
  }
890
+ /**
891
+ * Emitted when lyrics for a track are found.
892
+ * The payload of the event will contain the lyrics.
893
+ * @param {Player} player - The player associated with the lyrics.
894
+ * @param {Track} track - The track associated with the lyrics.
895
+ * @param {LyricsFoundEvent} payload - The event payload containing additional data about the lyrics found event.
896
+ */
897
+ lyricsFound(player, track, payload) {
898
+ return this.manager.emit(Enums_1.ManagerEventTypes.LyricsFoundEvent, player, track, payload);
899
+ }
900
+ /**
901
+ * Emitted when lyrics for a track are not found.
902
+ * The payload of the event will contain the track.
903
+ * @param {Player} player - The player associated with the lyrics.
904
+ * @param {Track} track - The track associated with the lyrics.
905
+ * @param {LyricsNotFoundEvent} payload - The event payload containing additional data about the lyrics not found event.
906
+ */
907
+ lyricsNotFound(player, track, payload) {
908
+ return this.manager.emit(Enums_1.ManagerEventTypes.LyricsNotFoundEvent, player, track, payload);
909
+ }
910
+ /**
911
+ * Emitted when a line of lyrics for a track is received.
912
+ * The payload of the event will contain the lyrics line.
913
+ * @param {Player} player - The player associated with the lyrics line.
914
+ * @param {Track} track - The track associated with the lyrics line.
915
+ * @param {LyricsLineEvent} payload - The event payload containing additional data about the lyrics line event.
916
+ */
917
+ lyricsLine(player, track, payload) {
918
+ return this.manager.emit(Enums_1.ManagerEventTypes.LyricsLineEvent, player, track, payload);
919
+ }
836
920
  /**
837
921
  * Fetches Lavalink node information.
838
922
  * @returns {Promise<LavalinkInfo>} A promise that resolves to the Lavalink node information.
@@ -4,8 +4,8 @@ exports.Structure = exports.AutoPlayUtils = exports.TrackUtils = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const axios_1 = tslib_1.__importDefault(require("axios"));
6
6
  const jsdom_1 = require("jsdom");
7
- const crypto_1 = tslib_1.__importDefault(require("crypto"));
8
7
  const Enums_1 = require("./Enums");
8
+ // import playwright from "playwright";
9
9
  /** @hidden */
10
10
  const SIZES = ["0", "1", "2", "3", "default", "mqdefault", "hqdefault", "maxresdefault"];
11
11
  class TrackUtils {
@@ -129,19 +129,32 @@ class TrackUtils {
129
129
  throw new RangeError(`Argument "data" is not a valid track: ${error.message}`);
130
130
  }
131
131
  }
132
+ /**
133
+ * Validates a search result.
134
+ * @param result The search result to validate.
135
+ * @returns Whether the search result is valid.
136
+ */
137
+ static isErrorOrEmptySearchResult(result) {
138
+ return result.loadType === Enums_1.LoadTypes.Empty || result.loadType === Enums_1.LoadTypes.Error;
139
+ }
132
140
  }
133
141
  exports.TrackUtils = TrackUtils;
134
142
  class AutoPlayUtils {
135
143
  static manager;
144
+ // private static cachedAccessToken: string | null = null;
145
+ // private static cachedAccessTokenExpiresAt: number = 0;
136
146
  /**
137
147
  * Initializes the AutoPlayUtils class with the given manager.
138
148
  * @param manager The manager instance to use.
139
149
  * @hidden
140
150
  */
141
- static init(manager) {
151
+ static async init(manager) {
142
152
  if (!manager)
143
153
  throw new Error("AutoPlayUtils.init() requires a valid Manager instance.");
144
154
  this.manager = manager;
155
+ // if (this.manager.options.autoPlaySearchPlatforms.includes(AutoPlayPlatform.Spotify)) {
156
+ // await this.getSpotifyAccessToken();
157
+ // }
145
158
  }
146
159
  /**
147
160
  * Gets recommended tracks for the given track.
@@ -190,15 +203,25 @@ class AutoPlayUtils {
190
203
  return [];
191
204
  }
192
205
  const randomTrack = response.data.toptracks.track[Math.floor(Math.random() * response.data.toptracks.track.length)];
193
- const res = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: this.manager.options.defaultSearchPlatform }, track.requester);
194
- if (res.loadType === Enums_1.LoadTypes.Empty || res.loadType === Enums_1.LoadTypes.Error) {
206
+ const searchResult = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: this.manager.options.defaultSearchPlatform }, track.requester);
207
+ if (TrackUtils.isErrorOrEmptySearchResult(searchResult)) {
195
208
  return [];
196
209
  }
197
- const filteredTracks = res.tracks.filter((t) => t.uri !== track.uri);
198
- if (!filteredTracks.length) {
199
- return [];
210
+ let resolvedTracks;
211
+ switch (searchResult.loadType) {
212
+ case Enums_1.LoadTypes.Playlist:
213
+ resolvedTracks = searchResult.playlist.tracks;
214
+ break;
215
+ case Enums_1.LoadTypes.Track:
216
+ case Enums_1.LoadTypes.Search:
217
+ resolvedTracks = searchResult.tracks;
218
+ break;
219
+ default:
220
+ return [];
200
221
  }
201
- return filteredTracks;
222
+ if (!resolvedTracks.length)
223
+ return [];
224
+ return resolvedTracks;
202
225
  }
203
226
  if (!artist) {
204
227
  // No artist provided, search for the track title
@@ -228,11 +251,25 @@ class AutoPlayUtils {
228
251
  return [];
229
252
  }
230
253
  const randomTrack = retryResponse.data.toptracks.track[Math.floor(Math.random() * retryResponse.data.toptracks.track.length)];
231
- const res = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: this.manager.options.defaultSearchPlatform }, track.requester);
232
- if (res.loadType === Enums_1.LoadTypes.Empty || res.loadType === Enums_1.LoadTypes.Error) {
254
+ const searchResult = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: this.manager.options.defaultSearchPlatform }, track.requester);
255
+ if (TrackUtils.isErrorOrEmptySearchResult(searchResult)) {
233
256
  return [];
234
257
  }
235
- const filteredTracks = res.tracks.filter((t) => t.uri !== track.uri);
258
+ let resolvedTracks;
259
+ switch (searchResult.loadType) {
260
+ case Enums_1.LoadTypes.Playlist:
261
+ resolvedTracks = searchResult.playlist.tracks;
262
+ break;
263
+ case Enums_1.LoadTypes.Track:
264
+ case Enums_1.LoadTypes.Search:
265
+ resolvedTracks = searchResult.tracks;
266
+ break;
267
+ default:
268
+ return [];
269
+ }
270
+ if (!resolvedTracks.length)
271
+ return [];
272
+ const filteredTracks = resolvedTracks.filter((t) => t.uri !== track.uri);
236
273
  if (!filteredTracks.length) {
237
274
  return [];
238
275
  }
@@ -242,16 +279,25 @@ class AutoPlayUtils {
242
279
  if (!randomTrack) {
243
280
  return [];
244
281
  }
245
- const res = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: this.manager.options.defaultSearchPlatform }, track.requester);
246
- if (res.loadType === Enums_1.LoadTypes.Empty || res.loadType === Enums_1.LoadTypes.Error) {
282
+ const searchResult = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: this.manager.options.defaultSearchPlatform }, track.requester);
283
+ if (TrackUtils.isErrorOrEmptySearchResult(searchResult)) {
247
284
  return [];
248
285
  }
249
- if (res.loadType === Enums_1.LoadTypes.Playlist)
250
- res.tracks = res.playlist.tracks;
251
- if (!res.tracks.length) {
252
- return [];
286
+ let resolvedTracks;
287
+ switch (searchResult.loadType) {
288
+ case Enums_1.LoadTypes.Playlist:
289
+ resolvedTracks = searchResult.playlist.tracks;
290
+ break;
291
+ case Enums_1.LoadTypes.Track:
292
+ case Enums_1.LoadTypes.Search:
293
+ resolvedTracks = searchResult.tracks;
294
+ break;
295
+ default:
296
+ return [];
253
297
  }
254
- return res.tracks;
298
+ if (!resolvedTracks.length)
299
+ return [];
300
+ return resolvedTracks;
255
301
  }
256
302
  /**
257
303
  * Gets recommended tracks from the given source.
@@ -260,157 +306,90 @@ class AutoPlayUtils {
260
306
  * @returns An array of recommended tracks.
261
307
  */
262
308
  static async getRecommendedTracksFromSource(track, platform) {
309
+ const requester = track.requester;
263
310
  switch (platform) {
264
- case "spotify":
311
+ case Enums_1.AutoPlayPlatform.Spotify:
265
312
  {
266
- try {
267
- if (!track.uri.includes("spotify")) {
268
- const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Enums_1.SearchPlatform.Spotify }, track.requester);
269
- if (res.loadType === Enums_1.LoadTypes.Empty || res.loadType === Enums_1.LoadTypes.Error) {
270
- return [];
271
- }
272
- if (res.loadType === Enums_1.LoadTypes.Playlist) {
273
- res.tracks = res.playlist.tracks;
274
- }
275
- if (!res.tracks.length) {
276
- return [];
277
- }
278
- track = res.tracks[0];
279
- }
280
- const [totp, timestamp] = this.generateTotp();
281
- const params = {
282
- reason: "init",
283
- productType: "web-player",
284
- totp: totp,
285
- totpVer: 5,
286
- ts: timestamp,
287
- };
288
- let body;
289
- try {
290
- const response = await axios_1.default.get("https://open.spotify.com/api/token", {
291
- params,
292
- headers: {
293
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.178 Spotify/1.2.65.255 Safari/537.36",
294
- "App-Platform": "WebPlayer",
295
- Referer: "https://open.spotify.com/",
296
- Origin: "https://open.spotify.com",
297
- "Accept-Language": "en",
298
- },
299
- });
300
- body = response.data;
301
- }
302
- catch (error) {
303
- const status = error.response?.status ?? "No response";
304
- console.error("[AutoPlay] Failed to get spotify access token:", status);
305
- return [];
306
- }
307
- let json;
308
- try {
309
- const response = await axios_1.default.get(`https://api.spotify.com/v1/recommendations`, {
310
- params: { limit: 10, seed_tracks: track.identifier },
311
- headers: {
312
- Authorization: `Bearer ${body.accessToken}`,
313
- "Content-Type": "application/json",
314
- },
315
- });
316
- json = response.data;
317
- }
318
- catch (error) {
319
- console.error("[AutoPlay] Failed to fetch spotify recommendations:", error.response?.status);
320
- return [];
321
- }
322
- if (!json.tracks || !json.tracks.length) {
323
- return [];
324
- }
325
- const recommendedTrackId = json.tracks[Math.floor(Math.random() * json.tracks.length)].id;
326
- const res = await this.manager.search({ query: `https://open.spotify.com/track/${recommendedTrackId}`, source: Enums_1.SearchPlatform.Spotify }, track.requester);
327
- if (res.loadType === Enums_1.LoadTypes.Empty || res.loadType === Enums_1.LoadTypes.Error) {
313
+ if (!track.uri.includes("spotify")) {
314
+ const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Enums_1.SearchPlatform.Spotify }, requester);
315
+ if (TrackUtils.isErrorOrEmptySearchResult(res))
328
316
  return [];
317
+ let resolvedTrack;
318
+ switch (res.loadType) {
319
+ case Enums_1.LoadTypes.Playlist:
320
+ resolvedTrack = res.playlist.tracks[0];
321
+ break;
322
+ case Enums_1.LoadTypes.Track:
323
+ case Enums_1.LoadTypes.Search:
324
+ resolvedTrack = res.tracks[0];
325
+ break;
326
+ default:
327
+ return [];
329
328
  }
330
- if (res.loadType === Enums_1.LoadTypes.Playlist) {
331
- res.tracks = res.playlist.tracks;
332
- }
333
- if (!res.tracks.length) {
329
+ if (!resolvedTrack)
334
330
  return [];
335
- }
336
- return res.tracks;
337
- }
338
- catch (error) {
339
- console.error("[AutoPlay] Unexpected spotify error:", error.message || error);
340
- return [];
341
- }
331
+ track = resolvedTrack;
332
+ }
333
+ const extractSpotifyArtistID = (url) => {
334
+ const regex = /https:\/\/open\.spotify\.com\/artist\/([a-zA-Z0-9]+)/;
335
+ const match = url.match(regex);
336
+ return match ? match[1] : null;
337
+ };
338
+ const identifier = `sprec:seed_artists=${extractSpotifyArtistID(track.pluginInfo.artistUrl)}&seed_tracks=${track.identifier}`;
339
+ const recommendedResult = (await this.manager.useableNode.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
340
+ const tracks = this.buildTracksFromResponse(recommendedResult, requester);
341
+ return tracks;
342
342
  }
343
343
  break;
344
- case "deezer":
344
+ case Enums_1.AutoPlayPlatform.Deezer:
345
345
  {
346
346
  if (!track.uri.includes("deezer")) {
347
- const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Enums_1.SearchPlatform.Deezer }, track.requester);
348
- if (res.loadType === Enums_1.LoadTypes.Empty || res.loadType === Enums_1.LoadTypes.Error) {
347
+ const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Enums_1.SearchPlatform.Deezer }, requester);
348
+ if (TrackUtils.isErrorOrEmptySearchResult(res))
349
349
  return [];
350
+ let resolvedTrack;
351
+ switch (res.loadType) {
352
+ case Enums_1.LoadTypes.Playlist:
353
+ resolvedTrack = res.playlist.tracks[0];
354
+ break;
355
+ case Enums_1.LoadTypes.Track:
356
+ case Enums_1.LoadTypes.Search:
357
+ resolvedTrack = res.tracks[0];
358
+ break;
359
+ default:
360
+ return [];
350
361
  }
351
- if (res.loadType === Enums_1.LoadTypes.Playlist) {
352
- res.tracks = res.playlist.tracks;
353
- }
354
- if (!res.tracks.length) {
362
+ if (!resolvedTrack)
355
363
  return [];
356
- }
357
- track = res.tracks[0];
364
+ track = resolvedTrack;
358
365
  }
359
366
  const identifier = `dzrec:${track.identifier}`;
360
367
  const recommendedResult = (await this.manager.useableNode.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
361
- if (!recommendedResult) {
362
- return [];
363
- }
364
- let tracks = [];
365
- let playlist = null;
366
- const requester = track.requester;
367
- switch (recommendedResult.loadType) {
368
- case Enums_1.LoadTypes.Search:
369
- tracks = recommendedResult.data.map((track) => TrackUtils.build(track, requester));
370
- break;
371
- case Enums_1.LoadTypes.Track:
372
- tracks = [TrackUtils.build(recommendedResult.data, requester)];
373
- break;
374
- case Enums_1.LoadTypes.Playlist: {
375
- const playlistData = recommendedResult.data;
376
- tracks = playlistData.tracks.map((track) => TrackUtils.build(track, requester));
377
- playlist = {
378
- name: playlistData.info.name,
379
- playlistInfo: playlistData.pluginInfo,
380
- requester: requester,
381
- tracks,
382
- duration: tracks.reduce((acc, cur) => acc + (cur.duration || 0), 0),
383
- };
384
- break;
385
- }
386
- }
387
- const result = { loadType: recommendedResult.loadType, tracks, playlist };
388
- if (result.loadType === Enums_1.LoadTypes.Empty || result.loadType === Enums_1.LoadTypes.Error) {
389
- return [];
390
- }
391
- if (result.loadType === Enums_1.LoadTypes.Playlist) {
392
- result.tracks = result.playlist.tracks;
393
- }
394
- if (!result.tracks.length) {
395
- return [];
396
- }
397
- return result.tracks;
368
+ const tracks = this.buildTracksFromResponse(recommendedResult, requester);
369
+ return tracks;
398
370
  }
399
371
  break;
400
- case "soundcloud":
372
+ case Enums_1.AutoPlayPlatform.SoundCloud:
401
373
  {
402
374
  if (!track.uri.includes("soundcloud")) {
403
- const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Enums_1.SearchPlatform.SoundCloud }, track.requester);
404
- if (res.loadType === Enums_1.LoadTypes.Empty || res.loadType === Enums_1.LoadTypes.Error) {
375
+ const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Enums_1.SearchPlatform.SoundCloud }, requester);
376
+ if (TrackUtils.isErrorOrEmptySearchResult(res))
405
377
  return [];
378
+ let resolvedTrack;
379
+ switch (res.loadType) {
380
+ case Enums_1.LoadTypes.Playlist:
381
+ resolvedTrack = res.playlist.tracks[0];
382
+ break;
383
+ case Enums_1.LoadTypes.Track:
384
+ case Enums_1.LoadTypes.Search:
385
+ resolvedTrack = res.tracks[0];
386
+ break;
387
+ default:
388
+ return [];
406
389
  }
407
- if (res.loadType === Enums_1.LoadTypes.Playlist) {
408
- res.tracks = res.playlist.tracks;
409
- }
410
- if (!res.tracks.length) {
390
+ if (!resolvedTrack)
411
391
  return [];
412
- }
413
- track = res.tracks[0];
392
+ track = resolvedTrack;
414
393
  }
415
394
  try {
416
395
  const recommendedRes = await axios_1.default.get(`${track.uri}/recommended`).catch((err) => {
@@ -440,17 +419,22 @@ class AutoPlayUtils {
440
419
  return [];
441
420
  }
442
421
  const randomUrl = urls[Math.floor(Math.random() * urls.length)];
443
- const res = await this.manager.search({ query: randomUrl, source: Enums_1.SearchPlatform.SoundCloud }, track.requester);
444
- if (res.loadType === Enums_1.LoadTypes.Empty || res.loadType === Enums_1.LoadTypes.Error) {
445
- return [];
446
- }
447
- if (res.loadType === Enums_1.LoadTypes.Playlist) {
448
- res.tracks = res.playlist.tracks;
422
+ const res = await this.manager.search({ query: randomUrl, source: Enums_1.SearchPlatform.SoundCloud }, requester);
423
+ let resolvedTrack;
424
+ switch (res.loadType) {
425
+ case Enums_1.LoadTypes.Playlist:
426
+ resolvedTrack = res.playlist.tracks[0];
427
+ break;
428
+ case Enums_1.LoadTypes.Track:
429
+ case Enums_1.LoadTypes.Search:
430
+ resolvedTrack = res.tracks[0];
431
+ break;
432
+ default:
433
+ return [];
449
434
  }
450
- if (!res.tracks.length) {
435
+ if (!resolvedTrack)
451
436
  return [];
452
- }
453
- return res.tracks;
437
+ return [resolvedTrack];
454
438
  }
455
439
  catch (error) {
456
440
  console.error("[AutoPlay] Error occurred while fetching soundcloud recommendations:", error);
@@ -458,7 +442,7 @@ class AutoPlayUtils {
458
442
  }
459
443
  }
460
444
  break;
461
- case "youtube":
445
+ case Enums_1.AutoPlayPlatform.YouTube:
462
446
  {
463
447
  const hasYouTubeURL = ["youtube.com", "youtu.be"].some((url) => track.uri.includes(url));
464
448
  let videoID = null;
@@ -466,8 +450,25 @@ class AutoPlayUtils {
466
450
  videoID = track.uri.split("=").pop();
467
451
  }
468
452
  else {
469
- const searchResult = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Enums_1.SearchPlatform.YouTube }, track.requester);
470
- videoID = searchResult.tracks[0]?.uri.split("=").pop();
453
+ const searchResult = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Enums_1.SearchPlatform.YouTube }, requester);
454
+ if (TrackUtils.isErrorOrEmptySearchResult(searchResult)) {
455
+ return [];
456
+ }
457
+ let resolvedTrack;
458
+ switch (searchResult.loadType) {
459
+ case Enums_1.LoadTypes.Playlist:
460
+ resolvedTrack = searchResult.playlist.tracks[0];
461
+ break;
462
+ case Enums_1.LoadTypes.Track:
463
+ case Enums_1.LoadTypes.Search:
464
+ resolvedTrack = searchResult.tracks[0];
465
+ break;
466
+ default:
467
+ return [];
468
+ }
469
+ if (!resolvedTrack)
470
+ return [];
471
+ videoID = resolvedTrack.uri.split("=").pop();
471
472
  }
472
473
  if (!videoID) {
473
474
  return [];
@@ -478,200 +479,181 @@ class AutoPlayUtils {
478
479
  randomIndex = Math.floor(Math.random() * 23) + 2;
479
480
  searchURI = `https://www.youtube.com/watch?v=${videoID}&list=RD${videoID}&index=${randomIndex}`;
480
481
  } while (track.uri.includes(searchURI));
481
- const res = await this.manager.search({ query: searchURI, source: Enums_1.SearchPlatform.YouTube }, track.requester);
482
- if (res.loadType === Enums_1.LoadTypes.Empty || res.loadType === Enums_1.LoadTypes.Error) {
482
+ const res = await this.manager.search({ query: searchURI, source: Enums_1.SearchPlatform.YouTube }, requester);
483
+ if (TrackUtils.isErrorOrEmptySearchResult(res)) {
483
484
  return [];
484
485
  }
485
- const filteredTracks = res.tracks.filter((t) => t.uri !== track.uri);
486
+ let resolvedTracks;
487
+ switch (res.loadType) {
488
+ case Enums_1.LoadTypes.Playlist:
489
+ resolvedTracks = res.playlist.tracks;
490
+ break;
491
+ case Enums_1.LoadTypes.Track:
492
+ case Enums_1.LoadTypes.Search:
493
+ resolvedTracks = res.tracks;
494
+ break;
495
+ default:
496
+ return [];
497
+ }
498
+ const filteredTracks = resolvedTracks.filter((t) => t.uri !== track.uri);
486
499
  return filteredTracks;
487
500
  }
488
501
  break;
489
- case "tidal":
502
+ case Enums_1.AutoPlayPlatform.Tidal:
490
503
  {
491
504
  if (!track.uri.includes("tidal")) {
492
- const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Enums_1.SearchPlatform.Tidal }, track.requester);
493
- if (res.loadType === Enums_1.LoadTypes.Empty || res.loadType === Enums_1.LoadTypes.Error) {
505
+ const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Enums_1.SearchPlatform.Tidal }, requester);
506
+ if (TrackUtils.isErrorOrEmptySearchResult(res))
494
507
  return [];
508
+ let resolvedTrack;
509
+ switch (res.loadType) {
510
+ case Enums_1.LoadTypes.Playlist:
511
+ resolvedTrack = res.playlist.tracks[0];
512
+ break;
513
+ case Enums_1.LoadTypes.Track:
514
+ case Enums_1.LoadTypes.Search:
515
+ resolvedTrack = res.tracks[0];
516
+ break;
517
+ default:
518
+ return [];
495
519
  }
496
- if (res.loadType === Enums_1.LoadTypes.Playlist) {
497
- res.tracks = res.playlist.tracks;
498
- }
499
- if (!res.tracks.length) {
520
+ if (!resolvedTrack)
500
521
  return [];
501
- }
502
- track = res.tracks[0];
522
+ track = resolvedTrack;
503
523
  }
504
524
  const identifier = `tdrec:${track.identifier}`;
505
525
  const recommendedResult = (await this.manager.useableNode.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
506
- if (!recommendedResult) {
507
- return [];
508
- }
509
- let tracks = [];
510
- let playlist = null;
511
- const requester = track.requester;
512
- switch (recommendedResult.loadType) {
513
- case Enums_1.LoadTypes.Search:
514
- tracks = recommendedResult.data.map((track) => TrackUtils.build(track, requester));
515
- break;
516
- case Enums_1.LoadTypes.Track:
517
- tracks = [TrackUtils.build(recommendedResult.data, requester)];
518
- break;
519
- case Enums_1.LoadTypes.Playlist: {
520
- const playlistData = recommendedResult.data;
521
- tracks = playlistData.tracks.map((track) => TrackUtils.build(track, requester));
522
- playlist = {
523
- name: playlistData.info.name,
524
- playlistInfo: playlistData.pluginInfo,
525
- requester: requester,
526
- tracks,
527
- duration: tracks.reduce((acc, cur) => acc + (cur.duration || 0), 0),
528
- };
529
- break;
530
- }
531
- }
532
- const result = { loadType: recommendedResult.loadType, tracks, playlist };
533
- if (result.loadType === Enums_1.LoadTypes.Empty || result.loadType === Enums_1.LoadTypes.Error) {
534
- return [];
535
- }
536
- if (result.loadType === Enums_1.LoadTypes.Playlist) {
537
- result.tracks = result.playlist.tracks;
538
- }
539
- if (!result.tracks.length) {
540
- return [];
541
- }
542
- return result.tracks;
526
+ const tracks = this.buildTracksFromResponse(recommendedResult, requester);
527
+ return tracks;
543
528
  }
544
529
  break;
545
- case "vkmusic":
530
+ case Enums_1.AutoPlayPlatform.VKMusic:
546
531
  {
547
532
  if (!track.uri.includes("vk.com") && !track.uri.includes("vk.ru")) {
548
- const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Enums_1.SearchPlatform.VKMusic }, track.requester);
549
- if (res.loadType === Enums_1.LoadTypes.Empty || res.loadType === Enums_1.LoadTypes.Error) {
533
+ const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Enums_1.SearchPlatform.VKMusic }, requester);
534
+ if (TrackUtils.isErrorOrEmptySearchResult(res))
550
535
  return [];
536
+ let resolvedTrack;
537
+ switch (res.loadType) {
538
+ case Enums_1.LoadTypes.Playlist:
539
+ resolvedTrack = res.playlist.tracks[0];
540
+ break;
541
+ case Enums_1.LoadTypes.Track:
542
+ case Enums_1.LoadTypes.Search:
543
+ resolvedTrack = res.tracks[0];
544
+ break;
545
+ default:
546
+ return [];
551
547
  }
552
- if (res.loadType === Enums_1.LoadTypes.Playlist) {
553
- res.tracks = res.playlist.tracks;
554
- }
555
- if (!res.tracks.length) {
548
+ if (!resolvedTrack)
556
549
  return [];
557
- }
558
- track = res.tracks[0];
550
+ track = resolvedTrack;
559
551
  }
560
552
  const identifier = `vkrec:${track.identifier}`;
561
553
  const recommendedResult = (await this.manager.useableNode.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
562
- if (!recommendedResult) {
563
- return [];
564
- }
565
- let tracks = [];
566
- let playlist = null;
567
- const requester = track.requester;
568
- switch (recommendedResult.loadType) {
569
- case Enums_1.LoadTypes.Search:
570
- tracks = recommendedResult.data.map((track) => TrackUtils.build(track, requester));
571
- break;
572
- case Enums_1.LoadTypes.Track:
573
- tracks = [TrackUtils.build(recommendedResult.data, requester)];
574
- break;
575
- case Enums_1.LoadTypes.Playlist: {
576
- const playlistData = recommendedResult.data;
577
- tracks = playlistData.tracks.map((track) => TrackUtils.build(track, requester));
578
- playlist = {
579
- name: playlistData.info.name,
580
- playlistInfo: playlistData.pluginInfo,
581
- requester: requester,
582
- tracks,
583
- duration: tracks.reduce((acc, cur) => acc + (cur.duration || 0), 0),
584
- };
585
- break;
586
- }
587
- }
588
- const result = { loadType: recommendedResult.loadType, tracks, playlist };
589
- if (result.loadType === Enums_1.LoadTypes.Empty || result.loadType === Enums_1.LoadTypes.Error) {
590
- return [];
591
- }
592
- if (result.loadType === Enums_1.LoadTypes.Playlist) {
593
- result.tracks = result.playlist.tracks;
594
- }
595
- if (!result.tracks.length) {
596
- return [];
597
- }
598
- return result.tracks;
554
+ const tracks = this.buildTracksFromResponse(recommendedResult, requester);
555
+ return tracks;
599
556
  }
600
557
  break;
601
- case "qobuz":
558
+ case Enums_1.AutoPlayPlatform.Qobuz:
602
559
  {
603
560
  if (!track.uri.includes("qobuz.com")) {
604
- const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Enums_1.SearchPlatform.Qobuz }, track.requester);
605
- if (res.loadType === Enums_1.LoadTypes.Empty || res.loadType === Enums_1.LoadTypes.Error) {
561
+ const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Enums_1.SearchPlatform.Qobuz }, requester);
562
+ if (TrackUtils.isErrorOrEmptySearchResult(res))
606
563
  return [];
564
+ let resolvedTrack;
565
+ switch (res.loadType) {
566
+ case Enums_1.LoadTypes.Playlist:
567
+ resolvedTrack = res.playlist.tracks[0];
568
+ break;
569
+ case Enums_1.LoadTypes.Track:
570
+ case Enums_1.LoadTypes.Search:
571
+ resolvedTrack = res.tracks[0];
572
+ break;
573
+ default:
574
+ return [];
607
575
  }
608
- if (res.loadType === Enums_1.LoadTypes.Playlist) {
609
- res.tracks = res.playlist.tracks;
610
- }
611
- if (!res.tracks.length) {
576
+ if (!resolvedTrack)
612
577
  return [];
613
- }
614
- track = res.tracks[0];
578
+ track = resolvedTrack;
615
579
  }
616
580
  const identifier = `qbrec:${track.identifier}`;
617
581
  const recommendedResult = (await this.manager.useableNode.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
618
- if (!recommendedResult) {
619
- return [];
620
- }
621
- let tracks = [];
622
- let playlist = null;
623
- const requester = track.requester;
624
- switch (recommendedResult.loadType) {
625
- case Enums_1.LoadTypes.Search:
626
- tracks = recommendedResult.data.map((track) => TrackUtils.build(track, requester));
627
- break;
628
- case Enums_1.LoadTypes.Track:
629
- tracks = [TrackUtils.build(recommendedResult.data, requester)];
630
- break;
631
- case Enums_1.LoadTypes.Playlist: {
632
- const playlistData = recommendedResult.data;
633
- tracks = playlistData.tracks.map((track) => TrackUtils.build(track, requester));
634
- playlist = {
635
- name: playlistData.info.name,
636
- playlistInfo: playlistData.pluginInfo,
637
- requester: requester,
638
- tracks,
639
- duration: tracks.reduce((acc, cur) => acc + (cur.duration || 0), 0),
640
- };
641
- break;
642
- }
643
- }
644
- const result = { loadType: recommendedResult.loadType, tracks, playlist };
645
- if (result.loadType === Enums_1.LoadTypes.Empty || result.loadType === Enums_1.LoadTypes.Error) {
646
- return [];
647
- }
648
- if (result.loadType === Enums_1.LoadTypes.Playlist) {
649
- result.tracks = result.playlist.tracks;
650
- }
651
- if (!result.tracks.length) {
652
- return [];
653
- }
654
- return result.tracks;
582
+ const tracks = this.buildTracksFromResponse(recommendedResult, requester);
583
+ return tracks;
655
584
  }
656
585
  break;
657
586
  default:
658
587
  return [];
659
588
  }
660
589
  }
661
- static generateTotp() {
662
- const TOTP_SECRET = new Uint8Array([
663
- 53, 53, 48, 55, 49, 52, 53, 56, 53, 51, 52, 56, 55, 52, 57, 57, 53, 57, 50, 50, 52, 56, 54, 51, 48, 51, 50, 57, 51, 52, 55,
664
- ]);
665
- const hmac = crypto_1.default.createHmac("sha1", TOTP_SECRET);
666
- const counter = Math.floor(Date.now() / 30000);
667
- const counterBuffer = Buffer.alloc(8);
668
- counterBuffer.writeBigInt64BE(BigInt(counter));
669
- hmac.update(counterBuffer);
670
- const hmacResult = hmac.digest();
671
- const offset = hmacResult[hmacResult.length - 1] & 15;
672
- const truncatedValue = ((hmacResult[offset] & 127) << 24) | ((hmacResult[offset + 1] & 255) << 16) | ((hmacResult[offset + 2] & 255) << 8) | (hmacResult[offset + 3] & 255);
673
- const totp = (truncatedValue % 1000000).toString().padStart(6, "0");
674
- return [totp, counter * 30000];
590
+ // static async getSpotifyAccessToken() {
591
+ // const timeoutMs = 15000;
592
+ // let browser;
593
+ // let timeout;
594
+ // try {
595
+ // browser = await playwright.chromium.launch({ headless: true, args: ["--no-sandbox", "--disable-gpu", "--disable-dev-shm-usage"] });
596
+ // const page = await browser.newPage();
597
+ // let tokenCaptured = false;
598
+ // timeout = setTimeout(async () => {
599
+ // if (!tokenCaptured) {
600
+ // console.warn("[Spotify] Token request timeout did Spotify change their internals?");
601
+ // await browser?.close();
602
+ // }
603
+ // }, timeoutMs);
604
+ // page.on("requestfinished", async (request) => {
605
+ // if (!request.url().includes("/api/token")) return;
606
+ // tokenCaptured = true;
607
+ // try {
608
+ // const response = await request.response();
609
+ // if (response && response.ok()) {
610
+ // const data = await response.json();
611
+ // this.cachedAccessToken = data?.accessToken ?? null;
612
+ // this.cachedAccessTokenExpiresAt = data?.accessTokenExpirationTimestampMs ?? 0;
613
+ // }
614
+ // } catch (err) {
615
+ // console.error("[Spotify] Error reading token response:", err);
616
+ // }
617
+ // clearTimeout(timeout);
618
+ // page.removeAllListeners();
619
+ // await browser.close();
620
+ // });
621
+ // try {
622
+ // await page.goto("https://open.spotify.com/", { waitUntil: "domcontentloaded" });
623
+ // } catch (err) {
624
+ // clearTimeout(timeout);
625
+ // await browser.close();
626
+ // console.error("[Spotify] Failed to navigate:", err);
627
+ // return [];
628
+ // }
629
+ // } catch (err) {
630
+ // clearTimeout(timeout);
631
+ // await browser?.close();
632
+ // console.error("[Spotify] Failed to launch Playwright:", err);
633
+ // }
634
+ // }
635
+ static buildTracksFromResponse(recommendedResult, requester) {
636
+ if (!recommendedResult)
637
+ return [];
638
+ if (TrackUtils.isErrorOrEmptySearchResult(recommendedResult))
639
+ return [];
640
+ switch (recommendedResult.loadType) {
641
+ case Enums_1.LoadTypes.Search: {
642
+ const tracks = recommendedResult.data.map((t) => TrackUtils.build(t, requester));
643
+ return tracks;
644
+ }
645
+ case Enums_1.LoadTypes.Track: {
646
+ const track = TrackUtils.build(recommendedResult.data, requester);
647
+ return [track];
648
+ }
649
+ case Enums_1.LoadTypes.Playlist: {
650
+ const playlistData = recommendedResult.data;
651
+ const tracks = playlistData.tracks.map((t) => TrackUtils.build(t, requester));
652
+ return tracks;
653
+ }
654
+ default:
655
+ throw new Error(`Unsupported loadType: ${recommendedResult.loadType}`);
656
+ }
675
657
  }
676
658
  }
677
659
  exports.AutoPlayUtils = AutoPlayUtils;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magmastream",
3
- "version": "2.9.0-dev.32",
3
+ "version": "2.9.0-dev.34",
4
4
  "description": "A user-friendly Lavalink client designed for NodeJS.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",