lavalink-client 2.6.7 → 2.7.0

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
@@ -108,13 +108,15 @@ pnpm add tomato6966/lavalink-client
108
108
  ## 💖 Used In
109
109
  This client powers various Discord bots:
110
110
  - **[Mivator](https://discord.gg/5dUb7M2qCj)** (Public Bot by @Tomato6966)
111
- - **[Betty](https://betty.cx/)** (Public Bot by fb_sean)
111
+ - **[Betty](https://betty.bot/?utm_source=lavalink-client)** (Public Bot by @fb_sean)
112
+ - **[Nero](https://betty.bot/?utm_source=lavalink-client)** (Public Bot by @fb_sean)
112
113
  - **Bots by Contributors:**
113
114
  - [Mintone](https://mintone.tech/) (@appujet)
114
115
  - [Stelle](https://github.com/Ganyu-Studios/stelle-music) (@EvilG-MC)
115
116
  - [Panais](https://panais.xyz/) (@LucasB25)
116
117
  - [Akyn](https://akynbot.vercel.app/) (@notdeltaxd)
117
118
  - [ARINO](https://site.arinoapp.qzz.io/) (@ryanwtf88)
119
+ - [iHorizon](https://github.com/ihrz/ihrz) (@iHorizon)
118
120
  - **Bots Community (Users):**
119
121
  - [Soundy](https://github.com/idMJA/Soundy) (@idMJA)
120
122
  - [BeatBot ](https://getbeatbot.vercel.app/) (@zenitsujs)
@@ -461,3 +463,4 @@ if (response.tracks.length > 0) {
461
463
  </div>
462
464
 
463
465
 
466
+
package/dist/index.d.mts CHANGED
@@ -210,6 +210,7 @@ declare enum DebugEvents {
210
210
  PlayerUpdateSuccess = "PlayerUpdateSuccess",
211
211
  HeartBeatTriggered = "HeartBeatTriggered",
212
212
  NoSocketOnDestroy = "NoSocketOnDestroy",
213
+ SocketCleanupError = "SocketCleanupError",
213
214
  SocketTerminateHeartBeatTimeout = "SocketTerminateHeartBeatTimeout",
214
215
  TryingConnectWhileConnected = "TryingConnectWhileConnected",
215
216
  LavaSearchNothingFound = "LavaSearchNothingFound",
@@ -704,138 +705,6 @@ declare class FilterManager {
704
705
  clearEQ(): Promise<this>;
705
706
  }
706
707
 
707
- /** Sourcenames provided by lavalink server */
708
- type LavalinkSourceNames = "youtube" | "youtubemusic" | "soundcloud" | "bandcamp" | "twitch";
709
- /** Source Names provided by lava src plugin */
710
- type LavalinkPlugin_LavaSrc_SourceNames = "deezer" | "spotify" | "applemusic" | "yandexmusic" | "flowery-tts" | "vkmusic" | "tidal" | "qobuz" | "pandora";
711
- /** Source Names provided by jiosaavan plugin */
712
- type LavalinkPlugin_JioSaavn_SourceNames = "jiosaavn";
713
- /** The SourceNames provided by lavalink */
714
- type SourceNames = LavalinkSourceNames | LavalinkPlugin_LavaSrc_SourceNames | LavalinkPlugin_JioSaavn_SourceNames;
715
- interface LavalinkTrackInfo {
716
- /** The Identifier of the Track */
717
- identifier: string;
718
- /** The Track Title / Name */
719
- title: string;
720
- /** The Name of the Author */
721
- author: string;
722
- /** The duration of the Track */
723
- length: number;
724
- /** The URL of the artwork if available */
725
- artworkUrl: string | null;
726
- /** The URL (aka Link) of the Track called URI */
727
- uri: string;
728
- /** The Source name of the Track, e.g. soundcloud, youtube, spotify */
729
- sourceName: SourceNames;
730
- /** Wether the audio is seekable */
731
- isSeekable: boolean;
732
- /** Wether the audio is of a live stream */
733
- isStream: boolean;
734
- /** If isrc code is available, it's provided */
735
- isrc: string | null;
736
- }
737
- interface TrackInfo {
738
- /** The Identifier of the Track */
739
- identifier: string;
740
- /** The Track Title / Name */
741
- title: string;
742
- /** The Name of the Author */
743
- author: string;
744
- /** The duration of the Track */
745
- duration: number;
746
- /** The URL of the artwork if available */
747
- artworkUrl: string | null;
748
- /** The URL (aka Link) of the Track called URI */
749
- uri: string;
750
- /** The Source name of the Track, e.g. soundcloud, youtube, spotify */
751
- sourceName: SourceNames;
752
- /** Wether the audio is seekable */
753
- isSeekable: boolean;
754
- /** Wether the audio is of a live stream */
755
- isStream: boolean;
756
- /** If isrc code is available, it's provided */
757
- isrc: string | null;
758
- }
759
- interface PluginInfo {
760
- /** The Type provided by a plugin */
761
- type?: "album" | "playlist" | "artist" | "recommendations" | string;
762
- /** The Identifier provided by a plugin */
763
- albumName?: string;
764
- /** The url of the album */
765
- albumUrl?: string;
766
- /** The url of the album art */
767
- albumArtUrl?: string;
768
- /** The url of the artist */
769
- artistUrl?: string;
770
- /** The url of the artist artwork */
771
- artistArtworkUrl?: string;
772
- /** The url of the preview */
773
- previewUrl?: string;
774
- /** Whether the track is a preview */
775
- isPreview?: boolean;
776
- /** The total number of tracks in the playlist */
777
- totalTracks?: number;
778
- /** The Identifier provided by a plugin */
779
- identifier?: string;
780
- /** The ArtworkUrl provided by a plugin */
781
- artworkUrl?: string;
782
- /** The Author Information provided by a plugin */
783
- author?: string;
784
- /** The Url provided by a Plugin */
785
- url?: string;
786
- /** The Url provided by a Plugin */
787
- uri?: string;
788
- /** You can put specific track information here, to transform the tracks... */
789
- clientData?: {
790
- previousTrack?: boolean;
791
- [key: string]: any;
792
- };
793
- }
794
- interface LavalinkTrack {
795
- /** The Base 64 encoded String */
796
- encoded?: Base64;
797
- /** Track Information */
798
- info: LavalinkTrackInfo;
799
- /** Plugin Information from Lavalink */
800
- pluginInfo: Partial<PluginInfo>;
801
- /** The userData Object from when you provide to the lavalink request */
802
- userData?: anyObject;
803
- }
804
- interface Track {
805
- /** The Base 64 encoded String */
806
- encoded?: Base64;
807
- /** Track Information */
808
- info: TrackInfo;
809
- /** Plugin Information from Lavalink */
810
- pluginInfo: Partial<PluginInfo>;
811
- /** The Track's Requester */
812
- requester?: unknown;
813
- /** The userData Object from when you provide to the lavalink request */
814
- userData?: anyObject;
815
- }
816
- interface UnresolvedTrackInfo extends Partial<TrackInfo> {
817
- /** Required */
818
- title: string;
819
- }
820
- interface UnresolvedQuery extends UnresolvedTrackInfo {
821
- /** The base64 of the unresolved track to "encode" */
822
- encoded?: Base64;
823
- }
824
- interface UnresolvedTrack {
825
- /** Required */
826
- resolve: (player: Player) => Promise<void>;
827
- /** The Base 64 encoded String */
828
- encoded?: Base64;
829
- /** Track Information */
830
- info: UnresolvedTrackInfo;
831
- /** Plugin Information from Lavalink */
832
- pluginInfo: Partial<PluginInfo>;
833
- /** The userData Object from when you provide to the lavalink request */
834
- userData?: anyObject;
835
- /** The Track's Requester */
836
- requester?: unknown;
837
- }
838
-
839
708
  declare class QueueSaver {
840
709
  /**
841
710
  * The queue store manager
@@ -977,6 +846,7 @@ declare class Queue {
977
846
  * - single Track | UnresolvedTrack
978
847
  * - multiple Track | UnresovedTrack
979
848
  * - at the index or multiple indexes
849
+ * - Since v2.7 the removed tracks get unshifted into the previous queue state instead of pushed (indexed at the start instead of end - as it should)
980
850
  * @param removeQueryTrack
981
851
  * @returns null (if nothing was removed) / { removed } where removed is an array with all removed elements
982
852
  *
@@ -1249,6 +1119,138 @@ declare class Player {
1249
1119
  toJSON(): PlayerJson;
1250
1120
  }
1251
1121
 
1122
+ /** Sourcenames provided by lavalink server */
1123
+ type LavalinkSourceNames = "youtube" | "youtubemusic" | "soundcloud" | "bandcamp" | "twitch";
1124
+ /** Source Names provided by lava src plugin */
1125
+ type LavalinkPlugin_LavaSrc_SourceNames = "deezer" | "spotify" | "applemusic" | "yandexmusic" | "flowery-tts" | "vkmusic" | "tidal" | "qobuz" | "pandora";
1126
+ /** Source Names provided by jiosaavan plugin */
1127
+ type LavalinkPlugin_JioSaavn_SourceNames = "jiosaavn";
1128
+ /** The SourceNames provided by lavalink */
1129
+ type SourceNames = LavalinkSourceNames | LavalinkPlugin_LavaSrc_SourceNames | LavalinkPlugin_JioSaavn_SourceNames;
1130
+ interface LavalinkTrackInfo {
1131
+ /** The Identifier of the Track */
1132
+ identifier: string;
1133
+ /** The Track Title / Name */
1134
+ title: string;
1135
+ /** The Name of the Author */
1136
+ author: string;
1137
+ /** The duration of the Track */
1138
+ length: number;
1139
+ /** The URL of the artwork if available */
1140
+ artworkUrl: string | null;
1141
+ /** The URL (aka Link) of the Track called URI */
1142
+ uri: string;
1143
+ /** The Source name of the Track, e.g. soundcloud, youtube, spotify */
1144
+ sourceName: SourceNames;
1145
+ /** Wether the audio is seekable */
1146
+ isSeekable: boolean;
1147
+ /** Wether the audio is of a live stream */
1148
+ isStream: boolean;
1149
+ /** If isrc code is available, it's provided */
1150
+ isrc: string | null;
1151
+ }
1152
+ interface TrackInfo {
1153
+ /** The Identifier of the Track */
1154
+ identifier: string;
1155
+ /** The Track Title / Name */
1156
+ title: string;
1157
+ /** The Name of the Author */
1158
+ author: string;
1159
+ /** The duration of the Track */
1160
+ duration: number;
1161
+ /** The URL of the artwork if available */
1162
+ artworkUrl: string | null;
1163
+ /** The URL (aka Link) of the Track called URI */
1164
+ uri: string;
1165
+ /** The Source name of the Track, e.g. soundcloud, youtube, spotify */
1166
+ sourceName: SourceNames;
1167
+ /** Wether the audio is seekable */
1168
+ isSeekable: boolean;
1169
+ /** Wether the audio is of a live stream */
1170
+ isStream: boolean;
1171
+ /** If isrc code is available, it's provided */
1172
+ isrc: string | null;
1173
+ }
1174
+ interface PluginInfo {
1175
+ /** The Type provided by a plugin */
1176
+ type?: "album" | "playlist" | "artist" | "recommendations" | string;
1177
+ /** The Identifier provided by a plugin */
1178
+ albumName?: string;
1179
+ /** The url of the album */
1180
+ albumUrl?: string;
1181
+ /** The url of the album art */
1182
+ albumArtUrl?: string;
1183
+ /** The url of the artist */
1184
+ artistUrl?: string;
1185
+ /** The url of the artist artwork */
1186
+ artistArtworkUrl?: string;
1187
+ /** The url of the preview */
1188
+ previewUrl?: string;
1189
+ /** Whether the track is a preview */
1190
+ isPreview?: boolean;
1191
+ /** The total number of tracks in the playlist */
1192
+ totalTracks?: number;
1193
+ /** The Identifier provided by a plugin */
1194
+ identifier?: string;
1195
+ /** The ArtworkUrl provided by a plugin */
1196
+ artworkUrl?: string;
1197
+ /** The Author Information provided by a plugin */
1198
+ author?: string;
1199
+ /** The Url provided by a Plugin */
1200
+ url?: string;
1201
+ /** The Url provided by a Plugin */
1202
+ uri?: string;
1203
+ /** You can put specific track information here, to transform the tracks... */
1204
+ clientData?: {
1205
+ previousTrack?: boolean;
1206
+ [key: string]: any;
1207
+ };
1208
+ }
1209
+ interface LavalinkTrack {
1210
+ /** The Base 64 encoded String */
1211
+ encoded?: Base64;
1212
+ /** Track Information */
1213
+ info: LavalinkTrackInfo;
1214
+ /** Plugin Information from Lavalink */
1215
+ pluginInfo: Partial<PluginInfo>;
1216
+ /** The userData Object from when you provide to the lavalink request */
1217
+ userData?: anyObject;
1218
+ }
1219
+ interface Track {
1220
+ /** The Base 64 encoded String */
1221
+ encoded?: Base64;
1222
+ /** Track Information */
1223
+ info: TrackInfo;
1224
+ /** Plugin Information from Lavalink */
1225
+ pluginInfo: Partial<PluginInfo>;
1226
+ /** The Track's Requester */
1227
+ requester?: unknown;
1228
+ /** The userData Object from when you provide to the lavalink request */
1229
+ userData?: anyObject;
1230
+ }
1231
+ interface UnresolvedTrackInfo extends Partial<TrackInfo> {
1232
+ /** Required */
1233
+ title: string;
1234
+ }
1235
+ interface UnresolvedQuery extends UnresolvedTrackInfo {
1236
+ /** The base64 of the unresolved track to "encode" */
1237
+ encoded?: Base64;
1238
+ }
1239
+ interface UnresolvedTrack {
1240
+ /** Required */
1241
+ resolve: (player: Player) => Promise<void>;
1242
+ /** The Base 64 encoded String */
1243
+ encoded?: Base64;
1244
+ /** Track Information */
1245
+ info: UnresolvedTrackInfo;
1246
+ /** Plugin Information from Lavalink */
1247
+ pluginInfo: Partial<PluginInfo>;
1248
+ /** The userData Object from when you provide to the lavalink request */
1249
+ userData?: anyObject;
1250
+ /** The Track's Requester */
1251
+ requester?: unknown;
1252
+ }
1253
+
1252
1254
  interface StoredQueue {
1253
1255
  current: Track | null;
1254
1256
  previous: Track[];
@@ -1266,7 +1268,7 @@ interface QueueStoreManager {
1266
1268
  /** @async Parse the saved value back to the Queue (IF YOU DON'T NEED PARSING/STRINGIFY, then just return the value) */
1267
1269
  parse: (value: StoredQueue | string) => Awaitable<Partial<StoredQueue>>;
1268
1270
  }
1269
- interface ManagerQueueOptions<CustomPlayerT extends Player = Player> {
1271
+ interface ManagerQueueOptions {
1270
1272
  /** Maximum Amount of tracks for the queue.previous array. Set to 0 to not save previous songs. Defaults to 25 Tracks */
1271
1273
  maxPreviousTracks?: number;
1272
1274
  /** Custom Queue Store option */
@@ -1916,38 +1918,12 @@ interface LyricsFoundEvent extends PlayerEvent {
1916
1918
  /** The lyrics */
1917
1919
  lyrics: LyricsResult;
1918
1920
  }
1919
- interface LyricsFoundEvent extends PlayerEvent {
1920
- /** The lyricsfound event */
1921
- type: "LyricsFoundEvent";
1922
- /** The guildId */
1923
- guildId: string;
1924
- /** The lyrics */
1925
- lyrics: LyricsResult;
1926
- }
1927
1921
  interface LyricsNotFoundEvent extends PlayerEvent {
1928
1922
  /**The lyricsnotfound event*/
1929
1923
  type: "LyricsNotFoundEvent";
1930
1924
  /**The guildId*/
1931
1925
  guildId: string;
1932
1926
  }
1933
- interface LyricsNotFoundEvent extends PlayerEvent {
1934
- /**The lyricsnotfound event*/
1935
- type: "LyricsNotFoundEvent";
1936
- /**The guildId*/
1937
- guildId: string;
1938
- }
1939
- interface LyricsLineEvent extends PlayerEvent {
1940
- /**The lyricsline event*/
1941
- type: "LyricsLineEvent";
1942
- /** The guildId */
1943
- guildId: string;
1944
- /** The line number */
1945
- lineIndex: number;
1946
- /** The line */
1947
- line: LyricsLine;
1948
- /**skipped is true if the line was skipped */
1949
- skipped: boolean;
1950
- }
1951
1927
  interface LyricsLineEvent extends PlayerEvent {
1952
1928
  /**The lyricsline event*/
1953
1929
  type: "LyricsLineEvent";
@@ -3040,7 +3016,7 @@ interface ManagerOptions<CustomPlayerT extends Player = Player> {
3040
3016
  /** The Bot Client's Data for Authorization */
3041
3017
  client?: BotClientOptions;
3042
3018
  /** QueueOptions for all Queues */
3043
- queueOptions?: ManagerQueueOptions<CustomPlayerT>;
3019
+ queueOptions?: ManagerQueueOptions;
3044
3020
  /** PlayerOptions for all Players */
3045
3021
  playerOptions?: ManagerPlayerOptions<CustomPlayerT>;
3046
3022
  /** The player class you want to use when creating a player. (can be extendable) */
package/dist/index.d.ts CHANGED
@@ -210,6 +210,7 @@ declare enum DebugEvents {
210
210
  PlayerUpdateSuccess = "PlayerUpdateSuccess",
211
211
  HeartBeatTriggered = "HeartBeatTriggered",
212
212
  NoSocketOnDestroy = "NoSocketOnDestroy",
213
+ SocketCleanupError = "SocketCleanupError",
213
214
  SocketTerminateHeartBeatTimeout = "SocketTerminateHeartBeatTimeout",
214
215
  TryingConnectWhileConnected = "TryingConnectWhileConnected",
215
216
  LavaSearchNothingFound = "LavaSearchNothingFound",
@@ -704,138 +705,6 @@ declare class FilterManager {
704
705
  clearEQ(): Promise<this>;
705
706
  }
706
707
 
707
- /** Sourcenames provided by lavalink server */
708
- type LavalinkSourceNames = "youtube" | "youtubemusic" | "soundcloud" | "bandcamp" | "twitch";
709
- /** Source Names provided by lava src plugin */
710
- type LavalinkPlugin_LavaSrc_SourceNames = "deezer" | "spotify" | "applemusic" | "yandexmusic" | "flowery-tts" | "vkmusic" | "tidal" | "qobuz" | "pandora";
711
- /** Source Names provided by jiosaavan plugin */
712
- type LavalinkPlugin_JioSaavn_SourceNames = "jiosaavn";
713
- /** The SourceNames provided by lavalink */
714
- type SourceNames = LavalinkSourceNames | LavalinkPlugin_LavaSrc_SourceNames | LavalinkPlugin_JioSaavn_SourceNames;
715
- interface LavalinkTrackInfo {
716
- /** The Identifier of the Track */
717
- identifier: string;
718
- /** The Track Title / Name */
719
- title: string;
720
- /** The Name of the Author */
721
- author: string;
722
- /** The duration of the Track */
723
- length: number;
724
- /** The URL of the artwork if available */
725
- artworkUrl: string | null;
726
- /** The URL (aka Link) of the Track called URI */
727
- uri: string;
728
- /** The Source name of the Track, e.g. soundcloud, youtube, spotify */
729
- sourceName: SourceNames;
730
- /** Wether the audio is seekable */
731
- isSeekable: boolean;
732
- /** Wether the audio is of a live stream */
733
- isStream: boolean;
734
- /** If isrc code is available, it's provided */
735
- isrc: string | null;
736
- }
737
- interface TrackInfo {
738
- /** The Identifier of the Track */
739
- identifier: string;
740
- /** The Track Title / Name */
741
- title: string;
742
- /** The Name of the Author */
743
- author: string;
744
- /** The duration of the Track */
745
- duration: number;
746
- /** The URL of the artwork if available */
747
- artworkUrl: string | null;
748
- /** The URL (aka Link) of the Track called URI */
749
- uri: string;
750
- /** The Source name of the Track, e.g. soundcloud, youtube, spotify */
751
- sourceName: SourceNames;
752
- /** Wether the audio is seekable */
753
- isSeekable: boolean;
754
- /** Wether the audio is of a live stream */
755
- isStream: boolean;
756
- /** If isrc code is available, it's provided */
757
- isrc: string | null;
758
- }
759
- interface PluginInfo {
760
- /** The Type provided by a plugin */
761
- type?: "album" | "playlist" | "artist" | "recommendations" | string;
762
- /** The Identifier provided by a plugin */
763
- albumName?: string;
764
- /** The url of the album */
765
- albumUrl?: string;
766
- /** The url of the album art */
767
- albumArtUrl?: string;
768
- /** The url of the artist */
769
- artistUrl?: string;
770
- /** The url of the artist artwork */
771
- artistArtworkUrl?: string;
772
- /** The url of the preview */
773
- previewUrl?: string;
774
- /** Whether the track is a preview */
775
- isPreview?: boolean;
776
- /** The total number of tracks in the playlist */
777
- totalTracks?: number;
778
- /** The Identifier provided by a plugin */
779
- identifier?: string;
780
- /** The ArtworkUrl provided by a plugin */
781
- artworkUrl?: string;
782
- /** The Author Information provided by a plugin */
783
- author?: string;
784
- /** The Url provided by a Plugin */
785
- url?: string;
786
- /** The Url provided by a Plugin */
787
- uri?: string;
788
- /** You can put specific track information here, to transform the tracks... */
789
- clientData?: {
790
- previousTrack?: boolean;
791
- [key: string]: any;
792
- };
793
- }
794
- interface LavalinkTrack {
795
- /** The Base 64 encoded String */
796
- encoded?: Base64;
797
- /** Track Information */
798
- info: LavalinkTrackInfo;
799
- /** Plugin Information from Lavalink */
800
- pluginInfo: Partial<PluginInfo>;
801
- /** The userData Object from when you provide to the lavalink request */
802
- userData?: anyObject;
803
- }
804
- interface Track {
805
- /** The Base 64 encoded String */
806
- encoded?: Base64;
807
- /** Track Information */
808
- info: TrackInfo;
809
- /** Plugin Information from Lavalink */
810
- pluginInfo: Partial<PluginInfo>;
811
- /** The Track's Requester */
812
- requester?: unknown;
813
- /** The userData Object from when you provide to the lavalink request */
814
- userData?: anyObject;
815
- }
816
- interface UnresolvedTrackInfo extends Partial<TrackInfo> {
817
- /** Required */
818
- title: string;
819
- }
820
- interface UnresolvedQuery extends UnresolvedTrackInfo {
821
- /** The base64 of the unresolved track to "encode" */
822
- encoded?: Base64;
823
- }
824
- interface UnresolvedTrack {
825
- /** Required */
826
- resolve: (player: Player) => Promise<void>;
827
- /** The Base 64 encoded String */
828
- encoded?: Base64;
829
- /** Track Information */
830
- info: UnresolvedTrackInfo;
831
- /** Plugin Information from Lavalink */
832
- pluginInfo: Partial<PluginInfo>;
833
- /** The userData Object from when you provide to the lavalink request */
834
- userData?: anyObject;
835
- /** The Track's Requester */
836
- requester?: unknown;
837
- }
838
-
839
708
  declare class QueueSaver {
840
709
  /**
841
710
  * The queue store manager
@@ -977,6 +846,7 @@ declare class Queue {
977
846
  * - single Track | UnresolvedTrack
978
847
  * - multiple Track | UnresovedTrack
979
848
  * - at the index or multiple indexes
849
+ * - Since v2.7 the removed tracks get unshifted into the previous queue state instead of pushed (indexed at the start instead of end - as it should)
980
850
  * @param removeQueryTrack
981
851
  * @returns null (if nothing was removed) / { removed } where removed is an array with all removed elements
982
852
  *
@@ -1249,6 +1119,138 @@ declare class Player {
1249
1119
  toJSON(): PlayerJson;
1250
1120
  }
1251
1121
 
1122
+ /** Sourcenames provided by lavalink server */
1123
+ type LavalinkSourceNames = "youtube" | "youtubemusic" | "soundcloud" | "bandcamp" | "twitch";
1124
+ /** Source Names provided by lava src plugin */
1125
+ type LavalinkPlugin_LavaSrc_SourceNames = "deezer" | "spotify" | "applemusic" | "yandexmusic" | "flowery-tts" | "vkmusic" | "tidal" | "qobuz" | "pandora";
1126
+ /** Source Names provided by jiosaavan plugin */
1127
+ type LavalinkPlugin_JioSaavn_SourceNames = "jiosaavn";
1128
+ /** The SourceNames provided by lavalink */
1129
+ type SourceNames = LavalinkSourceNames | LavalinkPlugin_LavaSrc_SourceNames | LavalinkPlugin_JioSaavn_SourceNames;
1130
+ interface LavalinkTrackInfo {
1131
+ /** The Identifier of the Track */
1132
+ identifier: string;
1133
+ /** The Track Title / Name */
1134
+ title: string;
1135
+ /** The Name of the Author */
1136
+ author: string;
1137
+ /** The duration of the Track */
1138
+ length: number;
1139
+ /** The URL of the artwork if available */
1140
+ artworkUrl: string | null;
1141
+ /** The URL (aka Link) of the Track called URI */
1142
+ uri: string;
1143
+ /** The Source name of the Track, e.g. soundcloud, youtube, spotify */
1144
+ sourceName: SourceNames;
1145
+ /** Wether the audio is seekable */
1146
+ isSeekable: boolean;
1147
+ /** Wether the audio is of a live stream */
1148
+ isStream: boolean;
1149
+ /** If isrc code is available, it's provided */
1150
+ isrc: string | null;
1151
+ }
1152
+ interface TrackInfo {
1153
+ /** The Identifier of the Track */
1154
+ identifier: string;
1155
+ /** The Track Title / Name */
1156
+ title: string;
1157
+ /** The Name of the Author */
1158
+ author: string;
1159
+ /** The duration of the Track */
1160
+ duration: number;
1161
+ /** The URL of the artwork if available */
1162
+ artworkUrl: string | null;
1163
+ /** The URL (aka Link) of the Track called URI */
1164
+ uri: string;
1165
+ /** The Source name of the Track, e.g. soundcloud, youtube, spotify */
1166
+ sourceName: SourceNames;
1167
+ /** Wether the audio is seekable */
1168
+ isSeekable: boolean;
1169
+ /** Wether the audio is of a live stream */
1170
+ isStream: boolean;
1171
+ /** If isrc code is available, it's provided */
1172
+ isrc: string | null;
1173
+ }
1174
+ interface PluginInfo {
1175
+ /** The Type provided by a plugin */
1176
+ type?: "album" | "playlist" | "artist" | "recommendations" | string;
1177
+ /** The Identifier provided by a plugin */
1178
+ albumName?: string;
1179
+ /** The url of the album */
1180
+ albumUrl?: string;
1181
+ /** The url of the album art */
1182
+ albumArtUrl?: string;
1183
+ /** The url of the artist */
1184
+ artistUrl?: string;
1185
+ /** The url of the artist artwork */
1186
+ artistArtworkUrl?: string;
1187
+ /** The url of the preview */
1188
+ previewUrl?: string;
1189
+ /** Whether the track is a preview */
1190
+ isPreview?: boolean;
1191
+ /** The total number of tracks in the playlist */
1192
+ totalTracks?: number;
1193
+ /** The Identifier provided by a plugin */
1194
+ identifier?: string;
1195
+ /** The ArtworkUrl provided by a plugin */
1196
+ artworkUrl?: string;
1197
+ /** The Author Information provided by a plugin */
1198
+ author?: string;
1199
+ /** The Url provided by a Plugin */
1200
+ url?: string;
1201
+ /** The Url provided by a Plugin */
1202
+ uri?: string;
1203
+ /** You can put specific track information here, to transform the tracks... */
1204
+ clientData?: {
1205
+ previousTrack?: boolean;
1206
+ [key: string]: any;
1207
+ };
1208
+ }
1209
+ interface LavalinkTrack {
1210
+ /** The Base 64 encoded String */
1211
+ encoded?: Base64;
1212
+ /** Track Information */
1213
+ info: LavalinkTrackInfo;
1214
+ /** Plugin Information from Lavalink */
1215
+ pluginInfo: Partial<PluginInfo>;
1216
+ /** The userData Object from when you provide to the lavalink request */
1217
+ userData?: anyObject;
1218
+ }
1219
+ interface Track {
1220
+ /** The Base 64 encoded String */
1221
+ encoded?: Base64;
1222
+ /** Track Information */
1223
+ info: TrackInfo;
1224
+ /** Plugin Information from Lavalink */
1225
+ pluginInfo: Partial<PluginInfo>;
1226
+ /** The Track's Requester */
1227
+ requester?: unknown;
1228
+ /** The userData Object from when you provide to the lavalink request */
1229
+ userData?: anyObject;
1230
+ }
1231
+ interface UnresolvedTrackInfo extends Partial<TrackInfo> {
1232
+ /** Required */
1233
+ title: string;
1234
+ }
1235
+ interface UnresolvedQuery extends UnresolvedTrackInfo {
1236
+ /** The base64 of the unresolved track to "encode" */
1237
+ encoded?: Base64;
1238
+ }
1239
+ interface UnresolvedTrack {
1240
+ /** Required */
1241
+ resolve: (player: Player) => Promise<void>;
1242
+ /** The Base 64 encoded String */
1243
+ encoded?: Base64;
1244
+ /** Track Information */
1245
+ info: UnresolvedTrackInfo;
1246
+ /** Plugin Information from Lavalink */
1247
+ pluginInfo: Partial<PluginInfo>;
1248
+ /** The userData Object from when you provide to the lavalink request */
1249
+ userData?: anyObject;
1250
+ /** The Track's Requester */
1251
+ requester?: unknown;
1252
+ }
1253
+
1252
1254
  interface StoredQueue {
1253
1255
  current: Track | null;
1254
1256
  previous: Track[];
@@ -1266,7 +1268,7 @@ interface QueueStoreManager {
1266
1268
  /** @async Parse the saved value back to the Queue (IF YOU DON'T NEED PARSING/STRINGIFY, then just return the value) */
1267
1269
  parse: (value: StoredQueue | string) => Awaitable<Partial<StoredQueue>>;
1268
1270
  }
1269
- interface ManagerQueueOptions<CustomPlayerT extends Player = Player> {
1271
+ interface ManagerQueueOptions {
1270
1272
  /** Maximum Amount of tracks for the queue.previous array. Set to 0 to not save previous songs. Defaults to 25 Tracks */
1271
1273
  maxPreviousTracks?: number;
1272
1274
  /** Custom Queue Store option */
@@ -1916,38 +1918,12 @@ interface LyricsFoundEvent extends PlayerEvent {
1916
1918
  /** The lyrics */
1917
1919
  lyrics: LyricsResult;
1918
1920
  }
1919
- interface LyricsFoundEvent extends PlayerEvent {
1920
- /** The lyricsfound event */
1921
- type: "LyricsFoundEvent";
1922
- /** The guildId */
1923
- guildId: string;
1924
- /** The lyrics */
1925
- lyrics: LyricsResult;
1926
- }
1927
1921
  interface LyricsNotFoundEvent extends PlayerEvent {
1928
1922
  /**The lyricsnotfound event*/
1929
1923
  type: "LyricsNotFoundEvent";
1930
1924
  /**The guildId*/
1931
1925
  guildId: string;
1932
1926
  }
1933
- interface LyricsNotFoundEvent extends PlayerEvent {
1934
- /**The lyricsnotfound event*/
1935
- type: "LyricsNotFoundEvent";
1936
- /**The guildId*/
1937
- guildId: string;
1938
- }
1939
- interface LyricsLineEvent extends PlayerEvent {
1940
- /**The lyricsline event*/
1941
- type: "LyricsLineEvent";
1942
- /** The guildId */
1943
- guildId: string;
1944
- /** The line number */
1945
- lineIndex: number;
1946
- /** The line */
1947
- line: LyricsLine;
1948
- /**skipped is true if the line was skipped */
1949
- skipped: boolean;
1950
- }
1951
1927
  interface LyricsLineEvent extends PlayerEvent {
1952
1928
  /**The lyricsline event*/
1953
1929
  type: "LyricsLineEvent";
@@ -3040,7 +3016,7 @@ interface ManagerOptions<CustomPlayerT extends Player = Player> {
3040
3016
  /** The Bot Client's Data for Authorization */
3041
3017
  client?: BotClientOptions;
3042
3018
  /** QueueOptions for all Queues */
3043
- queueOptions?: ManagerQueueOptions<CustomPlayerT>;
3019
+ queueOptions?: ManagerQueueOptions;
3044
3020
  /** PlayerOptions for all Players */
3045
3021
  playerOptions?: ManagerPlayerOptions<CustomPlayerT>;
3046
3022
  /** The player class you want to use when creating a player. (can be extendable) */
package/dist/index.js CHANGED
@@ -79,6 +79,7 @@ var DebugEvents = /* @__PURE__ */ ((DebugEvents2) => {
79
79
  DebugEvents2["PlayerUpdateSuccess"] = "PlayerUpdateSuccess";
80
80
  DebugEvents2["HeartBeatTriggered"] = "HeartBeatTriggered";
81
81
  DebugEvents2["NoSocketOnDestroy"] = "NoSocketOnDestroy";
82
+ DebugEvents2["SocketCleanupError"] = "SocketCleanupError";
82
83
  DebugEvents2["SocketTerminateHeartBeatTimeout"] = "SocketTerminateHeartBeatTimeout";
83
84
  DebugEvents2["TryingConnectWhileConnected"] = "TryingConnectWhileConnected";
84
85
  DebugEvents2["LavaSearchNothingFound"] = "LavaSearchNothingFound";
@@ -481,7 +482,7 @@ var SourceLinksRegexes = {
481
482
  /** DEFAULT SUPPORTED BY LAVALINK */
482
483
  YoutubeRegex: /https?:\/\/?(?:www\.)?(?:(m|www)\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|shorts|playlist\?|watch\?v=|watch\?.+(?:&|&#38;);v=))([a-zA-Z0-9\-_]{11})?(?:(?:\?|&|&#38;)index=((?:\d){1,3}))?(?:(?:\?|&|&#38;)?list=([a-zA-Z\-_0-9]{34}))?(?:\S+)?/,
483
484
  YoutubeMusicRegex: /https?:\/\/?(?:www\.)?(?:(music|m|www)\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|shorts|playlist\?|watch\?v=|watch\?.+(?:&|&#38;);v=))([a-zA-Z0-9\-_]{11})?(?:(?:\?|&|&#38;)index=((?:\d){1,3}))?(?:(?:\?|&|&#38;)?list=([a-zA-Z\-_0-9]{34}))?(?:\S+)?/,
484
- SoundCloudRegex: /https:\/\/(?:on\.)?soundcloud\.com\//,
485
+ SoundCloudRegex: /https?:\/\/(?:on\.)?soundcloud\.com\//,
485
486
  SoundCloudMobileRegex: /https?:\/\/(soundcloud\.app\.goo\.gl)\/(\S+)/,
486
487
  bandcamp: /https?:\/\/?(?:www\.)?([\d|\w]+)\.bandcamp\.com\/(\S+)/,
487
488
  TwitchTv: /https?:\/\/?(?:www\.)?twitch\.tv\/\w+/,
@@ -1001,9 +1002,15 @@ async function getClosestTrack(data, player) {
1001
1002
  source: sourceName !== "twitch" && sourceName !== "flowery-tts" ? sourceName : player.LavalinkManager.options?.playerOptions?.defaultSearchPlatform
1002
1003
  }, data.requester).then((res) => {
1003
1004
  let trackToUse = null;
1004
- if (data.info.author && !trackToUse) trackToUse = res.tracks.find((track) => [data.info?.author || "", `${data.info?.author} - Topic`].some((name) => new RegExp(`^${escapeRegExp(name)}$`, "i").test(track.info?.author)) || new RegExp(`^${escapeRegExp(data.info?.title)}$`, "i").test(track.info?.title));
1005
- if (data.info.duration && !trackToUse) trackToUse = res.tracks.find((track) => track.info?.duration >= data.info?.duration - 1500 && track?.info.duration <= data.info?.duration + 1500);
1006
- if (data.info.isrc && !trackToUse) trackToUse = res.tracks.find((track) => track.info?.isrc === data.info?.isrc);
1005
+ if ((data.info?.title || data.info?.author) && !trackToUse) trackToUse = res.tracks.find(
1006
+ (track) => (
1007
+ // find via author name (i ... case insensitve)
1008
+ [data.info?.author || "", `${data.info?.author} - Topic`].some((name) => new RegExp(`^${escapeRegExp(name)}$`, "i").test(track.info?.author)) || // find via title (i ... case insensitve)
1009
+ new RegExp(`^${escapeRegExp(data.info?.title)}$`, "i").test(track.info?.title)
1010
+ )
1011
+ );
1012
+ if (data.info?.isrc && !trackToUse) trackToUse = res.tracks.find((track) => track.info?.isrc === data.info?.isrc);
1013
+ if (data.info?.duration && !trackToUse) trackToUse = res.tracks.find((track) => track.info?.duration >= data.info?.duration - 1500 && track?.info.duration <= data.info?.duration + 1500);
1007
1014
  return applyUnresolvedData(trackToUse || res.tracks[0], data, player.LavalinkManager.utils);
1008
1015
  });
1009
1016
  }
@@ -1440,8 +1447,8 @@ var LavalinkNode = class {
1440
1447
  }
1441
1448
  };
1442
1449
  handlePlayerOperations().finally(() => {
1443
- this.socket.close(1e3, "Node-Destroy");
1444
- this.socket.removeAllListeners();
1450
+ this.socket?.close(1e3, "Node-Destroy");
1451
+ this.socket?.removeAllListeners();
1445
1452
  this.socket = null;
1446
1453
  this.reconnectAttempts = 1;
1447
1454
  clearTimeout(this.reconnectTimeout);
@@ -1455,8 +1462,8 @@ var LavalinkNode = class {
1455
1462
  }
1456
1463
  });
1457
1464
  } else {
1458
- this.socket.close(1e3, "Node-Destroy");
1459
- this.socket.removeAllListeners();
1465
+ this.socket?.close(1e3, "Node-Destroy");
1466
+ this.socket?.removeAllListeners();
1460
1467
  this.socket = null;
1461
1468
  this.reconnectAttempts = 1;
1462
1469
  clearTimeout(this.reconnectTimeout);
@@ -1485,8 +1492,8 @@ var LavalinkNode = class {
1485
1492
  */
1486
1493
  disconnect(disconnectReason) {
1487
1494
  if (!this.connected) return;
1488
- this.socket.close(1e3, "Node-Disconnect");
1489
- this.socket.removeAllListeners();
1495
+ this.socket?.close(1e3, "Node-Disconnect");
1496
+ this.socket?.removeAllListeners();
1490
1497
  this.socket = null;
1491
1498
  this.reconnectAttempts = 1;
1492
1499
  clearTimeout(this.reconnectTimeout);
@@ -1877,8 +1884,6 @@ var LavalinkNode = class {
1877
1884
  this.NodeManager.emit("error", this, error);
1878
1885
  return this.destroy("NodeReconnectFail" /* NodeReconnectFail */);
1879
1886
  }
1880
- this.socket.removeAllListeners();
1881
- this.socket = null;
1882
1887
  this.NodeManager.emit("reconnecting", this);
1883
1888
  this.connect();
1884
1889
  this.reconnectAttempts++;
@@ -1891,8 +1896,6 @@ var LavalinkNode = class {
1891
1896
  this.NodeManager.emit("error", this, error);
1892
1897
  return this.destroy("NodeReconnectFail" /* NodeReconnectFail */);
1893
1898
  }
1894
- this.socket.removeAllListeners();
1895
- this.socket = null;
1896
1899
  this.NodeManager.emit("reconnecting", this);
1897
1900
  this.connect();
1898
1901
  this.reconnectAttempts++;
@@ -1901,6 +1904,8 @@ var LavalinkNode = class {
1901
1904
  /** @private util function for handling opening events from websocket */
1902
1905
  async open() {
1903
1906
  this.isAlive = true;
1907
+ this.reconnectAttempts = 1;
1908
+ if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout);
1904
1909
  if (this.options.enablePingOnStatsCheck) this.heartBeat();
1905
1910
  if (this.heartBeatInterval) clearInterval(this.heartBeatInterval);
1906
1911
  if (this.options.heartBeatInterval > 0) {
@@ -1916,8 +1921,6 @@ var LavalinkNode = class {
1916
1921
  this.socket.ping();
1917
1922
  }, this.options.heartBeatInterval || 3e4);
1918
1923
  }
1919
- if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout);
1920
- this.reconnectAttempts = 1;
1921
1924
  this.info = await this.fetchInfo().catch((e) => (console.error(e, "ON-OPEN-FETCH"), null));
1922
1925
  if (!this.info && ["v3", "v4"].includes(this.version)) {
1923
1926
  const errorString = `Lavalink Node (${this.restAddress}) does not provide any /${this.version}/info`;
@@ -1928,6 +1931,21 @@ var LavalinkNode = class {
1928
1931
  /** @private util function for handling closing events from websocket */
1929
1932
  close(code, reason) {
1930
1933
  if (this.pingTimeout) clearTimeout(this.pingTimeout);
1934
+ try {
1935
+ if (this.socket) {
1936
+ this.socket.removeAllListeners();
1937
+ this.socket = null;
1938
+ }
1939
+ } catch (e) {
1940
+ if (this.NodeManager?.LavalinkManager?.options?.advancedOptions?.enableDebugEvents) {
1941
+ this.NodeManager.LavalinkManager.emit("debug", "SocketCleanupError" /* SocketCleanupError */, {
1942
+ state: "warn",
1943
+ message: `An error occurred during socket cleanup in close() (likely a race condition): ${e.message}`,
1944
+ functionLayer: "LavalinkNode > close()"
1945
+ });
1946
+ }
1947
+ }
1948
+ this.isAlive = false;
1931
1949
  if (this.heartBeatInterval) clearInterval(this.heartBeatInterval);
1932
1950
  if (code === 1006 && !reason) reason = "Socket got terminated due to no ping connection";
1933
1951
  if (code === 1e3 && reason === "Node-Disconnect") return;
@@ -2697,7 +2715,7 @@ var bandCampSearch = async (player, query, requestUser) => {
2697
2715
  artworkUrl: item.img,
2698
2716
  author: item.band_name,
2699
2717
  title: item.name,
2700
- identifier: item.id ? `${item.id}` : item.url?.split("/").reverse()[0]
2718
+ identifier: item.id ? `${item.id}` : item.url?.split("/")?.reverse()[0]
2701
2719
  }, requestUser));
2702
2720
  } catch (e) {
2703
2721
  error = e;
@@ -3741,6 +3759,7 @@ var Queue = class {
3741
3759
  * - single Track | UnresolvedTrack
3742
3760
  * - multiple Track | UnresovedTrack
3743
3761
  * - at the index or multiple indexes
3762
+ * - Since v2.7 the removed tracks get unshifted into the previous queue state instead of pushed (indexed at the start instead of end - as it should)
3744
3763
  * @param removeQueryTrack
3745
3764
  * @returns null (if nothing was removed) / { removed } where removed is an array with all removed elements
3746
3765
  *
@@ -3786,9 +3805,10 @@ var Queue = class {
3786
3805
  if (Array.isArray(removeQueryTrack)) {
3787
3806
  if (removeQueryTrack.every((v) => typeof v === "number")) {
3788
3807
  const removed3 = [];
3789
- for (const i of removeQueryTrack) {
3808
+ const sortedIndexes = removeQueryTrack.sort((a, b) => b - a);
3809
+ for (const i of sortedIndexes) {
3790
3810
  if (this.tracks[i]) {
3791
- removed3.push(...this.tracks.splice(i, 1));
3811
+ removed3.unshift(...this.tracks.splice(i, 1));
3792
3812
  }
3793
3813
  }
3794
3814
  if (!removed3.length) return null;
@@ -3804,9 +3824,10 @@ var Queue = class {
3804
3824
  ));
3805
3825
  if (!tracksToRemove.length) return null;
3806
3826
  const removed2 = [];
3827
+ tracksToRemove.sort((a, b) => b.i - a.i);
3807
3828
  for (const { i } of tracksToRemove) {
3808
3829
  if (this.tracks[i]) {
3809
- removed2.push(...this.tracks.splice(i, 1));
3830
+ removed2.unshift(...this.tracks.splice(i, 1));
3810
3831
  }
3811
3832
  }
3812
3833
  if (typeof this.queueChanges?.tracksRemoved === "function") try {
@@ -4029,7 +4050,7 @@ var Player = class {
4029
4050
  this.queue.current = options.clientTrack || null;
4030
4051
  this.queue.utils.save();
4031
4052
  if (typeof options?.volume === "number" && !isNaN(options?.volume)) {
4032
- this.volume = Math.max(Math.min(options?.volume, 500), 0);
4053
+ this.volume = Math.max(Math.min(options?.volume, 1e3), 0);
4033
4054
  let vol = Number(this.volume);
4034
4055
  if (this.LavalinkManager.options.playerOptions.volumeDecrementer) vol *= this.LavalinkManager.options.playerOptions.volumeDecrementer;
4035
4056
  this.lavalinkVolume = Math.round(vol);
@@ -4099,7 +4120,7 @@ var Player = class {
4099
4120
  }
4100
4121
  if (!this.queue.current) throw new Error(`There is no Track in the Queue, nor provided in the PlayOptions`);
4101
4122
  if (typeof options?.volume === "number" && !isNaN(options?.volume)) {
4102
- this.volume = Math.max(Math.min(options?.volume, 500), 0);
4123
+ this.volume = Math.max(Math.min(options?.volume, 1e3), 0);
4103
4124
  let vol = Number(this.volume);
4104
4125
  if (this.LavalinkManager.options.playerOptions.volumeDecrementer) vol *= this.LavalinkManager.options.playerOptions.volumeDecrementer;
4105
4126
  this.lavalinkVolume = Math.round(vol);
@@ -4122,9 +4143,9 @@ var Player = class {
4122
4143
  paused: options?.paused ?? void 0,
4123
4144
  voice: options?.voice ?? void 0
4124
4145
  }).filter((v) => typeof v[1] !== "undefined"));
4125
- if (typeof finalOptions.position !== "undefined" && isNaN(finalOptions.position) || typeof finalOptions.position === "number" && (finalOptions.position < 0 || finalOptions.position >= this.queue.current.info.duration)) throw new Error("PlayerOption#position must be a positive number, less than track's duration");
4146
+ if (typeof finalOptions.position !== "undefined" && isNaN(finalOptions.position) || typeof finalOptions.position === "number" && finalOptions.position < 0 || typeof finalOptions.position === "number" && this.queue.current.info.duration > 0 && finalOptions.position >= this.queue.current.info.duration) throw new Error("PlayerOption#position must be a positive number, less than track's duration");
4126
4147
  if (typeof finalOptions.volume !== "undefined" && isNaN(finalOptions.volume) || typeof finalOptions.volume === "number" && finalOptions.volume < 0) throw new Error("PlayerOption#volume must be a positive number");
4127
- if (typeof finalOptions.endTime !== "undefined" && isNaN(finalOptions.endTime) || typeof finalOptions.endTime === "number" && (finalOptions.endTime < 0 || finalOptions.endTime >= this.queue.current.info.duration)) throw new Error("PlayerOption#endTime must be a positive number, less than track's duration");
4148
+ if (typeof finalOptions.endTime !== "undefined" && isNaN(finalOptions.endTime) || typeof finalOptions.endTime === "number" && finalOptions.endTime < 0 || typeof finalOptions.endTime === "number" && this.queue.current.info.duration > 0 && finalOptions.endTime >= this.queue.current.info.duration) throw new Error("PlayerOption#endTime must be a positive number, less than track's duration");
4128
4149
  if (typeof finalOptions.position === "number" && typeof finalOptions.endTime === "number" && finalOptions.endTime < finalOptions.position) throw new Error("PlayerOption#endTime must be bigger than PlayerOption#position");
4129
4150
  const now = performance.now();
4130
4151
  await this.node.updatePlayer({
@@ -4199,7 +4220,7 @@ var Player = class {
4199
4220
  */
4200
4221
  async search(query, requestUser, throwOnEmpty = false) {
4201
4222
  const Query = this.LavalinkManager.utils.transformQuery(query);
4202
- if (["bcsearch", "bandcamp"].includes(Query.source) && !this.node.info.sourceManagers.includes("bandcamp")) {
4223
+ if (["bcsearch", "bandcamp"].includes(Query.source) && !this.node.info?.sourceManagers.includes("bandcamp")) {
4203
4224
  if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
4204
4225
  this.LavalinkManager.emit("debug", "BandcampSearchLokalEngine" /* BandcampSearchLokalEngine */, {
4205
4226
  state: "log",
@@ -4454,7 +4475,7 @@ var Player = class {
4454
4475
  if (this.queue.current || this.queue.tracks.length) {
4455
4476
  const trackSources = new Set([this.queue.current, ...this.queue.tracks].map((track) => track.info.sourceName));
4456
4477
  const missingSources = [...trackSources].filter(
4457
- (source) => !updateNode.info.sourceManagers.includes(source)
4478
+ (source) => !updateNode.info?.sourceManagers.includes(source)
4458
4479
  );
4459
4480
  if (missingSources.length)
4460
4481
  throw new RangeError(`Sources missing for Node ${updateNode.id}: ${missingSources.join(", ")}`);
@@ -4876,7 +4897,7 @@ var LavalinkManager = class extends import_events2.EventEmitter {
4876
4897
  deletePlayer(guildId) {
4877
4898
  const oldPlayer = this.getPlayer(guildId);
4878
4899
  if (!oldPlayer) return;
4879
- if (oldPlayer.voiceChannelId === "string" && oldPlayer.connected && !oldPlayer.get("internal_destroywithoutdisconnect")) {
4900
+ if (typeof oldPlayer.voiceChannelId === "string" && oldPlayer.connected && !oldPlayer.get("internal_destroywithoutdisconnect")) {
4880
4901
  if (!this.options?.advancedOptions?.debugOptions?.playerDestroy?.dontThrowError) throw new Error(`Use Player#destroy() not LavalinkManager#deletePlayer() to stop the Player ${safeStringify(oldPlayer.toJSON?.())}`);
4881
4902
  else if (this.options?.advancedOptions?.enableDebugEvents) {
4882
4903
  this.emit("debug", "PlayerDeleteInsteadOfDestroy" /* PlayerDeleteInsteadOfDestroy */, {
@@ -5139,6 +5160,7 @@ var LavalinkManager = class extends import_events2.EventEmitter {
5139
5160
  message: `Auto reconnected, but nothing to play`,
5140
5161
  functionLayer: "LavalinkManager > sendRawData()"
5141
5162
  });
5163
+ return;
5142
5164
  } catch (e) {
5143
5165
  console.error(e);
5144
5166
  return void await player.destroy("PlayerReconnectFail" /* PlayerReconnectFail */);
package/dist/index.mjs CHANGED
@@ -19,6 +19,7 @@ var DebugEvents = /* @__PURE__ */ ((DebugEvents2) => {
19
19
  DebugEvents2["PlayerUpdateSuccess"] = "PlayerUpdateSuccess";
20
20
  DebugEvents2["HeartBeatTriggered"] = "HeartBeatTriggered";
21
21
  DebugEvents2["NoSocketOnDestroy"] = "NoSocketOnDestroy";
22
+ DebugEvents2["SocketCleanupError"] = "SocketCleanupError";
22
23
  DebugEvents2["SocketTerminateHeartBeatTimeout"] = "SocketTerminateHeartBeatTimeout";
23
24
  DebugEvents2["TryingConnectWhileConnected"] = "TryingConnectWhileConnected";
24
25
  DebugEvents2["LavaSearchNothingFound"] = "LavaSearchNothingFound";
@@ -421,7 +422,7 @@ var SourceLinksRegexes = {
421
422
  /** DEFAULT SUPPORTED BY LAVALINK */
422
423
  YoutubeRegex: /https?:\/\/?(?:www\.)?(?:(m|www)\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|shorts|playlist\?|watch\?v=|watch\?.+(?:&|&#38;);v=))([a-zA-Z0-9\-_]{11})?(?:(?:\?|&|&#38;)index=((?:\d){1,3}))?(?:(?:\?|&|&#38;)?list=([a-zA-Z\-_0-9]{34}))?(?:\S+)?/,
423
424
  YoutubeMusicRegex: /https?:\/\/?(?:www\.)?(?:(music|m|www)\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|shorts|playlist\?|watch\?v=|watch\?.+(?:&|&#38;);v=))([a-zA-Z0-9\-_]{11})?(?:(?:\?|&|&#38;)index=((?:\d){1,3}))?(?:(?:\?|&|&#38;)?list=([a-zA-Z\-_0-9]{34}))?(?:\S+)?/,
424
- SoundCloudRegex: /https:\/\/(?:on\.)?soundcloud\.com\//,
425
+ SoundCloudRegex: /https?:\/\/(?:on\.)?soundcloud\.com\//,
425
426
  SoundCloudMobileRegex: /https?:\/\/(soundcloud\.app\.goo\.gl)\/(\S+)/,
426
427
  bandcamp: /https?:\/\/?(?:www\.)?([\d|\w]+)\.bandcamp\.com\/(\S+)/,
427
428
  TwitchTv: /https?:\/\/?(?:www\.)?twitch\.tv\/\w+/,
@@ -941,9 +942,15 @@ async function getClosestTrack(data, player) {
941
942
  source: sourceName !== "twitch" && sourceName !== "flowery-tts" ? sourceName : player.LavalinkManager.options?.playerOptions?.defaultSearchPlatform
942
943
  }, data.requester).then((res) => {
943
944
  let trackToUse = null;
944
- if (data.info.author && !trackToUse) trackToUse = res.tracks.find((track) => [data.info?.author || "", `${data.info?.author} - Topic`].some((name) => new RegExp(`^${escapeRegExp(name)}$`, "i").test(track.info?.author)) || new RegExp(`^${escapeRegExp(data.info?.title)}$`, "i").test(track.info?.title));
945
- if (data.info.duration && !trackToUse) trackToUse = res.tracks.find((track) => track.info?.duration >= data.info?.duration - 1500 && track?.info.duration <= data.info?.duration + 1500);
946
- if (data.info.isrc && !trackToUse) trackToUse = res.tracks.find((track) => track.info?.isrc === data.info?.isrc);
945
+ if ((data.info?.title || data.info?.author) && !trackToUse) trackToUse = res.tracks.find(
946
+ (track) => (
947
+ // find via author name (i ... case insensitve)
948
+ [data.info?.author || "", `${data.info?.author} - Topic`].some((name) => new RegExp(`^${escapeRegExp(name)}$`, "i").test(track.info?.author)) || // find via title (i ... case insensitve)
949
+ new RegExp(`^${escapeRegExp(data.info?.title)}$`, "i").test(track.info?.title)
950
+ )
951
+ );
952
+ if (data.info?.isrc && !trackToUse) trackToUse = res.tracks.find((track) => track.info?.isrc === data.info?.isrc);
953
+ if (data.info?.duration && !trackToUse) trackToUse = res.tracks.find((track) => track.info?.duration >= data.info?.duration - 1500 && track?.info.duration <= data.info?.duration + 1500);
947
954
  return applyUnresolvedData(trackToUse || res.tracks[0], data, player.LavalinkManager.utils);
948
955
  });
949
956
  }
@@ -1380,8 +1387,8 @@ var LavalinkNode = class {
1380
1387
  }
1381
1388
  };
1382
1389
  handlePlayerOperations().finally(() => {
1383
- this.socket.close(1e3, "Node-Destroy");
1384
- this.socket.removeAllListeners();
1390
+ this.socket?.close(1e3, "Node-Destroy");
1391
+ this.socket?.removeAllListeners();
1385
1392
  this.socket = null;
1386
1393
  this.reconnectAttempts = 1;
1387
1394
  clearTimeout(this.reconnectTimeout);
@@ -1395,8 +1402,8 @@ var LavalinkNode = class {
1395
1402
  }
1396
1403
  });
1397
1404
  } else {
1398
- this.socket.close(1e3, "Node-Destroy");
1399
- this.socket.removeAllListeners();
1405
+ this.socket?.close(1e3, "Node-Destroy");
1406
+ this.socket?.removeAllListeners();
1400
1407
  this.socket = null;
1401
1408
  this.reconnectAttempts = 1;
1402
1409
  clearTimeout(this.reconnectTimeout);
@@ -1425,8 +1432,8 @@ var LavalinkNode = class {
1425
1432
  */
1426
1433
  disconnect(disconnectReason) {
1427
1434
  if (!this.connected) return;
1428
- this.socket.close(1e3, "Node-Disconnect");
1429
- this.socket.removeAllListeners();
1435
+ this.socket?.close(1e3, "Node-Disconnect");
1436
+ this.socket?.removeAllListeners();
1430
1437
  this.socket = null;
1431
1438
  this.reconnectAttempts = 1;
1432
1439
  clearTimeout(this.reconnectTimeout);
@@ -1817,8 +1824,6 @@ var LavalinkNode = class {
1817
1824
  this.NodeManager.emit("error", this, error);
1818
1825
  return this.destroy("NodeReconnectFail" /* NodeReconnectFail */);
1819
1826
  }
1820
- this.socket.removeAllListeners();
1821
- this.socket = null;
1822
1827
  this.NodeManager.emit("reconnecting", this);
1823
1828
  this.connect();
1824
1829
  this.reconnectAttempts++;
@@ -1831,8 +1836,6 @@ var LavalinkNode = class {
1831
1836
  this.NodeManager.emit("error", this, error);
1832
1837
  return this.destroy("NodeReconnectFail" /* NodeReconnectFail */);
1833
1838
  }
1834
- this.socket.removeAllListeners();
1835
- this.socket = null;
1836
1839
  this.NodeManager.emit("reconnecting", this);
1837
1840
  this.connect();
1838
1841
  this.reconnectAttempts++;
@@ -1841,6 +1844,8 @@ var LavalinkNode = class {
1841
1844
  /** @private util function for handling opening events from websocket */
1842
1845
  async open() {
1843
1846
  this.isAlive = true;
1847
+ this.reconnectAttempts = 1;
1848
+ if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout);
1844
1849
  if (this.options.enablePingOnStatsCheck) this.heartBeat();
1845
1850
  if (this.heartBeatInterval) clearInterval(this.heartBeatInterval);
1846
1851
  if (this.options.heartBeatInterval > 0) {
@@ -1856,8 +1861,6 @@ var LavalinkNode = class {
1856
1861
  this.socket.ping();
1857
1862
  }, this.options.heartBeatInterval || 3e4);
1858
1863
  }
1859
- if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout);
1860
- this.reconnectAttempts = 1;
1861
1864
  this.info = await this.fetchInfo().catch((e) => (console.error(e, "ON-OPEN-FETCH"), null));
1862
1865
  if (!this.info && ["v3", "v4"].includes(this.version)) {
1863
1866
  const errorString = `Lavalink Node (${this.restAddress}) does not provide any /${this.version}/info`;
@@ -1868,6 +1871,21 @@ var LavalinkNode = class {
1868
1871
  /** @private util function for handling closing events from websocket */
1869
1872
  close(code, reason) {
1870
1873
  if (this.pingTimeout) clearTimeout(this.pingTimeout);
1874
+ try {
1875
+ if (this.socket) {
1876
+ this.socket.removeAllListeners();
1877
+ this.socket = null;
1878
+ }
1879
+ } catch (e) {
1880
+ if (this.NodeManager?.LavalinkManager?.options?.advancedOptions?.enableDebugEvents) {
1881
+ this.NodeManager.LavalinkManager.emit("debug", "SocketCleanupError" /* SocketCleanupError */, {
1882
+ state: "warn",
1883
+ message: `An error occurred during socket cleanup in close() (likely a race condition): ${e.message}`,
1884
+ functionLayer: "LavalinkNode > close()"
1885
+ });
1886
+ }
1887
+ }
1888
+ this.isAlive = false;
1871
1889
  if (this.heartBeatInterval) clearInterval(this.heartBeatInterval);
1872
1890
  if (code === 1006 && !reason) reason = "Socket got terminated due to no ping connection";
1873
1891
  if (code === 1e3 && reason === "Node-Disconnect") return;
@@ -2637,7 +2655,7 @@ var bandCampSearch = async (player, query, requestUser) => {
2637
2655
  artworkUrl: item.img,
2638
2656
  author: item.band_name,
2639
2657
  title: item.name,
2640
- identifier: item.id ? `${item.id}` : item.url?.split("/").reverse()[0]
2658
+ identifier: item.id ? `${item.id}` : item.url?.split("/")?.reverse()[0]
2641
2659
  }, requestUser));
2642
2660
  } catch (e) {
2643
2661
  error = e;
@@ -3681,6 +3699,7 @@ var Queue = class {
3681
3699
  * - single Track | UnresolvedTrack
3682
3700
  * - multiple Track | UnresovedTrack
3683
3701
  * - at the index or multiple indexes
3702
+ * - Since v2.7 the removed tracks get unshifted into the previous queue state instead of pushed (indexed at the start instead of end - as it should)
3684
3703
  * @param removeQueryTrack
3685
3704
  * @returns null (if nothing was removed) / { removed } where removed is an array with all removed elements
3686
3705
  *
@@ -3726,9 +3745,10 @@ var Queue = class {
3726
3745
  if (Array.isArray(removeQueryTrack)) {
3727
3746
  if (removeQueryTrack.every((v) => typeof v === "number")) {
3728
3747
  const removed3 = [];
3729
- for (const i of removeQueryTrack) {
3748
+ const sortedIndexes = removeQueryTrack.sort((a, b) => b - a);
3749
+ for (const i of sortedIndexes) {
3730
3750
  if (this.tracks[i]) {
3731
- removed3.push(...this.tracks.splice(i, 1));
3751
+ removed3.unshift(...this.tracks.splice(i, 1));
3732
3752
  }
3733
3753
  }
3734
3754
  if (!removed3.length) return null;
@@ -3744,9 +3764,10 @@ var Queue = class {
3744
3764
  ));
3745
3765
  if (!tracksToRemove.length) return null;
3746
3766
  const removed2 = [];
3767
+ tracksToRemove.sort((a, b) => b.i - a.i);
3747
3768
  for (const { i } of tracksToRemove) {
3748
3769
  if (this.tracks[i]) {
3749
- removed2.push(...this.tracks.splice(i, 1));
3770
+ removed2.unshift(...this.tracks.splice(i, 1));
3750
3771
  }
3751
3772
  }
3752
3773
  if (typeof this.queueChanges?.tracksRemoved === "function") try {
@@ -3969,7 +3990,7 @@ var Player = class {
3969
3990
  this.queue.current = options.clientTrack || null;
3970
3991
  this.queue.utils.save();
3971
3992
  if (typeof options?.volume === "number" && !isNaN(options?.volume)) {
3972
- this.volume = Math.max(Math.min(options?.volume, 500), 0);
3993
+ this.volume = Math.max(Math.min(options?.volume, 1e3), 0);
3973
3994
  let vol = Number(this.volume);
3974
3995
  if (this.LavalinkManager.options.playerOptions.volumeDecrementer) vol *= this.LavalinkManager.options.playerOptions.volumeDecrementer;
3975
3996
  this.lavalinkVolume = Math.round(vol);
@@ -4039,7 +4060,7 @@ var Player = class {
4039
4060
  }
4040
4061
  if (!this.queue.current) throw new Error(`There is no Track in the Queue, nor provided in the PlayOptions`);
4041
4062
  if (typeof options?.volume === "number" && !isNaN(options?.volume)) {
4042
- this.volume = Math.max(Math.min(options?.volume, 500), 0);
4063
+ this.volume = Math.max(Math.min(options?.volume, 1e3), 0);
4043
4064
  let vol = Number(this.volume);
4044
4065
  if (this.LavalinkManager.options.playerOptions.volumeDecrementer) vol *= this.LavalinkManager.options.playerOptions.volumeDecrementer;
4045
4066
  this.lavalinkVolume = Math.round(vol);
@@ -4062,9 +4083,9 @@ var Player = class {
4062
4083
  paused: options?.paused ?? void 0,
4063
4084
  voice: options?.voice ?? void 0
4064
4085
  }).filter((v) => typeof v[1] !== "undefined"));
4065
- if (typeof finalOptions.position !== "undefined" && isNaN(finalOptions.position) || typeof finalOptions.position === "number" && (finalOptions.position < 0 || finalOptions.position >= this.queue.current.info.duration)) throw new Error("PlayerOption#position must be a positive number, less than track's duration");
4086
+ if (typeof finalOptions.position !== "undefined" && isNaN(finalOptions.position) || typeof finalOptions.position === "number" && finalOptions.position < 0 || typeof finalOptions.position === "number" && this.queue.current.info.duration > 0 && finalOptions.position >= this.queue.current.info.duration) throw new Error("PlayerOption#position must be a positive number, less than track's duration");
4066
4087
  if (typeof finalOptions.volume !== "undefined" && isNaN(finalOptions.volume) || typeof finalOptions.volume === "number" && finalOptions.volume < 0) throw new Error("PlayerOption#volume must be a positive number");
4067
- if (typeof finalOptions.endTime !== "undefined" && isNaN(finalOptions.endTime) || typeof finalOptions.endTime === "number" && (finalOptions.endTime < 0 || finalOptions.endTime >= this.queue.current.info.duration)) throw new Error("PlayerOption#endTime must be a positive number, less than track's duration");
4088
+ if (typeof finalOptions.endTime !== "undefined" && isNaN(finalOptions.endTime) || typeof finalOptions.endTime === "number" && finalOptions.endTime < 0 || typeof finalOptions.endTime === "number" && this.queue.current.info.duration > 0 && finalOptions.endTime >= this.queue.current.info.duration) throw new Error("PlayerOption#endTime must be a positive number, less than track's duration");
4068
4089
  if (typeof finalOptions.position === "number" && typeof finalOptions.endTime === "number" && finalOptions.endTime < finalOptions.position) throw new Error("PlayerOption#endTime must be bigger than PlayerOption#position");
4069
4090
  const now = performance.now();
4070
4091
  await this.node.updatePlayer({
@@ -4139,7 +4160,7 @@ var Player = class {
4139
4160
  */
4140
4161
  async search(query, requestUser, throwOnEmpty = false) {
4141
4162
  const Query = this.LavalinkManager.utils.transformQuery(query);
4142
- if (["bcsearch", "bandcamp"].includes(Query.source) && !this.node.info.sourceManagers.includes("bandcamp")) {
4163
+ if (["bcsearch", "bandcamp"].includes(Query.source) && !this.node.info?.sourceManagers.includes("bandcamp")) {
4143
4164
  if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
4144
4165
  this.LavalinkManager.emit("debug", "BandcampSearchLokalEngine" /* BandcampSearchLokalEngine */, {
4145
4166
  state: "log",
@@ -4394,7 +4415,7 @@ var Player = class {
4394
4415
  if (this.queue.current || this.queue.tracks.length) {
4395
4416
  const trackSources = new Set([this.queue.current, ...this.queue.tracks].map((track) => track.info.sourceName));
4396
4417
  const missingSources = [...trackSources].filter(
4397
- (source) => !updateNode.info.sourceManagers.includes(source)
4418
+ (source) => !updateNode.info?.sourceManagers.includes(source)
4398
4419
  );
4399
4420
  if (missingSources.length)
4400
4421
  throw new RangeError(`Sources missing for Node ${updateNode.id}: ${missingSources.join(", ")}`);
@@ -4816,7 +4837,7 @@ var LavalinkManager = class extends EventEmitter2 {
4816
4837
  deletePlayer(guildId) {
4817
4838
  const oldPlayer = this.getPlayer(guildId);
4818
4839
  if (!oldPlayer) return;
4819
- if (oldPlayer.voiceChannelId === "string" && oldPlayer.connected && !oldPlayer.get("internal_destroywithoutdisconnect")) {
4840
+ if (typeof oldPlayer.voiceChannelId === "string" && oldPlayer.connected && !oldPlayer.get("internal_destroywithoutdisconnect")) {
4820
4841
  if (!this.options?.advancedOptions?.debugOptions?.playerDestroy?.dontThrowError) throw new Error(`Use Player#destroy() not LavalinkManager#deletePlayer() to stop the Player ${safeStringify(oldPlayer.toJSON?.())}`);
4821
4842
  else if (this.options?.advancedOptions?.enableDebugEvents) {
4822
4843
  this.emit("debug", "PlayerDeleteInsteadOfDestroy" /* PlayerDeleteInsteadOfDestroy */, {
@@ -5079,6 +5100,7 @@ var LavalinkManager = class extends EventEmitter2 {
5079
5100
  message: `Auto reconnected, but nothing to play`,
5080
5101
  functionLayer: "LavalinkManager > sendRawData()"
5081
5102
  });
5103
+ return;
5082
5104
  } catch (e) {
5083
5105
  console.error(e);
5084
5106
  return void await player.destroy("PlayerReconnectFail" /* PlayerReconnectFail */);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lavalink-client",
3
- "version": "2.6.7",
3
+ "version": "2.7.0",
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",
@@ -57,12 +57,12 @@
57
57
  "devDependencies": {
58
58
  "@eslint/eslintrc": "^3.3.1",
59
59
  "@eslint/js": "^9.39.1",
60
- "@types/node": "^24.10.0",
60
+ "@types/node": "^24.10.1",
61
61
  "@types/ws": "^8.18.1",
62
- "@typescript-eslint/eslint-plugin": "^8.46.3",
63
- "@typescript-eslint/parser": "^8.46.3",
62
+ "@typescript-eslint/eslint-plugin": "^8.46.4",
63
+ "@typescript-eslint/parser": "^8.46.4",
64
64
  "eslint": "^9.39.1",
65
- "tsup": "^8.5.0",
65
+ "tsup": "^8.5.1",
66
66
  "typescript": "^5.9.3"
67
67
  },
68
68
  "dependencies": {