lavalink-client 2.7.6 → 2.7.8

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/README.md CHANGED
@@ -32,6 +32,7 @@
32
32
  - ✨ **Flexible Queue Stores:** Use the default in-memory store or bring your own (Redis, databases, etc.) to sync queues across multiple processes.
33
33
  - 🎶 **Unresolved Tracks:** Supports unresolved track objects, fetching full data only when a track is about to play, saving API requests and resources.
34
34
  - 🎚️ **Built-in Filters & EQ:** Easy-to-use management for audio filters and equalizers.
35
+ - 🔍 **Advanced Queue Filtering:** Search and filter tracks in the queue by title, author, duration, and more with powerful query options.
35
36
  - ⚙️ **Advanced Player Options:** Fine-tune player behavior for disconnects, empty queues, volume handling, and more.
36
37
  - 🛡️ **Lavalink-Side Validation:** Ensures you only use filters, plugins, and sources that your Lavalink node actually supports.
37
38
  - 🔒 **Client-Side Validation:** Whitelist and blacklist URLs or domains to prevent unwanted requests and protect your bot.
package/dist/index.d.mts CHANGED
@@ -704,6 +704,77 @@ declare class Queue {
704
704
  * @returns {number}
705
705
  */
706
706
  totalDuration: () => number;
707
+ /**
708
+ * Find tracks in the queue matching specific criteria.
709
+ * **This method DOES NOT MUTATE the queue** - it returns a new array without modifying the original queue.
710
+ * @param predicate Function to test each track, or an object with criteria to match
711
+ * @returns Array of matching tracks with their indexes
712
+ *
713
+ * @example
714
+ * ```ts
715
+ * // Find by author
716
+ * const artistTracks = player.queue.utils.filterTracks({ author: "Artist Name" });
717
+ *
718
+ * // Find by duration range (5-10 minutes)
719
+ * const longTracks = player.queue.utils.filterTracks({ duration: { min: 300000, max: 600000 } });
720
+ *
721
+ * // Find by title (partial match)
722
+ * const titleMatches = player.queue.utils.filterTracks({ title: "Never Gonna" });
723
+ *
724
+ * // Custom predicate
725
+ * const customFilter = player.queue.utils.filterTracks(track => track.info.isStream);
726
+ * ```
727
+ */
728
+ filterTracks: (predicate: ((track: Track | UnresolvedTrack, index: number) => boolean) | {
729
+ title?: string;
730
+ author?: string;
731
+ duration?: number | {
732
+ min?: number;
733
+ max?: number;
734
+ };
735
+ uri?: string;
736
+ identifier?: string;
737
+ sourceName?: string;
738
+ isStream?: boolean;
739
+ isSeekable?: boolean;
740
+ }) => Array<{
741
+ track: Track | UnresolvedTrack;
742
+ index: number;
743
+ }>;
744
+ /**
745
+ * Find a single track in the queue matching specific criteria.
746
+ * **This method DOES NOT MUTATE the queue** - it searches without modifying the original queue.
747
+ * @param predicate Function to test each track, or an object with criteria to match
748
+ * @returns First matching track with its index, or null if not found
749
+ *
750
+ * @example
751
+ * ```ts
752
+ * // Find first track by author
753
+ * const track = player.queue.utils.findTrack({ author: "Artist Name" });
754
+ * if (track) {
755
+ * console.log(`Found at index ${track.index}: ${track.track.info.title}`);
756
+ * }
757
+ *
758
+ * // Find with custom predicate
759
+ * const liveStream = player.queue.utils.findTrack(track => track.info.isStream);
760
+ * ```
761
+ */
762
+ findTrack: (predicate: ((track: Track | UnresolvedTrack, index: number) => boolean) | {
763
+ title?: string;
764
+ author?: string;
765
+ duration?: number | {
766
+ min?: number;
767
+ max?: number;
768
+ };
769
+ uri?: string;
770
+ identifier?: string;
771
+ sourceName?: string;
772
+ isStream?: boolean;
773
+ isSeekable?: boolean;
774
+ }) => {
775
+ track: Track | UnresolvedTrack;
776
+ index: number;
777
+ } | null;
707
778
  };
708
779
  /**
709
780
  * Shuffles the current Queue, then saves it
@@ -773,6 +844,126 @@ declare class Queue {
773
844
  * ```
774
845
  */
775
846
  shiftPrevious(): Promise<Track>;
847
+ /**
848
+ * Find tracks in the queue matching specific criteria.
849
+ * **This method DOES NOT MUTATE the queue** - it returns a new array without modifying the original queue.
850
+ * @deprecated Use `player.queue.utils.filterTracks()` instead.
851
+ * @param predicate Function to test each track, or an object with criteria to match
852
+ * @returns Array of matching tracks with their indexes
853
+ *
854
+ * @example
855
+ * ```ts
856
+ * // Use the new method instead:
857
+ * const artistTracks = player.queue.utils.filterTracks({ author: "Artist Name" });
858
+ * ```
859
+ */
860
+ filter(predicate: ((track: Track | UnresolvedTrack, index: number) => boolean) | {
861
+ title?: string;
862
+ author?: string;
863
+ duration?: number | {
864
+ min?: number;
865
+ max?: number;
866
+ };
867
+ uri?: string;
868
+ identifier?: string;
869
+ sourceName?: string;
870
+ isStream?: boolean;
871
+ isSeekable?: boolean;
872
+ }): Array<{
873
+ track: Track | UnresolvedTrack;
874
+ index: number;
875
+ }>;
876
+ /**
877
+ * Find a single track in the queue matching specific criteria.
878
+ * **This method DOES NOT MUTATE the queue** - it searches without modifying the original queue.
879
+ * @deprecated Use `player.queue.utils.findTrack()` instead.
880
+ * @param predicate Function to test each track, or an object with criteria to match
881
+ * @returns First matching track with its index, or null if not found
882
+ *
883
+ * @example
884
+ * ```ts
885
+ * // Use the new method instead:
886
+ * const track = player.queue.utils.findTrack({ author: "Artist Name" });
887
+ * ```
888
+ */
889
+ find(predicate: ((track: Track | UnresolvedTrack, index: number) => boolean) | {
890
+ title?: string;
891
+ author?: string;
892
+ duration?: number | {
893
+ min?: number;
894
+ max?: number;
895
+ };
896
+ uri?: string;
897
+ identifier?: string;
898
+ sourceName?: string;
899
+ isStream?: boolean;
900
+ isSeekable?: boolean;
901
+ }): {
902
+ track: Track | UnresolvedTrack;
903
+ index: number;
904
+ } | null;
905
+ /**
906
+ * Sort the queue tracks by a specific property.
907
+ * **⚠️ This method MUTATES the queue** - it modifies the original queue in place.
908
+ * @param sortBy Property to sort by or custom comparator function
909
+ * @param order Sort order: 'asc' or 'desc' (default: 'asc')
910
+ * @returns The queue instance for chaining
911
+ *
912
+ * @example
913
+ * ```ts
914
+ * // Sort by duration (shortest first)
915
+ * await player.queue.sortBy("duration", "asc");
916
+ *
917
+ * // Sort by title alphabetically (Z-A)
918
+ * await player.queue.sortBy("title", "desc");
919
+ *
920
+ * // Custom sorting
921
+ * await player.queue.sortBy((a, b) => {
922
+ * return a.info.title.localeCompare(b.info.title);
923
+ * });
924
+ * ```
925
+ */
926
+ sortBy(sortBy: "duration" | "title" | "author" | ((a: Track | UnresolvedTrack, b: Track | UnresolvedTrack) => number), order?: "asc" | "desc"): Promise<this>;
927
+ /**
928
+ * Get a sorted copy of the queue tracks without modifying the original queue.
929
+ * **This method DOES NOT MUTATE the queue** - it returns a new sorted array, similar to `Array.toSorted()`.
930
+ * @param sortBy Property to sort by or custom comparator function
931
+ * @param order Sort order: 'asc' or 'desc' (default: 'asc')
932
+ * @returns A new sorted array of tracks (does not modify the queue)
933
+ *
934
+ * @example
935
+ * ```ts
936
+ * // Get sorted copy by duration (shortest first)
937
+ * const sortedTracks = player.queue.toSortedBy("duration", "asc");
938
+ * // Original queue remains unchanged
939
+ *
940
+ * // Get sorted copy by title alphabetically (Z-A)
941
+ * const sortedByTitle = player.queue.toSortedBy("title", "desc");
942
+ *
943
+ * // Custom sorting
944
+ * const customSorted = player.queue.toSortedBy((a, b) => {
945
+ * return a.info.title.localeCompare(b.info.title);
946
+ * });
947
+ * ```
948
+ */
949
+ toSortedBy(sortBy: "duration" | "title" | "author" | ((a: Track | UnresolvedTrack, b: Track | UnresolvedTrack) => number), order?: "asc" | "desc"): (Track | UnresolvedTrack)[];
950
+ /**
951
+ * Get a range of tracks from the queue.
952
+ * **This method DOES NOT MUTATE the queue** - it returns a new array slice, similar to `Array.slice()`.
953
+ * @param start Start index (inclusive)
954
+ * @param end End index (exclusive)
955
+ * @returns Array of tracks in the specified range
956
+ *
957
+ * @example
958
+ * ```ts
959
+ * // Get tracks 5-15
960
+ * const tracks = player.queue.getTracks(5, 15);
961
+ *
962
+ * // Get first 10 tracks
963
+ * const firstTen = player.queue.getTracks(0, 10);
964
+ * ```
965
+ */
966
+ getTracks(start: number, end?: number): (Track | UnresolvedTrack)[];
776
967
  }
777
968
 
778
969
  declare class Player {
@@ -1959,6 +2150,66 @@ interface PlayOptions extends LavalinkPlayOptions {
1959
2150
  clientTrack?: Track | UnresolvedTrack;
1960
2151
  }
1961
2152
 
2153
+ type NodeLinkEventTypes = "PlayerCreatedEvent" | "PlayerDestroyedEvent" | "PlayerConnectedEvent" | "PlayerReconnectingEvent" | "VolumeChangedEvent" | "FiltersChangedEvent" | "SeekEvent" | "PauseEvent" | "ConnectionStatusEvent" | "MixStartedEvent" | "MixEndedEvent";
2154
+ interface NodeLinkBaseEvent {
2155
+ op: "event";
2156
+ type: NodeLinkEventTypes;
2157
+ guildId: string;
2158
+ }
2159
+ interface PlayerCreatedEvent extends NodeLinkBaseEvent {
2160
+ type: "PlayerCreatedEvent";
2161
+ }
2162
+ interface PlayerDestroyedEvent extends NodeLinkBaseEvent {
2163
+ type: "PlayerDestroyedEvent";
2164
+ }
2165
+ interface PlayerConnectedEvent extends NodeLinkBaseEvent {
2166
+ type: "PlayerConnectedEvent";
2167
+ }
2168
+ interface PlayerReconnectingEvent extends NodeLinkBaseEvent {
2169
+ type: "PlayerReconnectingEvent";
2170
+ }
2171
+ interface VolumeChangedEvent extends NodeLinkBaseEvent {
2172
+ type: "VolumeChangedEvent";
2173
+ /** New volume level (0-1000) */
2174
+ volume: number;
2175
+ }
2176
+ interface FiltersChangedEvent extends NodeLinkBaseEvent {
2177
+ type: "FiltersChangedEvent";
2178
+ filters: LavalinkFilterData;
2179
+ }
2180
+ interface SeekEvent extends NodeLinkBaseEvent {
2181
+ type: "SeekEvent";
2182
+ /** New position in milliseconds */
2183
+ position: number;
2184
+ }
2185
+ interface PauseEvent extends NodeLinkBaseEvent {
2186
+ type: "PauseEvent";
2187
+ /** Whether playback is now paused (true) or resumed (false) */
2188
+ paused: boolean;
2189
+ }
2190
+ interface ConnectionStatusEvent extends NodeLinkBaseEvent {
2191
+ type: "ConnectionStatusEvent";
2192
+ /** Current connection status */
2193
+ connected: boolean;
2194
+ }
2195
+ interface MixStartedEvent extends NodeLinkBaseEvent {
2196
+ type: "MixStartedEvent";
2197
+ /** Unique identifier for the mix layer */
2198
+ mixId: string;
2199
+ /** Full track information of the mixed layer */
2200
+ track: LavalinkTrack;
2201
+ /** Volume of the mixed layer (0.0 to 1.0) */
2202
+ volume: number;
2203
+ }
2204
+ interface MixEndedEvent extends NodeLinkBaseEvent {
2205
+ type: "MixEndedEvent";
2206
+ /** Unique identifier for the mix layer */
2207
+ mixId: string;
2208
+ /** Reason the mix layer ended (FINISHED, REMOVED, ERROR, MAIN_ENDED) */
2209
+ reason: "FINISHED" | "REMOVED" | "ERROR" | "MAIN_ENDED" | string;
2210
+ }
2211
+ type NodeLinkEventPayload<T extends NodeLinkEventTypes> = T extends "PlayerCreatedEvent" ? PlayerCreatedEvent : T extends "PlayerDestroyedEvent" ? PlayerDestroyedEvent : T extends "PlayerConnectedEvent" ? PlayerConnectedEvent : T extends "PlayerReconnectingEvent" ? PlayerReconnectingEvent : T extends "VolumeChangedEvent" ? VolumeChangedEvent : T extends "FiltersChangedEvent" ? FiltersChangedEvent : T extends "SeekEvent" ? SeekEvent : T extends "PauseEvent" ? PauseEvent : T extends "ConnectionStatusEvent" ? ConnectionStatusEvent : T extends "MixStartedEvent" ? MixStartedEvent : T extends "MixEndedEvent" ? MixEndedEvent : never;
2212
+
1962
2213
  /** Ability to manipulate fetch requests */
1963
2214
  type ModifyRequest = (options: RequestInit & {
1964
2215
  path: string;
@@ -2224,6 +2475,25 @@ interface NodeManagerEvents {
2224
2475
  sessionId: string;
2225
2476
  op: "ready";
2226
2477
  }, players: LavalinkPlayer[] | InvalidLavalinkRestRequest) => void;
2478
+ /**
2479
+ * Event Handler for Nodelink specific events https://nodelink.js.org/docs/api/websocket Fully typed and generic based on the eventName.
2480
+ * @event Manager.nodeManager#nodeLinkEvent
2481
+ * @example
2482
+ *
2483
+ * ```ts
2484
+ * this.nodeManager.on("nodeLinkEvent", (node, event, player, track, payload) => {
2485
+ * if (event === "SeekEvent") {
2486
+ * console.log("new position:", payload.position);
2487
+ * }
2488
+ * if (event === "FiltersChangedEvent") {
2489
+ * console.log("new filters state", payload.filters);
2490
+ * }
2491
+ * });
2492
+ * ```
2493
+ */
2494
+ "nodeLinkEvent": (...args: {
2495
+ [K in NodeLinkEventTypes]: [node: LavalinkNode, event: K, player: Player, track: Track | null, payload: NodeLinkEventPayload<K>];
2496
+ }[NodeLinkEventTypes]) => void;
2227
2497
  }
2228
2498
  declare enum ReconnectionState {
2229
2499
  IDLE = "IDLE",
@@ -2721,6 +2991,14 @@ declare class LavalinkNode {
2721
2991
  private message;
2722
2992
  /** @private middleware util function for handling all kind of events from websocket */
2723
2993
  private handleEvent;
2994
+ /**
2995
+ * nodeLink specific events handling https://nodelink.js.org/docs/api/websocket#incoming-events-server--client
2996
+ * @param eventName
2997
+ * @param player
2998
+ * @param track
2999
+ * @param payload
3000
+ */
3001
+ private nodeLinkEventHandler;
2724
3002
  private getTrackOfPayload;
2725
3003
  /** @private util function for handling trackStart event */
2726
3004
  private trackStart;
@@ -2944,6 +3222,13 @@ interface LavalinkManagerEvents<CustomPlayerT extends Player = Player> {
2944
3222
  * @event Manager#playerDisconnect
2945
3223
  */
2946
3224
  "playerDisconnect": (player: CustomPlayerT, voiceChannelId: string) => void;
3225
+ /**
3226
+ * Emitted when a Player automatically reconnects after a disconnect.
3227
+ * This event is triggered when the player successfully reconnects to the voice channel
3228
+ * and resumes playback after being disconnected (requires onDisconnect.autoReconnect to be enabled).
3229
+ * @event Manager#playerReconnect
3230
+ */
3231
+ "playerReconnect": (player: CustomPlayerT, voiceChannelId: string) => void;
2947
3232
  /**
2948
3233
  * Emitted when a Node-Socket got closed for a specific Player.
2949
3234
  * Usually emits when the audio websocket to discord is closed, This can happen for various reasons (normal and abnormal), e.g. when using an expired voice server update. 4xxx codes are usually bad.
package/dist/index.d.ts CHANGED
@@ -704,6 +704,77 @@ declare class Queue {
704
704
  * @returns {number}
705
705
  */
706
706
  totalDuration: () => number;
707
+ /**
708
+ * Find tracks in the queue matching specific criteria.
709
+ * **This method DOES NOT MUTATE the queue** - it returns a new array without modifying the original queue.
710
+ * @param predicate Function to test each track, or an object with criteria to match
711
+ * @returns Array of matching tracks with their indexes
712
+ *
713
+ * @example
714
+ * ```ts
715
+ * // Find by author
716
+ * const artistTracks = player.queue.utils.filterTracks({ author: "Artist Name" });
717
+ *
718
+ * // Find by duration range (5-10 minutes)
719
+ * const longTracks = player.queue.utils.filterTracks({ duration: { min: 300000, max: 600000 } });
720
+ *
721
+ * // Find by title (partial match)
722
+ * const titleMatches = player.queue.utils.filterTracks({ title: "Never Gonna" });
723
+ *
724
+ * // Custom predicate
725
+ * const customFilter = player.queue.utils.filterTracks(track => track.info.isStream);
726
+ * ```
727
+ */
728
+ filterTracks: (predicate: ((track: Track | UnresolvedTrack, index: number) => boolean) | {
729
+ title?: string;
730
+ author?: string;
731
+ duration?: number | {
732
+ min?: number;
733
+ max?: number;
734
+ };
735
+ uri?: string;
736
+ identifier?: string;
737
+ sourceName?: string;
738
+ isStream?: boolean;
739
+ isSeekable?: boolean;
740
+ }) => Array<{
741
+ track: Track | UnresolvedTrack;
742
+ index: number;
743
+ }>;
744
+ /**
745
+ * Find a single track in the queue matching specific criteria.
746
+ * **This method DOES NOT MUTATE the queue** - it searches without modifying the original queue.
747
+ * @param predicate Function to test each track, or an object with criteria to match
748
+ * @returns First matching track with its index, or null if not found
749
+ *
750
+ * @example
751
+ * ```ts
752
+ * // Find first track by author
753
+ * const track = player.queue.utils.findTrack({ author: "Artist Name" });
754
+ * if (track) {
755
+ * console.log(`Found at index ${track.index}: ${track.track.info.title}`);
756
+ * }
757
+ *
758
+ * // Find with custom predicate
759
+ * const liveStream = player.queue.utils.findTrack(track => track.info.isStream);
760
+ * ```
761
+ */
762
+ findTrack: (predicate: ((track: Track | UnresolvedTrack, index: number) => boolean) | {
763
+ title?: string;
764
+ author?: string;
765
+ duration?: number | {
766
+ min?: number;
767
+ max?: number;
768
+ };
769
+ uri?: string;
770
+ identifier?: string;
771
+ sourceName?: string;
772
+ isStream?: boolean;
773
+ isSeekable?: boolean;
774
+ }) => {
775
+ track: Track | UnresolvedTrack;
776
+ index: number;
777
+ } | null;
707
778
  };
708
779
  /**
709
780
  * Shuffles the current Queue, then saves it
@@ -773,6 +844,126 @@ declare class Queue {
773
844
  * ```
774
845
  */
775
846
  shiftPrevious(): Promise<Track>;
847
+ /**
848
+ * Find tracks in the queue matching specific criteria.
849
+ * **This method DOES NOT MUTATE the queue** - it returns a new array without modifying the original queue.
850
+ * @deprecated Use `player.queue.utils.filterTracks()` instead.
851
+ * @param predicate Function to test each track, or an object with criteria to match
852
+ * @returns Array of matching tracks with their indexes
853
+ *
854
+ * @example
855
+ * ```ts
856
+ * // Use the new method instead:
857
+ * const artistTracks = player.queue.utils.filterTracks({ author: "Artist Name" });
858
+ * ```
859
+ */
860
+ filter(predicate: ((track: Track | UnresolvedTrack, index: number) => boolean) | {
861
+ title?: string;
862
+ author?: string;
863
+ duration?: number | {
864
+ min?: number;
865
+ max?: number;
866
+ };
867
+ uri?: string;
868
+ identifier?: string;
869
+ sourceName?: string;
870
+ isStream?: boolean;
871
+ isSeekable?: boolean;
872
+ }): Array<{
873
+ track: Track | UnresolvedTrack;
874
+ index: number;
875
+ }>;
876
+ /**
877
+ * Find a single track in the queue matching specific criteria.
878
+ * **This method DOES NOT MUTATE the queue** - it searches without modifying the original queue.
879
+ * @deprecated Use `player.queue.utils.findTrack()` instead.
880
+ * @param predicate Function to test each track, or an object with criteria to match
881
+ * @returns First matching track with its index, or null if not found
882
+ *
883
+ * @example
884
+ * ```ts
885
+ * // Use the new method instead:
886
+ * const track = player.queue.utils.findTrack({ author: "Artist Name" });
887
+ * ```
888
+ */
889
+ find(predicate: ((track: Track | UnresolvedTrack, index: number) => boolean) | {
890
+ title?: string;
891
+ author?: string;
892
+ duration?: number | {
893
+ min?: number;
894
+ max?: number;
895
+ };
896
+ uri?: string;
897
+ identifier?: string;
898
+ sourceName?: string;
899
+ isStream?: boolean;
900
+ isSeekable?: boolean;
901
+ }): {
902
+ track: Track | UnresolvedTrack;
903
+ index: number;
904
+ } | null;
905
+ /**
906
+ * Sort the queue tracks by a specific property.
907
+ * **⚠️ This method MUTATES the queue** - it modifies the original queue in place.
908
+ * @param sortBy Property to sort by or custom comparator function
909
+ * @param order Sort order: 'asc' or 'desc' (default: 'asc')
910
+ * @returns The queue instance for chaining
911
+ *
912
+ * @example
913
+ * ```ts
914
+ * // Sort by duration (shortest first)
915
+ * await player.queue.sortBy("duration", "asc");
916
+ *
917
+ * // Sort by title alphabetically (Z-A)
918
+ * await player.queue.sortBy("title", "desc");
919
+ *
920
+ * // Custom sorting
921
+ * await player.queue.sortBy((a, b) => {
922
+ * return a.info.title.localeCompare(b.info.title);
923
+ * });
924
+ * ```
925
+ */
926
+ sortBy(sortBy: "duration" | "title" | "author" | ((a: Track | UnresolvedTrack, b: Track | UnresolvedTrack) => number), order?: "asc" | "desc"): Promise<this>;
927
+ /**
928
+ * Get a sorted copy of the queue tracks without modifying the original queue.
929
+ * **This method DOES NOT MUTATE the queue** - it returns a new sorted array, similar to `Array.toSorted()`.
930
+ * @param sortBy Property to sort by or custom comparator function
931
+ * @param order Sort order: 'asc' or 'desc' (default: 'asc')
932
+ * @returns A new sorted array of tracks (does not modify the queue)
933
+ *
934
+ * @example
935
+ * ```ts
936
+ * // Get sorted copy by duration (shortest first)
937
+ * const sortedTracks = player.queue.toSortedBy("duration", "asc");
938
+ * // Original queue remains unchanged
939
+ *
940
+ * // Get sorted copy by title alphabetically (Z-A)
941
+ * const sortedByTitle = player.queue.toSortedBy("title", "desc");
942
+ *
943
+ * // Custom sorting
944
+ * const customSorted = player.queue.toSortedBy((a, b) => {
945
+ * return a.info.title.localeCompare(b.info.title);
946
+ * });
947
+ * ```
948
+ */
949
+ toSortedBy(sortBy: "duration" | "title" | "author" | ((a: Track | UnresolvedTrack, b: Track | UnresolvedTrack) => number), order?: "asc" | "desc"): (Track | UnresolvedTrack)[];
950
+ /**
951
+ * Get a range of tracks from the queue.
952
+ * **This method DOES NOT MUTATE the queue** - it returns a new array slice, similar to `Array.slice()`.
953
+ * @param start Start index (inclusive)
954
+ * @param end End index (exclusive)
955
+ * @returns Array of tracks in the specified range
956
+ *
957
+ * @example
958
+ * ```ts
959
+ * // Get tracks 5-15
960
+ * const tracks = player.queue.getTracks(5, 15);
961
+ *
962
+ * // Get first 10 tracks
963
+ * const firstTen = player.queue.getTracks(0, 10);
964
+ * ```
965
+ */
966
+ getTracks(start: number, end?: number): (Track | UnresolvedTrack)[];
776
967
  }
777
968
 
778
969
  declare class Player {
@@ -1959,6 +2150,66 @@ interface PlayOptions extends LavalinkPlayOptions {
1959
2150
  clientTrack?: Track | UnresolvedTrack;
1960
2151
  }
1961
2152
 
2153
+ type NodeLinkEventTypes = "PlayerCreatedEvent" | "PlayerDestroyedEvent" | "PlayerConnectedEvent" | "PlayerReconnectingEvent" | "VolumeChangedEvent" | "FiltersChangedEvent" | "SeekEvent" | "PauseEvent" | "ConnectionStatusEvent" | "MixStartedEvent" | "MixEndedEvent";
2154
+ interface NodeLinkBaseEvent {
2155
+ op: "event";
2156
+ type: NodeLinkEventTypes;
2157
+ guildId: string;
2158
+ }
2159
+ interface PlayerCreatedEvent extends NodeLinkBaseEvent {
2160
+ type: "PlayerCreatedEvent";
2161
+ }
2162
+ interface PlayerDestroyedEvent extends NodeLinkBaseEvent {
2163
+ type: "PlayerDestroyedEvent";
2164
+ }
2165
+ interface PlayerConnectedEvent extends NodeLinkBaseEvent {
2166
+ type: "PlayerConnectedEvent";
2167
+ }
2168
+ interface PlayerReconnectingEvent extends NodeLinkBaseEvent {
2169
+ type: "PlayerReconnectingEvent";
2170
+ }
2171
+ interface VolumeChangedEvent extends NodeLinkBaseEvent {
2172
+ type: "VolumeChangedEvent";
2173
+ /** New volume level (0-1000) */
2174
+ volume: number;
2175
+ }
2176
+ interface FiltersChangedEvent extends NodeLinkBaseEvent {
2177
+ type: "FiltersChangedEvent";
2178
+ filters: LavalinkFilterData;
2179
+ }
2180
+ interface SeekEvent extends NodeLinkBaseEvent {
2181
+ type: "SeekEvent";
2182
+ /** New position in milliseconds */
2183
+ position: number;
2184
+ }
2185
+ interface PauseEvent extends NodeLinkBaseEvent {
2186
+ type: "PauseEvent";
2187
+ /** Whether playback is now paused (true) or resumed (false) */
2188
+ paused: boolean;
2189
+ }
2190
+ interface ConnectionStatusEvent extends NodeLinkBaseEvent {
2191
+ type: "ConnectionStatusEvent";
2192
+ /** Current connection status */
2193
+ connected: boolean;
2194
+ }
2195
+ interface MixStartedEvent extends NodeLinkBaseEvent {
2196
+ type: "MixStartedEvent";
2197
+ /** Unique identifier for the mix layer */
2198
+ mixId: string;
2199
+ /** Full track information of the mixed layer */
2200
+ track: LavalinkTrack;
2201
+ /** Volume of the mixed layer (0.0 to 1.0) */
2202
+ volume: number;
2203
+ }
2204
+ interface MixEndedEvent extends NodeLinkBaseEvent {
2205
+ type: "MixEndedEvent";
2206
+ /** Unique identifier for the mix layer */
2207
+ mixId: string;
2208
+ /** Reason the mix layer ended (FINISHED, REMOVED, ERROR, MAIN_ENDED) */
2209
+ reason: "FINISHED" | "REMOVED" | "ERROR" | "MAIN_ENDED" | string;
2210
+ }
2211
+ type NodeLinkEventPayload<T extends NodeLinkEventTypes> = T extends "PlayerCreatedEvent" ? PlayerCreatedEvent : T extends "PlayerDestroyedEvent" ? PlayerDestroyedEvent : T extends "PlayerConnectedEvent" ? PlayerConnectedEvent : T extends "PlayerReconnectingEvent" ? PlayerReconnectingEvent : T extends "VolumeChangedEvent" ? VolumeChangedEvent : T extends "FiltersChangedEvent" ? FiltersChangedEvent : T extends "SeekEvent" ? SeekEvent : T extends "PauseEvent" ? PauseEvent : T extends "ConnectionStatusEvent" ? ConnectionStatusEvent : T extends "MixStartedEvent" ? MixStartedEvent : T extends "MixEndedEvent" ? MixEndedEvent : never;
2212
+
1962
2213
  /** Ability to manipulate fetch requests */
1963
2214
  type ModifyRequest = (options: RequestInit & {
1964
2215
  path: string;
@@ -2224,6 +2475,25 @@ interface NodeManagerEvents {
2224
2475
  sessionId: string;
2225
2476
  op: "ready";
2226
2477
  }, players: LavalinkPlayer[] | InvalidLavalinkRestRequest) => void;
2478
+ /**
2479
+ * Event Handler for Nodelink specific events https://nodelink.js.org/docs/api/websocket Fully typed and generic based on the eventName.
2480
+ * @event Manager.nodeManager#nodeLinkEvent
2481
+ * @example
2482
+ *
2483
+ * ```ts
2484
+ * this.nodeManager.on("nodeLinkEvent", (node, event, player, track, payload) => {
2485
+ * if (event === "SeekEvent") {
2486
+ * console.log("new position:", payload.position);
2487
+ * }
2488
+ * if (event === "FiltersChangedEvent") {
2489
+ * console.log("new filters state", payload.filters);
2490
+ * }
2491
+ * });
2492
+ * ```
2493
+ */
2494
+ "nodeLinkEvent": (...args: {
2495
+ [K in NodeLinkEventTypes]: [node: LavalinkNode, event: K, player: Player, track: Track | null, payload: NodeLinkEventPayload<K>];
2496
+ }[NodeLinkEventTypes]) => void;
2227
2497
  }
2228
2498
  declare enum ReconnectionState {
2229
2499
  IDLE = "IDLE",
@@ -2721,6 +2991,14 @@ declare class LavalinkNode {
2721
2991
  private message;
2722
2992
  /** @private middleware util function for handling all kind of events from websocket */
2723
2993
  private handleEvent;
2994
+ /**
2995
+ * nodeLink specific events handling https://nodelink.js.org/docs/api/websocket#incoming-events-server--client
2996
+ * @param eventName
2997
+ * @param player
2998
+ * @param track
2999
+ * @param payload
3000
+ */
3001
+ private nodeLinkEventHandler;
2724
3002
  private getTrackOfPayload;
2725
3003
  /** @private util function for handling trackStart event */
2726
3004
  private trackStart;
@@ -2944,6 +3222,13 @@ interface LavalinkManagerEvents<CustomPlayerT extends Player = Player> {
2944
3222
  * @event Manager#playerDisconnect
2945
3223
  */
2946
3224
  "playerDisconnect": (player: CustomPlayerT, voiceChannelId: string) => void;
3225
+ /**
3226
+ * Emitted when a Player automatically reconnects after a disconnect.
3227
+ * This event is triggered when the player successfully reconnects to the voice channel
3228
+ * and resumes playback after being disconnected (requires onDisconnect.autoReconnect to be enabled).
3229
+ * @event Manager#playerReconnect
3230
+ */
3231
+ "playerReconnect": (player: CustomPlayerT, voiceChannelId: string) => void;
2947
3232
  /**
2948
3233
  * Emitted when a Node-Socket got closed for a specific Player.
2949
3234
  * Usually emits when the audio websocket to discord is closed, This can happen for various reasons (normal and abnormal), e.g. when using an expired voice server update. 4xxx codes are usually bad.
package/dist/index.js CHANGED
@@ -1433,7 +1433,6 @@ var LavalinkNode = class {
1433
1433
  functionLayer: "LavalinkNode > nodeEvent > stats > heartBeat()"
1434
1434
  });
1435
1435
  this.resetAckTimeouts(false, true);
1436
- if (this.pingTimeout) clearTimeout(this.pingTimeout);
1437
1436
  this.pingTimeout = setTimeout(() => {
1438
1437
  this.pingTimeout = null;
1439
1438
  if (!this.socket) {
@@ -1958,9 +1957,10 @@ var LavalinkNode = class {
1958
1957
  }, this.options.retryDelay || 1e3);
1959
1958
  }
1960
1959
  get reconnectionAttemptCount() {
1960
+ if (!Array.isArray(this.reconnectAttempts)) this.reconnectAttempts = [];
1961
1961
  const maxAllowedTimestan = this.options.retryTimespan || -1;
1962
1962
  if (maxAllowedTimestan <= 0) return this.reconnectAttempts.length;
1963
- return this.reconnectAttempts.filter((timestamp) => Date.now() - timestamp <= maxAllowedTimestan).length;
1963
+ return this.reconnectAttempts?.filter((timestamp) => Date.now() - timestamp <= maxAllowedTimestan).length || 0;
1964
1964
  }
1965
1965
  /**
1966
1966
  * Private Utility function to execute the reconnection
@@ -1973,6 +1973,7 @@ var LavalinkNode = class {
1973
1973
  this.destroy("NodeReconnectFail" /* NodeReconnectFail */);
1974
1974
  return;
1975
1975
  }
1976
+ if (!Array.isArray(this.reconnectAttempts)) this.reconnectAttempts = [];
1976
1977
  this.reconnectAttempts.push(Date.now());
1977
1978
  this.reconnectionState = "RECONNECTING" /* RECONNECTING */;
1978
1979
  this.NodeManager.emit("reconnecting", this);
@@ -2157,6 +2158,23 @@ var LavalinkNode = class {
2157
2158
  if (!payload?.guildId) return;
2158
2159
  const player = this._LManager.getPlayer(payload.guildId);
2159
2160
  if (!player) return;
2161
+ const NodeLinkEventType = payload.type;
2162
+ const NodeLinkExclusiveEvents = [
2163
+ "PlayerCreatedEvent",
2164
+ "PlayerDestroyedEvent",
2165
+ "PlayerConnectedEvent",
2166
+ "PlayerReconnectingEvent",
2167
+ "VolumeChangedEvent",
2168
+ "FiltersChangedEvent",
2169
+ "SeekEvent",
2170
+ "PauseEvent",
2171
+ "ConnectionStatusEvent",
2172
+ "MixStartedEvent",
2173
+ "MixEndedEvent"
2174
+ ];
2175
+ if (NodeLinkExclusiveEvents.includes(NodeLinkEventType) && (!this.info || this.info.isNodelink)) {
2176
+ return this.nodeLinkEventHandler(NodeLinkEventType, player, player.queue.current, payload);
2177
+ }
2160
2178
  switch (payload.type) {
2161
2179
  case "TrackStartEvent":
2162
2180
  this.trackStart(player, player.queue.current, payload);
@@ -2200,6 +2218,16 @@ var LavalinkNode = class {
2200
2218
  }
2201
2219
  return;
2202
2220
  }
2221
+ /**
2222
+ * nodeLink specific events handling https://nodelink.js.org/docs/api/websocket#incoming-events-server--client
2223
+ * @param eventName
2224
+ * @param player
2225
+ * @param track
2226
+ * @param payload
2227
+ */
2228
+ async nodeLinkEventHandler(eventName, player, track, payload) {
2229
+ this.NodeManager.emit("nodeLinkEvent", this, eventName, player, track, payload);
2230
+ }
2203
2231
  getTrackOfPayload(payload) {
2204
2232
  return "track" in payload ? this._LManager.utils.buildTrack(payload.track, void 0) : null;
2205
2233
  }
@@ -3790,6 +3818,87 @@ var Queue = class {
3790
3818
  */
3791
3819
  totalDuration: () => {
3792
3820
  return this.tracks.reduce((acc, cur) => acc + (cur.info.duration || 0), this.current?.info.duration || 0);
3821
+ },
3822
+ /**
3823
+ * Find tracks in the queue matching specific criteria.
3824
+ * **This method DOES NOT MUTATE the queue** - it returns a new array without modifying the original queue.
3825
+ * @param predicate Function to test each track, or an object with criteria to match
3826
+ * @returns Array of matching tracks with their indexes
3827
+ *
3828
+ * @example
3829
+ * ```ts
3830
+ * // Find by author
3831
+ * const artistTracks = player.queue.utils.filterTracks({ author: "Artist Name" });
3832
+ *
3833
+ * // Find by duration range (5-10 minutes)
3834
+ * const longTracks = player.queue.utils.filterTracks({ duration: { min: 300000, max: 600000 } });
3835
+ *
3836
+ * // Find by title (partial match)
3837
+ * const titleMatches = player.queue.utils.filterTracks({ title: "Never Gonna" });
3838
+ *
3839
+ * // Custom predicate
3840
+ * const customFilter = player.queue.utils.filterTracks(track => track.info.isStream);
3841
+ * ```
3842
+ */
3843
+ filterTracks: (predicate) => {
3844
+ if (typeof predicate === "function") {
3845
+ return this.tracks.map((track, index) => ({ track, index })).filter(({ track, index }) => predicate(track, index));
3846
+ }
3847
+ return this.tracks.map((track, index) => ({ track, index })).filter(({ track }) => {
3848
+ if (predicate.title && !track.info?.title?.toLowerCase().includes(predicate.title.toLowerCase())) {
3849
+ return false;
3850
+ }
3851
+ if (predicate.author && !track.info?.author?.toLowerCase().includes(predicate.author.toLowerCase())) {
3852
+ return false;
3853
+ }
3854
+ if (predicate.duration !== void 0) {
3855
+ const duration = track.info?.duration || 0;
3856
+ if (typeof predicate.duration === "number") {
3857
+ if (duration !== predicate.duration) return false;
3858
+ } else {
3859
+ if (predicate.duration.min !== void 0 && duration < predicate.duration.min) return false;
3860
+ if (predicate.duration.max !== void 0 && duration > predicate.duration.max) return false;
3861
+ }
3862
+ }
3863
+ if (predicate.uri && track.info?.uri !== predicate.uri) {
3864
+ return false;
3865
+ }
3866
+ if (predicate.identifier && track.info?.identifier !== predicate.identifier) {
3867
+ return false;
3868
+ }
3869
+ if (predicate.sourceName && track.info?.sourceName?.toLowerCase() !== predicate.sourceName.toLowerCase()) {
3870
+ return false;
3871
+ }
3872
+ if (predicate.isStream !== void 0 && track.info?.isStream !== predicate.isStream) {
3873
+ return false;
3874
+ }
3875
+ if (predicate.isSeekable !== void 0 && track.info?.isSeekable !== predicate.isSeekable) {
3876
+ return false;
3877
+ }
3878
+ return true;
3879
+ });
3880
+ },
3881
+ /**
3882
+ * Find a single track in the queue matching specific criteria.
3883
+ * **This method DOES NOT MUTATE the queue** - it searches without modifying the original queue.
3884
+ * @param predicate Function to test each track, or an object with criteria to match
3885
+ * @returns First matching track with its index, or null if not found
3886
+ *
3887
+ * @example
3888
+ * ```ts
3889
+ * // Find first track by author
3890
+ * const track = player.queue.utils.findTrack({ author: "Artist Name" });
3891
+ * if (track) {
3892
+ * console.log(`Found at index ${track.index}: ${track.track.info.title}`);
3893
+ * }
3894
+ *
3895
+ * // Find with custom predicate
3896
+ * const liveStream = player.queue.utils.findTrack(track => track.info.isStream);
3897
+ * ```
3898
+ */
3899
+ findTrack: (predicate) => {
3900
+ const results = this.utils.filterTracks(predicate);
3901
+ return results.length > 0 ? results[0] : null;
3793
3902
  }
3794
3903
  };
3795
3904
  /**
@@ -3971,6 +4080,149 @@ var Queue = class {
3971
4080
  if (removed) await this.utils.save();
3972
4081
  return removed ?? null;
3973
4082
  }
4083
+ /**
4084
+ * Find tracks in the queue matching specific criteria.
4085
+ * **This method DOES NOT MUTATE the queue** - it returns a new array without modifying the original queue.
4086
+ * @deprecated Use `player.queue.utils.filterTracks()` instead.
4087
+ * @param predicate Function to test each track, or an object with criteria to match
4088
+ * @returns Array of matching tracks with their indexes
4089
+ *
4090
+ * @example
4091
+ * ```ts
4092
+ * // Use the new method instead:
4093
+ * const artistTracks = player.queue.utils.filterTracks({ author: "Artist Name" });
4094
+ * ```
4095
+ */
4096
+ filter(predicate) {
4097
+ return this.utils.filterTracks(predicate);
4098
+ }
4099
+ /**
4100
+ * Find a single track in the queue matching specific criteria.
4101
+ * **This method DOES NOT MUTATE the queue** - it searches without modifying the original queue.
4102
+ * @deprecated Use `player.queue.utils.findTrack()` instead.
4103
+ * @param predicate Function to test each track, or an object with criteria to match
4104
+ * @returns First matching track with its index, or null if not found
4105
+ *
4106
+ * @example
4107
+ * ```ts
4108
+ * // Use the new method instead:
4109
+ * const track = player.queue.utils.findTrack({ author: "Artist Name" });
4110
+ * ```
4111
+ */
4112
+ find(predicate) {
4113
+ return this.utils.findTrack(predicate);
4114
+ }
4115
+ /**
4116
+ * Sort the queue tracks by a specific property.
4117
+ * **⚠️ This method MUTATES the queue** - it modifies the original queue in place.
4118
+ * @param sortBy Property to sort by or custom comparator function
4119
+ * @param order Sort order: 'asc' or 'desc' (default: 'asc')
4120
+ * @returns The queue instance for chaining
4121
+ *
4122
+ * @example
4123
+ * ```ts
4124
+ * // Sort by duration (shortest first)
4125
+ * await player.queue.sortBy("duration", "asc");
4126
+ *
4127
+ * // Sort by title alphabetically (Z-A)
4128
+ * await player.queue.sortBy("title", "desc");
4129
+ *
4130
+ * // Custom sorting
4131
+ * await player.queue.sortBy((a, b) => {
4132
+ * return a.info.title.localeCompare(b.info.title);
4133
+ * });
4134
+ * ```
4135
+ */
4136
+ async sortBy(sortBy, order = "asc") {
4137
+ const oldStored = typeof this.queueChanges?.tracksAdd === "function" ? this.utils.toJSON() : null;
4138
+ if (typeof sortBy === "function") {
4139
+ this.tracks.sort(sortBy);
4140
+ } else {
4141
+ this.tracks.sort((a, b) => {
4142
+ let comparison = 0;
4143
+ switch (sortBy) {
4144
+ case "duration":
4145
+ comparison = (a.info?.duration || 0) - (b.info?.duration || 0);
4146
+ break;
4147
+ case "title":
4148
+ comparison = (a.info?.title || "").localeCompare(b.info?.title || "");
4149
+ break;
4150
+ case "author":
4151
+ comparison = (a.info?.author || "").localeCompare(b.info?.author || "");
4152
+ break;
4153
+ default:
4154
+ return 0;
4155
+ }
4156
+ return order === "desc" ? -comparison : comparison;
4157
+ });
4158
+ }
4159
+ await this.utils.save();
4160
+ return this;
4161
+ }
4162
+ /**
4163
+ * Get a sorted copy of the queue tracks without modifying the original queue.
4164
+ * **This method DOES NOT MUTATE the queue** - it returns a new sorted array, similar to `Array.toSorted()`.
4165
+ * @param sortBy Property to sort by or custom comparator function
4166
+ * @param order Sort order: 'asc' or 'desc' (default: 'asc')
4167
+ * @returns A new sorted array of tracks (does not modify the queue)
4168
+ *
4169
+ * @example
4170
+ * ```ts
4171
+ * // Get sorted copy by duration (shortest first)
4172
+ * const sortedTracks = player.queue.toSortedBy("duration", "asc");
4173
+ * // Original queue remains unchanged
4174
+ *
4175
+ * // Get sorted copy by title alphabetically (Z-A)
4176
+ * const sortedByTitle = player.queue.toSortedBy("title", "desc");
4177
+ *
4178
+ * // Custom sorting
4179
+ * const customSorted = player.queue.toSortedBy((a, b) => {
4180
+ * return a.info.title.localeCompare(b.info.title);
4181
+ * });
4182
+ * ```
4183
+ */
4184
+ toSortedBy(sortBy, order = "asc") {
4185
+ const tracksCopy = [...this.tracks];
4186
+ if (typeof sortBy === "function") {
4187
+ return tracksCopy.sort(sortBy);
4188
+ }
4189
+ return tracksCopy.sort((a, b) => {
4190
+ let comparison = 0;
4191
+ switch (sortBy) {
4192
+ case "duration":
4193
+ comparison = (a.info?.duration || 0) - (b.info?.duration || 0);
4194
+ break;
4195
+ case "title":
4196
+ comparison = (a.info?.title || "").localeCompare(b.info?.title || "");
4197
+ break;
4198
+ case "author":
4199
+ comparison = (a.info?.author || "").localeCompare(b.info?.author || "");
4200
+ break;
4201
+ default:
4202
+ return 0;
4203
+ }
4204
+ return order === "desc" ? -comparison : comparison;
4205
+ });
4206
+ }
4207
+ /**
4208
+ * Get a range of tracks from the queue.
4209
+ * **This method DOES NOT MUTATE the queue** - it returns a new array slice, similar to `Array.slice()`.
4210
+ * @param start Start index (inclusive)
4211
+ * @param end End index (exclusive)
4212
+ * @returns Array of tracks in the specified range
4213
+ *
4214
+ * @example
4215
+ * ```ts
4216
+ * // Get tracks 5-15
4217
+ * const tracks = player.queue.getTracks(5, 15);
4218
+ *
4219
+ * // Get first 10 tracks
4220
+ * const firstTen = player.queue.getTracks(0, 10);
4221
+ * ```
4222
+ */
4223
+ getTracks(start, end) {
4224
+ return this.tracks.slice(start, end);
4225
+ }
3974
4226
  };
3975
4227
 
3976
4228
  // src/structures/Player.ts
@@ -5235,6 +5487,7 @@ var LavalinkManager = class extends import_events2.EventEmitter {
5235
5487
  });
5236
5488
  if (!autoReconnectOnlyWithTracks || autoReconnectOnlyWithTracks && (player.queue.current || player.queue.tracks.length)) {
5237
5489
  await player.connect();
5490
+ this.emit("playerReconnect", player, player.voiceChannelId);
5238
5491
  }
5239
5492
  if (player.queue.current) {
5240
5493
  return void await player.play({ position: previousPosition, paused: previousPaused, clientTrack: player.queue.current });
package/dist/index.mjs CHANGED
@@ -1372,7 +1372,6 @@ var LavalinkNode = class {
1372
1372
  functionLayer: "LavalinkNode > nodeEvent > stats > heartBeat()"
1373
1373
  });
1374
1374
  this.resetAckTimeouts(false, true);
1375
- if (this.pingTimeout) clearTimeout(this.pingTimeout);
1376
1375
  this.pingTimeout = setTimeout(() => {
1377
1376
  this.pingTimeout = null;
1378
1377
  if (!this.socket) {
@@ -1897,9 +1896,10 @@ var LavalinkNode = class {
1897
1896
  }, this.options.retryDelay || 1e3);
1898
1897
  }
1899
1898
  get reconnectionAttemptCount() {
1899
+ if (!Array.isArray(this.reconnectAttempts)) this.reconnectAttempts = [];
1900
1900
  const maxAllowedTimestan = this.options.retryTimespan || -1;
1901
1901
  if (maxAllowedTimestan <= 0) return this.reconnectAttempts.length;
1902
- return this.reconnectAttempts.filter((timestamp) => Date.now() - timestamp <= maxAllowedTimestan).length;
1902
+ return this.reconnectAttempts?.filter((timestamp) => Date.now() - timestamp <= maxAllowedTimestan).length || 0;
1903
1903
  }
1904
1904
  /**
1905
1905
  * Private Utility function to execute the reconnection
@@ -1912,6 +1912,7 @@ var LavalinkNode = class {
1912
1912
  this.destroy("NodeReconnectFail" /* NodeReconnectFail */);
1913
1913
  return;
1914
1914
  }
1915
+ if (!Array.isArray(this.reconnectAttempts)) this.reconnectAttempts = [];
1915
1916
  this.reconnectAttempts.push(Date.now());
1916
1917
  this.reconnectionState = "RECONNECTING" /* RECONNECTING */;
1917
1918
  this.NodeManager.emit("reconnecting", this);
@@ -2096,6 +2097,23 @@ var LavalinkNode = class {
2096
2097
  if (!payload?.guildId) return;
2097
2098
  const player = this._LManager.getPlayer(payload.guildId);
2098
2099
  if (!player) return;
2100
+ const NodeLinkEventType = payload.type;
2101
+ const NodeLinkExclusiveEvents = [
2102
+ "PlayerCreatedEvent",
2103
+ "PlayerDestroyedEvent",
2104
+ "PlayerConnectedEvent",
2105
+ "PlayerReconnectingEvent",
2106
+ "VolumeChangedEvent",
2107
+ "FiltersChangedEvent",
2108
+ "SeekEvent",
2109
+ "PauseEvent",
2110
+ "ConnectionStatusEvent",
2111
+ "MixStartedEvent",
2112
+ "MixEndedEvent"
2113
+ ];
2114
+ if (NodeLinkExclusiveEvents.includes(NodeLinkEventType) && (!this.info || this.info.isNodelink)) {
2115
+ return this.nodeLinkEventHandler(NodeLinkEventType, player, player.queue.current, payload);
2116
+ }
2099
2117
  switch (payload.type) {
2100
2118
  case "TrackStartEvent":
2101
2119
  this.trackStart(player, player.queue.current, payload);
@@ -2139,6 +2157,16 @@ var LavalinkNode = class {
2139
2157
  }
2140
2158
  return;
2141
2159
  }
2160
+ /**
2161
+ * nodeLink specific events handling https://nodelink.js.org/docs/api/websocket#incoming-events-server--client
2162
+ * @param eventName
2163
+ * @param player
2164
+ * @param track
2165
+ * @param payload
2166
+ */
2167
+ async nodeLinkEventHandler(eventName, player, track, payload) {
2168
+ this.NodeManager.emit("nodeLinkEvent", this, eventName, player, track, payload);
2169
+ }
2142
2170
  getTrackOfPayload(payload) {
2143
2171
  return "track" in payload ? this._LManager.utils.buildTrack(payload.track, void 0) : null;
2144
2172
  }
@@ -3729,6 +3757,87 @@ var Queue = class {
3729
3757
  */
3730
3758
  totalDuration: () => {
3731
3759
  return this.tracks.reduce((acc, cur) => acc + (cur.info.duration || 0), this.current?.info.duration || 0);
3760
+ },
3761
+ /**
3762
+ * Find tracks in the queue matching specific criteria.
3763
+ * **This method DOES NOT MUTATE the queue** - it returns a new array without modifying the original queue.
3764
+ * @param predicate Function to test each track, or an object with criteria to match
3765
+ * @returns Array of matching tracks with their indexes
3766
+ *
3767
+ * @example
3768
+ * ```ts
3769
+ * // Find by author
3770
+ * const artistTracks = player.queue.utils.filterTracks({ author: "Artist Name" });
3771
+ *
3772
+ * // Find by duration range (5-10 minutes)
3773
+ * const longTracks = player.queue.utils.filterTracks({ duration: { min: 300000, max: 600000 } });
3774
+ *
3775
+ * // Find by title (partial match)
3776
+ * const titleMatches = player.queue.utils.filterTracks({ title: "Never Gonna" });
3777
+ *
3778
+ * // Custom predicate
3779
+ * const customFilter = player.queue.utils.filterTracks(track => track.info.isStream);
3780
+ * ```
3781
+ */
3782
+ filterTracks: (predicate) => {
3783
+ if (typeof predicate === "function") {
3784
+ return this.tracks.map((track, index) => ({ track, index })).filter(({ track, index }) => predicate(track, index));
3785
+ }
3786
+ return this.tracks.map((track, index) => ({ track, index })).filter(({ track }) => {
3787
+ if (predicate.title && !track.info?.title?.toLowerCase().includes(predicate.title.toLowerCase())) {
3788
+ return false;
3789
+ }
3790
+ if (predicate.author && !track.info?.author?.toLowerCase().includes(predicate.author.toLowerCase())) {
3791
+ return false;
3792
+ }
3793
+ if (predicate.duration !== void 0) {
3794
+ const duration = track.info?.duration || 0;
3795
+ if (typeof predicate.duration === "number") {
3796
+ if (duration !== predicate.duration) return false;
3797
+ } else {
3798
+ if (predicate.duration.min !== void 0 && duration < predicate.duration.min) return false;
3799
+ if (predicate.duration.max !== void 0 && duration > predicate.duration.max) return false;
3800
+ }
3801
+ }
3802
+ if (predicate.uri && track.info?.uri !== predicate.uri) {
3803
+ return false;
3804
+ }
3805
+ if (predicate.identifier && track.info?.identifier !== predicate.identifier) {
3806
+ return false;
3807
+ }
3808
+ if (predicate.sourceName && track.info?.sourceName?.toLowerCase() !== predicate.sourceName.toLowerCase()) {
3809
+ return false;
3810
+ }
3811
+ if (predicate.isStream !== void 0 && track.info?.isStream !== predicate.isStream) {
3812
+ return false;
3813
+ }
3814
+ if (predicate.isSeekable !== void 0 && track.info?.isSeekable !== predicate.isSeekable) {
3815
+ return false;
3816
+ }
3817
+ return true;
3818
+ });
3819
+ },
3820
+ /**
3821
+ * Find a single track in the queue matching specific criteria.
3822
+ * **This method DOES NOT MUTATE the queue** - it searches without modifying the original queue.
3823
+ * @param predicate Function to test each track, or an object with criteria to match
3824
+ * @returns First matching track with its index, or null if not found
3825
+ *
3826
+ * @example
3827
+ * ```ts
3828
+ * // Find first track by author
3829
+ * const track = player.queue.utils.findTrack({ author: "Artist Name" });
3830
+ * if (track) {
3831
+ * console.log(`Found at index ${track.index}: ${track.track.info.title}`);
3832
+ * }
3833
+ *
3834
+ * // Find with custom predicate
3835
+ * const liveStream = player.queue.utils.findTrack(track => track.info.isStream);
3836
+ * ```
3837
+ */
3838
+ findTrack: (predicate) => {
3839
+ const results = this.utils.filterTracks(predicate);
3840
+ return results.length > 0 ? results[0] : null;
3732
3841
  }
3733
3842
  };
3734
3843
  /**
@@ -3910,6 +4019,149 @@ var Queue = class {
3910
4019
  if (removed) await this.utils.save();
3911
4020
  return removed ?? null;
3912
4021
  }
4022
+ /**
4023
+ * Find tracks in the queue matching specific criteria.
4024
+ * **This method DOES NOT MUTATE the queue** - it returns a new array without modifying the original queue.
4025
+ * @deprecated Use `player.queue.utils.filterTracks()` instead.
4026
+ * @param predicate Function to test each track, or an object with criteria to match
4027
+ * @returns Array of matching tracks with their indexes
4028
+ *
4029
+ * @example
4030
+ * ```ts
4031
+ * // Use the new method instead:
4032
+ * const artistTracks = player.queue.utils.filterTracks({ author: "Artist Name" });
4033
+ * ```
4034
+ */
4035
+ filter(predicate) {
4036
+ return this.utils.filterTracks(predicate);
4037
+ }
4038
+ /**
4039
+ * Find a single track in the queue matching specific criteria.
4040
+ * **This method DOES NOT MUTATE the queue** - it searches without modifying the original queue.
4041
+ * @deprecated Use `player.queue.utils.findTrack()` instead.
4042
+ * @param predicate Function to test each track, or an object with criteria to match
4043
+ * @returns First matching track with its index, or null if not found
4044
+ *
4045
+ * @example
4046
+ * ```ts
4047
+ * // Use the new method instead:
4048
+ * const track = player.queue.utils.findTrack({ author: "Artist Name" });
4049
+ * ```
4050
+ */
4051
+ find(predicate) {
4052
+ return this.utils.findTrack(predicate);
4053
+ }
4054
+ /**
4055
+ * Sort the queue tracks by a specific property.
4056
+ * **⚠️ This method MUTATES the queue** - it modifies the original queue in place.
4057
+ * @param sortBy Property to sort by or custom comparator function
4058
+ * @param order Sort order: 'asc' or 'desc' (default: 'asc')
4059
+ * @returns The queue instance for chaining
4060
+ *
4061
+ * @example
4062
+ * ```ts
4063
+ * // Sort by duration (shortest first)
4064
+ * await player.queue.sortBy("duration", "asc");
4065
+ *
4066
+ * // Sort by title alphabetically (Z-A)
4067
+ * await player.queue.sortBy("title", "desc");
4068
+ *
4069
+ * // Custom sorting
4070
+ * await player.queue.sortBy((a, b) => {
4071
+ * return a.info.title.localeCompare(b.info.title);
4072
+ * });
4073
+ * ```
4074
+ */
4075
+ async sortBy(sortBy, order = "asc") {
4076
+ const oldStored = typeof this.queueChanges?.tracksAdd === "function" ? this.utils.toJSON() : null;
4077
+ if (typeof sortBy === "function") {
4078
+ this.tracks.sort(sortBy);
4079
+ } else {
4080
+ this.tracks.sort((a, b) => {
4081
+ let comparison = 0;
4082
+ switch (sortBy) {
4083
+ case "duration":
4084
+ comparison = (a.info?.duration || 0) - (b.info?.duration || 0);
4085
+ break;
4086
+ case "title":
4087
+ comparison = (a.info?.title || "").localeCompare(b.info?.title || "");
4088
+ break;
4089
+ case "author":
4090
+ comparison = (a.info?.author || "").localeCompare(b.info?.author || "");
4091
+ break;
4092
+ default:
4093
+ return 0;
4094
+ }
4095
+ return order === "desc" ? -comparison : comparison;
4096
+ });
4097
+ }
4098
+ await this.utils.save();
4099
+ return this;
4100
+ }
4101
+ /**
4102
+ * Get a sorted copy of the queue tracks without modifying the original queue.
4103
+ * **This method DOES NOT MUTATE the queue** - it returns a new sorted array, similar to `Array.toSorted()`.
4104
+ * @param sortBy Property to sort by or custom comparator function
4105
+ * @param order Sort order: 'asc' or 'desc' (default: 'asc')
4106
+ * @returns A new sorted array of tracks (does not modify the queue)
4107
+ *
4108
+ * @example
4109
+ * ```ts
4110
+ * // Get sorted copy by duration (shortest first)
4111
+ * const sortedTracks = player.queue.toSortedBy("duration", "asc");
4112
+ * // Original queue remains unchanged
4113
+ *
4114
+ * // Get sorted copy by title alphabetically (Z-A)
4115
+ * const sortedByTitle = player.queue.toSortedBy("title", "desc");
4116
+ *
4117
+ * // Custom sorting
4118
+ * const customSorted = player.queue.toSortedBy((a, b) => {
4119
+ * return a.info.title.localeCompare(b.info.title);
4120
+ * });
4121
+ * ```
4122
+ */
4123
+ toSortedBy(sortBy, order = "asc") {
4124
+ const tracksCopy = [...this.tracks];
4125
+ if (typeof sortBy === "function") {
4126
+ return tracksCopy.sort(sortBy);
4127
+ }
4128
+ return tracksCopy.sort((a, b) => {
4129
+ let comparison = 0;
4130
+ switch (sortBy) {
4131
+ case "duration":
4132
+ comparison = (a.info?.duration || 0) - (b.info?.duration || 0);
4133
+ break;
4134
+ case "title":
4135
+ comparison = (a.info?.title || "").localeCompare(b.info?.title || "");
4136
+ break;
4137
+ case "author":
4138
+ comparison = (a.info?.author || "").localeCompare(b.info?.author || "");
4139
+ break;
4140
+ default:
4141
+ return 0;
4142
+ }
4143
+ return order === "desc" ? -comparison : comparison;
4144
+ });
4145
+ }
4146
+ /**
4147
+ * Get a range of tracks from the queue.
4148
+ * **This method DOES NOT MUTATE the queue** - it returns a new array slice, similar to `Array.slice()`.
4149
+ * @param start Start index (inclusive)
4150
+ * @param end End index (exclusive)
4151
+ * @returns Array of tracks in the specified range
4152
+ *
4153
+ * @example
4154
+ * ```ts
4155
+ * // Get tracks 5-15
4156
+ * const tracks = player.queue.getTracks(5, 15);
4157
+ *
4158
+ * // Get first 10 tracks
4159
+ * const firstTen = player.queue.getTracks(0, 10);
4160
+ * ```
4161
+ */
4162
+ getTracks(start, end) {
4163
+ return this.tracks.slice(start, end);
4164
+ }
3913
4165
  };
3914
4166
 
3915
4167
  // src/structures/Player.ts
@@ -5174,6 +5426,7 @@ var LavalinkManager = class extends EventEmitter2 {
5174
5426
  });
5175
5427
  if (!autoReconnectOnlyWithTracks || autoReconnectOnlyWithTracks && (player.queue.current || player.queue.tracks.length)) {
5176
5428
  await player.connect();
5429
+ this.emit("playerReconnect", player, player.voiceChannelId);
5177
5430
  }
5178
5431
  if (player.queue.current) {
5179
5432
  return void await player.play({ position: previousPosition, paused: previousPaused, clientTrack: player.queue.current });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lavalink-client",
3
- "version": "2.7.6",
3
+ "version": "2.7.8",
4
4
  "description": "Easy, flexible and feature-rich lavalink@v4 Client. Both for Beginners and Proficients.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",