lavalink-client 2.7.7 → 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 {
@@ -3031,6 +3222,13 @@ interface LavalinkManagerEvents<CustomPlayerT extends Player = Player> {
3031
3222
  * @event Manager#playerDisconnect
3032
3223
  */
3033
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;
3034
3232
  /**
3035
3233
  * Emitted when a Node-Socket got closed for a specific Player.
3036
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 {
@@ -3031,6 +3222,13 @@ interface LavalinkManagerEvents<CustomPlayerT extends Player = Player> {
3031
3222
  * @event Manager#playerDisconnect
3032
3223
  */
3033
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;
3034
3232
  /**
3035
3233
  * Emitted when a Node-Socket got closed for a specific Player.
3036
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);
@@ -3817,6 +3818,87 @@ var Queue = class {
3817
3818
  */
3818
3819
  totalDuration: () => {
3819
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;
3820
3902
  }
3821
3903
  };
3822
3904
  /**
@@ -3998,6 +4080,149 @@ var Queue = class {
3998
4080
  if (removed) await this.utils.save();
3999
4081
  return removed ?? null;
4000
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
+ }
4001
4226
  };
4002
4227
 
4003
4228
  // src/structures/Player.ts
@@ -5262,6 +5487,7 @@ var LavalinkManager = class extends import_events2.EventEmitter {
5262
5487
  });
5263
5488
  if (!autoReconnectOnlyWithTracks || autoReconnectOnlyWithTracks && (player.queue.current || player.queue.tracks.length)) {
5264
5489
  await player.connect();
5490
+ this.emit("playerReconnect", player, player.voiceChannelId);
5265
5491
  }
5266
5492
  if (player.queue.current) {
5267
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);
@@ -3756,6 +3757,87 @@ var Queue = class {
3756
3757
  */
3757
3758
  totalDuration: () => {
3758
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;
3759
3841
  }
3760
3842
  };
3761
3843
  /**
@@ -3937,6 +4019,149 @@ var Queue = class {
3937
4019
  if (removed) await this.utils.save();
3938
4020
  return removed ?? null;
3939
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
+ }
3940
4165
  };
3941
4166
 
3942
4167
  // src/structures/Player.ts
@@ -5201,6 +5426,7 @@ var LavalinkManager = class extends EventEmitter2 {
5201
5426
  });
5202
5427
  if (!autoReconnectOnlyWithTracks || autoReconnectOnlyWithTracks && (player.queue.current || player.queue.tracks.length)) {
5203
5428
  await player.connect();
5429
+ this.emit("playerReconnect", player, player.voiceChannelId);
5204
5430
  }
5205
5431
  if (player.queue.current) {
5206
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.7",
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",