lavalink-client 2.2.1 → 2.2.2

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
@@ -2,7 +2,7 @@
2
2
  Easy, flexible and feature-rich lavalink@v4 Client. Both for Beginners and Proficients.
3
3
 
4
4
  <div align="center">
5
- <p>
5
+ <p>
6
6
  <img src="https://madewithlove.now.sh/at?heart=true&template=for-the-badge" alt="Made with love in Austria">
7
7
  <img alt="Made with TypeScript" src="https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white">
8
8
  </p>
@@ -75,7 +75,7 @@ Check out the [Documentation](https://lc4.gitbook.io/lavalink-client) | or the [
75
75
 
76
76
  - ✨ Choose able queue stores (maps, collections, redis, databases, ...)
77
77
  - You can create your own queueStore, thus make it easy to sync queues accross multiple connections (e.g. dashboard-bot)
78
- - Automated Queue Sync methods
78
+ - Automated Queue Sync methods
79
79
  - Automated unresolveable Tracks (save the queries as Partial Track Objects -> Fetch the tracks only once they are gonna play)
80
80
 
81
81
  - 😍 Included Filter & Equalizer Management
@@ -91,20 +91,20 @@ Check out the [Documentation](https://lc4.gitbook.io/lavalink-client) | or the [
91
91
 
92
92
  - 🛡️ Client Validations
93
93
  - Allows you to whitelist links and even blacklist links / words / domain names, so that it doesn't allow requests you don't want!
94
- - Checks almost all Lavalink Requests for out of bound errors, right before the request is made to prevent process breaking errors.
94
+ - Checks almost all Lavalink Requests for out of bound errors, right before the request is made to prevent process breaking errors.
95
95
 
96
96
  - 🧑‍💻 Memory friendly and easy style
97
97
  - Only the required data is displayed, and the store-way & types match Lavalink#IMPLEMENTATION.md
98
98
 
99
99
  - 😘 Automated Handlings
100
- - Skips the songs, on TrackEnd, TrackStuck, TrackError,
100
+ - Skips the songs, on TrackEnd, TrackStuck, TrackError,
101
101
  - Destroys the player on channeldelete
102
102
  - Pauses / resumes the player if it get's muted / unmuted (server-wide) [soon]
103
103
  - ...
104
104
 
105
105
  - 😁 Much much more!
106
106
 
107
- ***
107
+ ***
108
108
 
109
109
  # All Events:
110
110
 
@@ -169,10 +169,10 @@ class myCustomWatcher implements QueueChangesWatcher {
169
169
  this.client = client;
170
170
  }
171
171
  shuffled(guildId, oldStoredQueue, newStoredQueue) {
172
- console.log(`${this.client.guilds.cache.get(guildId)?.name || guildId}: Queue got shuffled`)
172
+ console.log(`${this.client.guilds.cache.get(guildId)?.name || guildId}: Queue got shuffled`)
173
173
  }
174
174
  tracksAdd(guildId, tracks, position, oldStoredQueue, newStoredQueue) {
175
- console.log(`${this.client.guilds.cache.get(guildId)?.name || guildId}: ${tracks.length} Tracks got added into the Queue at position #${position}`);
175
+ console.log(`${this.client.guilds.cache.get(guildId)?.name || guildId}: ${tracks.length} Tracks got added into the Queue at position #${position}`);
176
176
  }
177
177
  tracksRemoved(guildId, tracks, position, oldStoredQueue, newStoredQueue) {
178
178
  console.log(`${this.client.guilds.cache.get(guildId)?.name || guildId}: ${tracks.length} Tracks got removed from the Queue at position #${position}`);
@@ -208,9 +208,9 @@ client.lavalink.nodeManager.on("resumed", (node, payload, fetchedPlayers) => {
208
208
  await player.queue.utils.sync(); // only works with a queuestore
209
209
  // you can now overwride the player.queue.current track from the fetchedPlayer, or use the one from the queue.uztils.sync function
210
210
  // continue with your resuming code...
211
- }
211
+ }
212
212
  })
213
- ```
213
+ ```
214
214
 
215
215
  ***
216
216
 
@@ -228,7 +228,7 @@ const extraParams = new URLSearchParams();
228
228
  if(voice) extraParams.append(`voice`, voice);
229
229
 
230
230
  // all params for flowertts can be found here: https://flowery.pw/docs
231
- const response = await player.search({
231
+ const response = await player.search({
232
232
  query: `${query}`,
233
233
  extraQueryUrlParams: extraParams, // as of my knowledge this is currently only used for flowertts, adjusting the playback url dynamically mid-request
234
234
  source: "ftts"
@@ -236,7 +236,7 @@ const response = await player.search({
236
236
  ```
237
237
 
238
238
 
239
- ***
239
+ ***
240
240
 
241
241
 
242
242
  # UpdateLog
@@ -254,8 +254,8 @@ const response = await player.search({
254
254
  - `player.deleteSponsorBlock()` / `node.deleteSponsorBlock()`
255
255
  - That Plugin adds following **Events** to the **Manager**: `"SegmentsLoaded"`, `"SegmentSkipped"`, `"ChapterStarted"`, `"ChaptersLoaded"`
256
256
  - Example Bot show example in autoplayFunction how to "disable" / "enable" Autoplay with bot data variables.
257
- - Added `ManagerOptions#emitNewSongsOnly`. If set to true, it won't emit "trackStart" Event, when track.loop is active, or the new current track == the previous (current) track.
258
- - Added `ManagerOptions#linksBlacklist` which allows user to specify an array of regExp / strings to match query strings (for links / words) and if a match happens it doesn't allow the request (blacklist)
257
+ - Added `ManagerOptions#emitNewSongsOnly`. If set to true, it won't emit "trackStart" Event, when track.loop is active, or the new current track == the previous (current) track.
258
+ - Added `ManagerOptions#linksBlacklist` which allows user to specify an array of regExp / strings to match query strings (for links / words) and if a match happens it doesn't allow the request (blacklist)
259
259
  - Added `ManagerOptions#linksWhitelist` which allows user to specify an array of regExp / strings to match query strings (for links only) and if a match does NOT HAPPEN it doesn't allow the request (whitelist)
260
260
  - Added `ManagerOptions#linksAllowed` if set to false, it does not allow requests which are links
261
261
  - Moved `ManaagerOptions#debugOptions` to `ManaagerOptions#advancedOptions.debugOptions`
@@ -263,10 +263,10 @@ const response = await player.search({
263
263
  ### **Version 1.2.1**
264
264
  - Adjusted `player.stopPlaying()`
265
265
  - There are now following parameters. `stopPlaying(clearQueue:boolean = true, executeAutoplay:boolean = false)`.
266
- - On Default it now clears the queue and stops playing. Also it does not execute Autoplay on default. IF you want the function to behave differently, you can use the 2 states for that.
266
+ - On Default it now clears the queue and stops playing. Also it does not execute Autoplay on default. IF you want the function to behave differently, you can use the 2 states for that.
267
267
  - Fixed that it looped the current track if repeatmode === "track" / "queue". (it stops playing and loop stays)
268
268
  - Implemented a `parseLavalinkConnUrl(connectionUrl:string)` Util Function.
269
- - It allows you to parse Lavalink Connection Data of a Lavalink Connection Url.
269
+ - It allows you to parse Lavalink Connection Data of a Lavalink Connection Url.
270
270
  Pattern: `lavalink://<nodeId>:<nodeAuthorization(Password)>@<NodeHost>:<NodePort>`
271
271
  - Note that the nodeId and NodeAuthorization must be encoded via encodeURIComponents before you provide it into the function.
272
272
  - The function will return the following: `{ id: string, authorization: string, host: string, port: number }`
@@ -298,10 +298,10 @@ const response = await player.search({
298
298
  # and after connecting the nodeManager.on("resumed", (node, payload, players) => {}) will be executed, where you can sync the players!
299
299
 
300
300
  # Node Options got adjusted # It's a property not a method should be treated readonly
301
- + node.resuming: { enabled: boolean, timeout: number | null };
301
+ + node.resuming: { enabled: boolean, timeout: number | null };
302
302
 
303
303
  # Player function got added to stop playing without disconnecting
304
- + player.stopPlaying(clearQueue:boolean = true, executeAutoplay:boolean = false);
304
+ + player.stopPlaying(clearQueue:boolean = true, executeAutoplay:boolean = false);
305
305
 
306
306
  # Node functions for sponsorBlock Plugin (https://github.com/topi314/Sponsorblock-Plugin) got added
307
307
  + deleteSponsorBlock(player:Player)
@@ -321,7 +321,7 @@ const response = await player.search({
321
321
  # Lavalink track.userData got added (basically same feature as my custom pluginInfo.clientData system)
322
322
  # You only get the track.userData data through playerUpdate object
323
323
  ```
324
- In one of the next updates, there will be more queueWatcher options and more custom nodeevents to trace
324
+ In one of the next updates, there will be more queueWatcher options and more custom nodeevents to trace
325
325
 
326
326
  Most features of this update got tested, but if you encounter any bugs feel free to open an issue!
327
327
 
@@ -378,9 +378,35 @@ const extraParams = new URLSearchParams();
378
378
  if(voice) extraParams.append(`voice`, voice);
379
379
 
380
380
  // all params for flowertts can be found here: https://flowery.pw/docs
381
- const response = await player.search({
381
+ const response = await player.search({
382
382
  query: `${query}`,
383
383
  extraQueryUrlParams: extraParams, // as of my knowledge this is currently only used for flowertts, adjusting the playback url dynamically mid-request
384
384
  source: "ftts"
385
385
  }, interaction.user);
386
386
  ```
387
+
388
+
389
+ ## **Version 2.2.2**
390
+ - Fixed a bug in player.pause() where when you pause the track longer than the left over currentTrack.info.duration is, then it would auto skip the track on resume.
391
+ - Fixed the handling of the previous track array ( sometimes it adds "null", due to lavalink errors )
392
+ - Added new functions for the queue, to make migrations and coding easier for beginners,
393
+ - ` const previousTrack = await player.queue.shiftPrevious() ` -> removes the previously played track from the player.queue.previous array, and returns it, so you can use it for something like "play previous"
394
+ - *Neat 1-liner: ` await player.queue.shiftPrevious().then(clientTrack => player.play({ clientTrack })) `*
395
+ - ` await player.queue.remove(removeQuery) ` -> Remove function to remove stuff from the queue.tracks array., following params are valid:
396
+ - Array of Tracks / UnresolvedTracks, e.g. ` await player.queue.remove( player.queue.tracks.slice(4, 10) ) ` *(would remove tracks from #4 (incl.) to #10 (excl.) aka those indexes: 4, 5, 6, 7, 8, 9 - this is how array.slice works)*
397
+ - Single Track / UnresolveTrack, e.g. ` await player.queue.remove(player.queue.tracks[player.queue.tracks.length - 1]); ` *(would remove the last track)*
398
+ - Array of track-indexes, e.g. ` await player.queue.remove([1, 4, 5]) ` *(Would remove track #1, #4 and #5)*
399
+ - Single track index, e.g. ` await player.queue.remove(5) ` *(would remove the #5 track from the queue)*
400
+ - **NOTE:** I still highly recommend, to use the ` player.queue.splice() ` function for mutating the queue:
401
+ - it is possible to remove single tracks, multiple tracks and insert tracks at specific positions!
402
+ - *the remove function haven't been fully tested yet*
403
+ - Added `track.pluginInfo.clientData?.previousTrack` handling:
404
+ - If a track has this property in the pluginInfo in the clientData object set to "true" then it won't get added to the previous track array. Example:
405
+ ```js
406
+ const previousTrack = await player.queue.shiftPrevious();
407
+ if(previousTrack) {
408
+ const previousClientData = previousTrack.pluginInfo.clientData || {};
409
+ previousTrack.pluginInfo.clientData = { previousTrack: true, ...previousClientData }
410
+ await player.play({ clientTrack: previousTrack });
411
+ }
412
+ ```
@@ -78,9 +78,9 @@ class FilterManager {
78
78
  // "cutoffFrequency": 284, // Integer, higher than zero, in Hz.
79
79
  // "boostFactor": 1.24389 // Float, higher than 0.0. This alters volume output. A value of 1.0 means no volume change.
80
80
  },
81
- "normalization": { // Attenuates peaking where peaks are defined as having a higher value than {maxAmplitude}.
81
+ "normalization": { // Attenuates peaking where peaks are defined as having a higher value than {maxAmplitude}.
82
82
  // "maxAmplitude": 0.6327, // Float, within the range of 0.0 - 1.0. A value of 0.0 mutes the output.
83
- // "adaptive": true // false
83
+ // "adaptive": true // false
84
84
  },
85
85
  "echo": { // Self-explanatory; provides an echo effect.
86
86
  // "echoLength": 0.5649, // Float, higher than 0.0, in seconds (1.0 = 1 second).
@@ -253,9 +253,9 @@ class FilterManager {
253
253
  // "cutoffFrequency": 284, // Integer, higher than zero, in Hz.
254
254
  // "boostFactor": 1.24389 // Float, higher than 0.0. This alters volume output. A value of 1.0 means no volume change.
255
255
  },
256
- "normalization": { // Attenuates peaking where peaks are defined as having a higher value than {maxAmplitude}.
256
+ "normalization": { // Attenuates peaking where peaks are defined as having a higher value than {maxAmplitude}.
257
257
  // "maxAmplitude": 0.6327, // Float, within the range of 0.0 - 1.0. A value of 0.0 mutes the output.
258
- // "adaptive": true // false
258
+ // "adaptive": true // false
259
259
  },
260
260
  "echo": { // Self-explanatory; provides an echo effect.
261
261
  // "echoLength": 0.5649, // Float, higher than 0.0, in seconds (1.0 = 1 second).
@@ -95,7 +95,7 @@ export interface LavalinkManagerEvents {
95
95
  * Emitted when a Track finished.
96
96
  * @event Manager#trackEnd
97
97
  */
98
- "trackEnd": (player: Player, track: Track, payload: TrackEndEvent) => void;
98
+ "trackEnd": (player: Player, track: Track | null, payload: TrackEndEvent) => void;
99
99
  /**
100
100
  * Emitted when a Track got stuck while playing.
101
101
  * @event Manager#trackStuck
@@ -110,7 +110,7 @@ export interface LavalinkManagerEvents {
110
110
  * Emitted when the Playing finished and no more tracks in the queue.
111
111
  * @event Manager#queueEnd
112
112
  */
113
- "queueEnd": (player: Player, track: Track, payload: TrackEndEvent | TrackStuckEvent | TrackExceptionEvent) => void;
113
+ "queueEnd": (player: Player, track: Track | UnresolvedTrack | null, payload: TrackEndEvent | TrackStuckEvent | TrackExceptionEvent) => void;
114
114
  /**
115
115
  * Emitted when a Player is created.
116
116
  * @event Manager#playerCreate
@@ -147,28 +147,28 @@ export interface LavalinkManagerEvents {
147
147
  * @link https://github.com/topi314/Sponsorblock-Plugin#segmentsloaded
148
148
  * @event Manager#trackError
149
149
  */
150
- "SegmentsLoaded": (player: Player, track: Track | UnresolvedTrack, payload: SponsorBlockSegmentsLoaded) => void;
150
+ "SegmentsLoaded": (player: Player, track: Track | UnresolvedTrack | null, payload: SponsorBlockSegmentsLoaded) => void;
151
151
  /**
152
152
  * SPONSORBLOCK-PLUGIN EVENT
153
153
  * Emitted when a specific Segment was skipped
154
154
  * @link https://github.com/topi314/Sponsorblock-Plugin#segmentskipped
155
155
  * @event Manager#trackError
156
156
  */
157
- "SegmentSkipped": (player: Player, track: Track | UnresolvedTrack, payload: SponsorBlockSegmentSkipped) => void;
157
+ "SegmentSkipped": (player: Player, track: Track | UnresolvedTrack | null, payload: SponsorBlockSegmentSkipped) => void;
158
158
  /**
159
159
  * SPONSORBLOCK-PLUGIN EVENT
160
160
  * Emitted when a specific Chapter starts playing
161
161
  * @link https://github.com/topi314/Sponsorblock-Plugin#chapterstarted
162
162
  * @event Manager#trackError
163
163
  */
164
- "ChapterStarted": (player: Player, track: Track | UnresolvedTrack, payload: SponsorBlockChapterStarted) => void;
164
+ "ChapterStarted": (player: Player, track: Track | UnresolvedTrack | null, payload: SponsorBlockChapterStarted) => void;
165
165
  /**
166
166
  * SPONSORBLOCK-PLUGIN EVENT
167
167
  * Emitted when Chapters are loaded
168
168
  * @link https://github.com/topi314/Sponsorblock-Plugin#chaptersloaded
169
169
  * @event Manager#trackError
170
170
  */
171
- "ChaptersLoaded": (player: Player, track: Track | UnresolvedTrack, payload: SponsorBlockChaptersLoaded) => void;
171
+ "ChaptersLoaded": (player: Player, track: Track | UnresolvedTrack | null, payload: SponsorBlockChaptersLoaded) => void;
172
172
  }
173
173
  export interface LavalinkManager {
174
174
  /** @private */
@@ -257,7 +257,7 @@ class LavalinkManager extends events_1.EventEmitter {
257
257
  const oldPlayer = this.getPlayer(guildId);
258
258
  if (!oldPlayer)
259
259
  return;
260
- // oldPlayer.connected is operational. you could also do oldPlayer.voice?.token
260
+ // oldPlayer.connected is operational. you could also do oldPlayer.voice?.token
261
261
  if (oldPlayer.voiceChannelId === "string" && oldPlayer.connected && !oldPlayer.get("internal_destroywithoutdisconnect")) {
262
262
  if (!this.options?.advancedOptions?.debugOptions?.playerDestroy?.dontThrowError)
263
263
  throw new Error(`Use Player#destroy() not LavalinkManager#deletePlayer() to stop the Player ${JSON.stringify(oldPlayer.toJSON?.())}`);
@@ -26,7 +26,7 @@ exports.DefaultSources = {
26
26
  "am": "amsearch",
27
27
  "musicapple": "amsearch",
28
28
  "music apple": "amsearch",
29
- // spotify
29
+ // spotify
30
30
  "spotify": "spsearch",
31
31
  "spsearch": "spsearch",
32
32
  "sp": "spsearch",
@@ -355,9 +355,11 @@ class LavalinkNode {
355
355
  destroy(destroyReason, deleteNode = true) {
356
356
  if (!this.connected)
357
357
  return;
358
- const players = this.NodeManager.LavalinkManager.players.filter(p => p.node.id == this.id);
358
+ const players = this.NodeManager.LavalinkManager.players.filter(p => p.node.id === this.id);
359
359
  if (players)
360
- players.forEach(p => p.destroy(destroyReason || Player_1.DestroyReasons.NodeDestroy));
360
+ players.forEach(p => {
361
+ p.destroy(destroyReason || Player_1.DestroyReasons.NodeDestroy);
362
+ });
361
363
  this.socket.close(1000, "Node-Destroy");
362
364
  this.socket.removeAllListeners();
363
365
  this.socket = null;
@@ -880,7 +882,7 @@ class LavalinkNode {
880
882
  // remove tracks from the queue
881
883
  if (player.repeatMode !== "track" || player.get("internal_skipped"))
882
884
  await (0, Utils_1.queueTrackEnd)(player);
883
- else if (player.queue.current) { // If there was a current Track already and repeatmode === true, add it to the queue.
885
+ else if (player.queue.current && !player.queue.current?.pluginInfo?.clientData?.previousTrack) { // If there was a current Track already and repeatmode === true, add it to the queue.
884
886
  player.queue.previous.unshift(player.queue.current);
885
887
  if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
886
888
  player.queue.previous.splice(player.queue.options.maxPreviousTracks, player.queue.previous.length);
@@ -1029,7 +1031,12 @@ class LavalinkNode {
1029
1031
  }
1030
1032
  }
1031
1033
  player.set("internal_autoplayStopPlaying", undefined);
1032
- player.queue.previous.unshift(track);
1034
+ if (track && !track?.pluginInfo?.clientData?.previousTrack) { // If there was a current Track already and repeatmode === true, add it to the queue.
1035
+ player.queue.previous.unshift(track);
1036
+ if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
1037
+ player.queue.previous.splice(player.queue.options.maxPreviousTracks, player.queue.previous.length);
1038
+ await player.queue.utils.save();
1039
+ }
1033
1040
  if (payload?.reason !== "stopped") {
1034
1041
  await player.queue.utils.save();
1035
1042
  }
@@ -11,7 +11,9 @@ class NodeManager extends stream_1.EventEmitter {
11
11
  super();
12
12
  this.LavalinkManager = LavalinkManager;
13
13
  if (this.LavalinkManager.options.nodes)
14
- this.LavalinkManager.options.nodes.forEach(node => this.createNode(node));
14
+ this.LavalinkManager.options.nodes.forEach(node => {
15
+ this.createNode(node);
16
+ });
15
17
  }
16
18
  /**
17
19
  * Disconnects all Nodes from lavalink ws sockets
@@ -281,6 +281,7 @@ class Player {
281
281
  if (this.paused && !this.playing)
282
282
  throw new Error("Player is already paused - not able to pause.");
283
283
  this.paused = true;
284
+ this.lastPositionChange = null; // needs to removed to not cause issues
284
285
  const now = performance.now();
285
286
  await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { paused: true } });
286
287
  this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
@@ -113,4 +113,51 @@ export declare class Queue {
113
113
  * @returns {Track} Spliced Track
114
114
  */
115
115
  splice(index: number, amount: number, TrackOrTracks?: Track | UnresolvedTrack | (Track | UnresolvedTrack)[]): any;
116
+ /**
117
+ * Remove stuff from the queue.tracks array
118
+ * - single Track | UnresolvedTrack
119
+ * - multiple Track | UnresovedTrack
120
+ * - at the index or multiple indexes
121
+ * @param removeQueryTrack
122
+ * @returns null (if nothing was removed) / { removed } where removed is an array with all removed elements
123
+ *
124
+ * @example
125
+ * ```js
126
+ * // remove single track
127
+ *
128
+ * const track = player.queue.tracks[4];
129
+ * await player.queue.remove(track);
130
+ *
131
+ * // if you already have the index you can straight up pass it too
132
+ * await player.queue.remove(4);
133
+ *
134
+ *
135
+ * // if you want to remove multiple tracks, e.g. from position 4 to position 10 you can do smt like this
136
+ * await player.queue.remove(player.queue.tracks.slice(4, 10)) // get's the tracks from 4 - 10, which then get's found in the remove function to be removed
137
+ *
138
+ * // I still highly suggest to use .splice!
139
+ *
140
+ * await player.queue.splice(4, 10); // removes at index 4, 10 tracks
141
+ *
142
+ * await player.queue.splice(1, 1); // removes at index 1, 1 track
143
+ *
144
+ * await player.queue.splice(4, 0, ...tracks) // removes 0 tracks at position 4, and then inserts all tracks after position 4.
145
+ * ```
146
+ */
147
+ remove<T extends Track | UnresolvedTrack | number | Track[] | UnresolvedTrack[] | number[] | (number | Track | UnresolvedTrack)[]>(removeQueryTrack: T): Promise<{
148
+ removed: (Track | UnresolvedTrack)[];
149
+ } | null>;
150
+ /**
151
+ * Shifts the previous array, to return the last previous track & thus remove it from the previous queue
152
+ * @returns
153
+ *
154
+ * @example
155
+ * ```js
156
+ * // example on how to play the previous track again
157
+ * const previous = await player.queue.shiftPrevious(); // get the previous track and remove it from the previous queue array!!
158
+ * if(!previous) return console.error("No previous track found");
159
+ * await player.play({ clientTrack: previous }); // play it again
160
+ * ```
161
+ */
162
+ shiftPrevious(): Promise<Track>;
116
163
  }
@@ -123,7 +123,7 @@ class Queue {
123
123
  if (this.tracks.length <= 1)
124
124
  return this.tracks.length;
125
125
  // swap #1 and #2 if only 2 tracks.
126
- if (this.tracks.length == 2) {
126
+ if (this.tracks.length === 2) {
127
127
  [this.tracks[0], this.tracks[1]] = [this.tracks[1], this.tracks[0]];
128
128
  }
129
129
  else { // randomly swap places.
@@ -197,5 +197,108 @@ class Queue {
197
197
  // return the things
198
198
  return spliced.length === 1 ? spliced[0] : spliced;
199
199
  }
200
+ /**
201
+ * Remove stuff from the queue.tracks array
202
+ * - single Track | UnresolvedTrack
203
+ * - multiple Track | UnresovedTrack
204
+ * - at the index or multiple indexes
205
+ * @param removeQueryTrack
206
+ * @returns null (if nothing was removed) / { removed } where removed is an array with all removed elements
207
+ *
208
+ * @example
209
+ * ```js
210
+ * // remove single track
211
+ *
212
+ * const track = player.queue.tracks[4];
213
+ * await player.queue.remove(track);
214
+ *
215
+ * // if you already have the index you can straight up pass it too
216
+ * await player.queue.remove(4);
217
+ *
218
+ *
219
+ * // if you want to remove multiple tracks, e.g. from position 4 to position 10 you can do smt like this
220
+ * await player.queue.remove(player.queue.tracks.slice(4, 10)) // get's the tracks from 4 - 10, which then get's found in the remove function to be removed
221
+ *
222
+ * // I still highly suggest to use .splice!
223
+ *
224
+ * await player.queue.splice(4, 10); // removes at index 4, 10 tracks
225
+ *
226
+ * await player.queue.splice(1, 1); // removes at index 1, 1 track
227
+ *
228
+ * await player.queue.splice(4, 0, ...tracks) // removes 0 tracks at position 4, and then inserts all tracks after position 4.
229
+ * ```
230
+ */
231
+ async remove(removeQueryTrack) {
232
+ if (typeof removeQueryTrack === "number") {
233
+ const toRemove = this.tracks[removeQueryTrack];
234
+ if (!toRemove)
235
+ return null;
236
+ const removed = this.tracks.splice(removeQueryTrack, 1);
237
+ await this.utils.save();
238
+ console.log("0st", removed, toRemove);
239
+ return { removed };
240
+ }
241
+ if (Array.isArray(removeQueryTrack)) {
242
+ if (removeQueryTrack.every(v => typeof v === "number")) {
243
+ const removed = [];
244
+ for (const i of removeQueryTrack) {
245
+ if (this.tracks[i])
246
+ removed.push(...this.tracks.splice(i, 1));
247
+ }
248
+ console.log("1st", removed, removeQueryTrack);
249
+ if (!removed.length)
250
+ return null;
251
+ await this.utils.save();
252
+ return { removed };
253
+ }
254
+ const tracksToRemove = this.tracks.map((v, i) => ({ v, i })).filter(({ v, i }) => removeQueryTrack.find(t => typeof t === "number" && (t === i) ||
255
+ typeof t === "object" && (t.encoded && t.encoded === v.encoded ||
256
+ t.info?.identifier && t.info.identifier === v.info?.identifier ||
257
+ t.info?.uri && t.info.uri === v.info?.uri ||
258
+ t.info?.title && t.info.title === v.info?.title ||
259
+ t.info?.isrc && t.info.isrc === v.info?.isrc ||
260
+ t.info?.artworkUrl && t.info.artworkUrl === v.info?.artworkUrl)));
261
+ if (!tracksToRemove.length)
262
+ return null;
263
+ const removed = [];
264
+ for (const { i } of tracksToRemove) {
265
+ if (this.tracks[i])
266
+ removed.push(...this.tracks.splice(i, 1));
267
+ }
268
+ await this.utils.save();
269
+ console.log("2nd", removed, tracksToRemove);
270
+ return { removed };
271
+ }
272
+ const toRemove = this.tracks.findIndex((v) => removeQueryTrack.encoded && removeQueryTrack.encoded === v.encoded ||
273
+ removeQueryTrack.info?.identifier && removeQueryTrack.info.identifier === v.info?.identifier ||
274
+ removeQueryTrack.info?.uri && removeQueryTrack.info.uri === v.info?.uri ||
275
+ removeQueryTrack.info?.title && removeQueryTrack.info.title === v.info?.title ||
276
+ removeQueryTrack.info?.isrc && removeQueryTrack.info.isrc === v.info?.isrc ||
277
+ removeQueryTrack.info?.artworkUrl && removeQueryTrack.info.artworkUrl === v.info?.artworkUrl);
278
+ if (toRemove < 0)
279
+ return null;
280
+ const removed = this.tracks.splice(toRemove, 1);
281
+ await this.utils.save();
282
+ console.log("3rd", removed, toRemove);
283
+ return { removed };
284
+ }
285
+ /**
286
+ * Shifts the previous array, to return the last previous track & thus remove it from the previous queue
287
+ * @returns
288
+ *
289
+ * @example
290
+ * ```js
291
+ * // example on how to play the previous track again
292
+ * const previous = await player.queue.shiftPrevious(); // get the previous track and remove it from the previous queue array!!
293
+ * if(!previous) return console.error("No previous track found");
294
+ * await player.play({ clientTrack: previous }); // play it again
295
+ * ```
296
+ */
297
+ async shiftPrevious() {
298
+ const removed = this.previous.shift();
299
+ if (removed)
300
+ await this.utils.save();
301
+ return removed ?? null;
302
+ }
200
303
  }
201
304
  exports.Queue = Queue;
@@ -81,6 +81,7 @@ export interface PluginInfo {
81
81
  uri?: string;
82
82
  /** You can put specific track information here, to transform the tracks... */
83
83
  clientData?: {
84
+ previousTrack?: boolean;
84
85
  [key: string]: any;
85
86
  };
86
87
  }
@@ -345,10 +345,11 @@ class MiniMap extends Map {
345
345
  }
346
346
  exports.MiniMap = MiniMap;
347
347
  async function queueTrackEnd(player) {
348
- if (player.queue.current) { // if there was a current Track -> Add it
348
+ if (player.queue.current && !player.queue.current?.pluginInfo?.clientData?.previousTrack) { // If there was a current Track already and repeatmode === true, add it to the queue.
349
349
  player.queue.previous.unshift(player.queue.current);
350
350
  if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
351
351
  player.queue.previous.splice(player.queue.options.maxPreviousTracks, player.queue.previous.length);
352
+ await player.queue.utils.save();
352
353
  }
353
354
  // and if repeatMode == queue, add it back to the queue!
354
355
  if (player.repeatMode === "queue" && player.queue.current)
@@ -378,11 +379,11 @@ async function applyUnresolvedData(resTrack, data, utils) {
378
379
  resTrack.info.author = data.info.author;
379
380
  }
380
381
  else { // only overwrite if undefined / invalid
381
- if ((resTrack.info.title == 'Unknown title' || resTrack.info.title == "Unspecified description") && resTrack.info.title != data.info.title)
382
+ if ((resTrack.info.title === 'Unknown title' || resTrack.info.title === "Unspecified description") && resTrack.info.title != data.info.title)
382
383
  resTrack.info.title = data.info.title;
383
- if (resTrack.info.author != data.info.author)
384
+ if (resTrack.info.author !== data.info.author)
384
385
  resTrack.info.author = data.info.author;
385
- if (resTrack.info.artworkUrl != data.info.artworkUrl)
386
+ if (resTrack.info.artworkUrl !== data.info.artworkUrl)
386
387
  resTrack.info.artworkUrl = data.info.artworkUrl;
387
388
  }
388
389
  for (const key of Object.keys(data.info))
@@ -75,9 +75,9 @@ export class FilterManager {
75
75
  // "cutoffFrequency": 284, // Integer, higher than zero, in Hz.
76
76
  // "boostFactor": 1.24389 // Float, higher than 0.0. This alters volume output. A value of 1.0 means no volume change.
77
77
  },
78
- "normalization": { // Attenuates peaking where peaks are defined as having a higher value than {maxAmplitude}.
78
+ "normalization": { // Attenuates peaking where peaks are defined as having a higher value than {maxAmplitude}.
79
79
  // "maxAmplitude": 0.6327, // Float, within the range of 0.0 - 1.0. A value of 0.0 mutes the output.
80
- // "adaptive": true // false
80
+ // "adaptive": true // false
81
81
  },
82
82
  "echo": { // Self-explanatory; provides an echo effect.
83
83
  // "echoLength": 0.5649, // Float, higher than 0.0, in seconds (1.0 = 1 second).
@@ -250,9 +250,9 @@ export class FilterManager {
250
250
  // "cutoffFrequency": 284, // Integer, higher than zero, in Hz.
251
251
  // "boostFactor": 1.24389 // Float, higher than 0.0. This alters volume output. A value of 1.0 means no volume change.
252
252
  },
253
- "normalization": { // Attenuates peaking where peaks are defined as having a higher value than {maxAmplitude}.
253
+ "normalization": { // Attenuates peaking where peaks are defined as having a higher value than {maxAmplitude}.
254
254
  // "maxAmplitude": 0.6327, // Float, within the range of 0.0 - 1.0. A value of 0.0 mutes the output.
255
- // "adaptive": true // false
255
+ // "adaptive": true // false
256
256
  },
257
257
  "echo": { // Self-explanatory; provides an echo effect.
258
258
  // "echoLength": 0.5649, // Float, higher than 0.0, in seconds (1.0 = 1 second).
@@ -95,7 +95,7 @@ export interface LavalinkManagerEvents {
95
95
  * Emitted when a Track finished.
96
96
  * @event Manager#trackEnd
97
97
  */
98
- "trackEnd": (player: Player, track: Track, payload: TrackEndEvent) => void;
98
+ "trackEnd": (player: Player, track: Track | null, payload: TrackEndEvent) => void;
99
99
  /**
100
100
  * Emitted when a Track got stuck while playing.
101
101
  * @event Manager#trackStuck
@@ -110,7 +110,7 @@ export interface LavalinkManagerEvents {
110
110
  * Emitted when the Playing finished and no more tracks in the queue.
111
111
  * @event Manager#queueEnd
112
112
  */
113
- "queueEnd": (player: Player, track: Track, payload: TrackEndEvent | TrackStuckEvent | TrackExceptionEvent) => void;
113
+ "queueEnd": (player: Player, track: Track | UnresolvedTrack | null, payload: TrackEndEvent | TrackStuckEvent | TrackExceptionEvent) => void;
114
114
  /**
115
115
  * Emitted when a Player is created.
116
116
  * @event Manager#playerCreate
@@ -147,28 +147,28 @@ export interface LavalinkManagerEvents {
147
147
  * @link https://github.com/topi314/Sponsorblock-Plugin#segmentsloaded
148
148
  * @event Manager#trackError
149
149
  */
150
- "SegmentsLoaded": (player: Player, track: Track | UnresolvedTrack, payload: SponsorBlockSegmentsLoaded) => void;
150
+ "SegmentsLoaded": (player: Player, track: Track | UnresolvedTrack | null, payload: SponsorBlockSegmentsLoaded) => void;
151
151
  /**
152
152
  * SPONSORBLOCK-PLUGIN EVENT
153
153
  * Emitted when a specific Segment was skipped
154
154
  * @link https://github.com/topi314/Sponsorblock-Plugin#segmentskipped
155
155
  * @event Manager#trackError
156
156
  */
157
- "SegmentSkipped": (player: Player, track: Track | UnresolvedTrack, payload: SponsorBlockSegmentSkipped) => void;
157
+ "SegmentSkipped": (player: Player, track: Track | UnresolvedTrack | null, payload: SponsorBlockSegmentSkipped) => void;
158
158
  /**
159
159
  * SPONSORBLOCK-PLUGIN EVENT
160
160
  * Emitted when a specific Chapter starts playing
161
161
  * @link https://github.com/topi314/Sponsorblock-Plugin#chapterstarted
162
162
  * @event Manager#trackError
163
163
  */
164
- "ChapterStarted": (player: Player, track: Track | UnresolvedTrack, payload: SponsorBlockChapterStarted) => void;
164
+ "ChapterStarted": (player: Player, track: Track | UnresolvedTrack | null, payload: SponsorBlockChapterStarted) => void;
165
165
  /**
166
166
  * SPONSORBLOCK-PLUGIN EVENT
167
167
  * Emitted when Chapters are loaded
168
168
  * @link https://github.com/topi314/Sponsorblock-Plugin#chaptersloaded
169
169
  * @event Manager#trackError
170
170
  */
171
- "ChaptersLoaded": (player: Player, track: Track | UnresolvedTrack, payload: SponsorBlockChaptersLoaded) => void;
171
+ "ChaptersLoaded": (player: Player, track: Track | UnresolvedTrack | null, payload: SponsorBlockChaptersLoaded) => void;
172
172
  }
173
173
  export interface LavalinkManager {
174
174
  /** @private */
@@ -254,7 +254,7 @@ export class LavalinkManager extends EventEmitter {
254
254
  const oldPlayer = this.getPlayer(guildId);
255
255
  if (!oldPlayer)
256
256
  return;
257
- // oldPlayer.connected is operational. you could also do oldPlayer.voice?.token
257
+ // oldPlayer.connected is operational. you could also do oldPlayer.voice?.token
258
258
  if (oldPlayer.voiceChannelId === "string" && oldPlayer.connected && !oldPlayer.get("internal_destroywithoutdisconnect")) {
259
259
  if (!this.options?.advancedOptions?.debugOptions?.playerDestroy?.dontThrowError)
260
260
  throw new Error(`Use Player#destroy() not LavalinkManager#deletePlayer() to stop the Player ${JSON.stringify(oldPlayer.toJSON?.())}`);
@@ -23,7 +23,7 @@ export const DefaultSources = {
23
23
  "am": "amsearch",
24
24
  "musicapple": "amsearch",
25
25
  "music apple": "amsearch",
26
- // spotify
26
+ // spotify
27
27
  "spotify": "spsearch",
28
28
  "spsearch": "spsearch",
29
29
  "sp": "spsearch",
@@ -351,9 +351,11 @@ export class LavalinkNode {
351
351
  destroy(destroyReason, deleteNode = true) {
352
352
  if (!this.connected)
353
353
  return;
354
- const players = this.NodeManager.LavalinkManager.players.filter(p => p.node.id == this.id);
354
+ const players = this.NodeManager.LavalinkManager.players.filter(p => p.node.id === this.id);
355
355
  if (players)
356
- players.forEach(p => p.destroy(destroyReason || DestroyReasons.NodeDestroy));
356
+ players.forEach(p => {
357
+ p.destroy(destroyReason || DestroyReasons.NodeDestroy);
358
+ });
357
359
  this.socket.close(1000, "Node-Destroy");
358
360
  this.socket.removeAllListeners();
359
361
  this.socket = null;
@@ -876,7 +878,7 @@ export class LavalinkNode {
876
878
  // remove tracks from the queue
877
879
  if (player.repeatMode !== "track" || player.get("internal_skipped"))
878
880
  await queueTrackEnd(player);
879
- else if (player.queue.current) { // If there was a current Track already and repeatmode === true, add it to the queue.
881
+ else if (player.queue.current && !player.queue.current?.pluginInfo?.clientData?.previousTrack) { // If there was a current Track already and repeatmode === true, add it to the queue.
880
882
  player.queue.previous.unshift(player.queue.current);
881
883
  if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
882
884
  player.queue.previous.splice(player.queue.options.maxPreviousTracks, player.queue.previous.length);
@@ -1025,7 +1027,12 @@ export class LavalinkNode {
1025
1027
  }
1026
1028
  }
1027
1029
  player.set("internal_autoplayStopPlaying", undefined);
1028
- player.queue.previous.unshift(track);
1030
+ if (track && !track?.pluginInfo?.clientData?.previousTrack) { // If there was a current Track already and repeatmode === true, add it to the queue.
1031
+ player.queue.previous.unshift(track);
1032
+ if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
1033
+ player.queue.previous.splice(player.queue.options.maxPreviousTracks, player.queue.previous.length);
1034
+ await player.queue.utils.save();
1035
+ }
1029
1036
  if (payload?.reason !== "stopped") {
1030
1037
  await player.queue.utils.save();
1031
1038
  }
@@ -8,7 +8,9 @@ export class NodeManager extends EventEmitter {
8
8
  super();
9
9
  this.LavalinkManager = LavalinkManager;
10
10
  if (this.LavalinkManager.options.nodes)
11
- this.LavalinkManager.options.nodes.forEach(node => this.createNode(node));
11
+ this.LavalinkManager.options.nodes.forEach(node => {
12
+ this.createNode(node);
13
+ });
12
14
  }
13
15
  /**
14
16
  * Disconnects all Nodes from lavalink ws sockets
@@ -278,6 +278,7 @@ export class Player {
278
278
  if (this.paused && !this.playing)
279
279
  throw new Error("Player is already paused - not able to pause.");
280
280
  this.paused = true;
281
+ this.lastPositionChange = null; // needs to removed to not cause issues
281
282
  const now = performance.now();
282
283
  await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { paused: true } });
283
284
  this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
@@ -113,4 +113,51 @@ export declare class Queue {
113
113
  * @returns {Track} Spliced Track
114
114
  */
115
115
  splice(index: number, amount: number, TrackOrTracks?: Track | UnresolvedTrack | (Track | UnresolvedTrack)[]): any;
116
+ /**
117
+ * Remove stuff from the queue.tracks array
118
+ * - single Track | UnresolvedTrack
119
+ * - multiple Track | UnresovedTrack
120
+ * - at the index or multiple indexes
121
+ * @param removeQueryTrack
122
+ * @returns null (if nothing was removed) / { removed } where removed is an array with all removed elements
123
+ *
124
+ * @example
125
+ * ```js
126
+ * // remove single track
127
+ *
128
+ * const track = player.queue.tracks[4];
129
+ * await player.queue.remove(track);
130
+ *
131
+ * // if you already have the index you can straight up pass it too
132
+ * await player.queue.remove(4);
133
+ *
134
+ *
135
+ * // if you want to remove multiple tracks, e.g. from position 4 to position 10 you can do smt like this
136
+ * await player.queue.remove(player.queue.tracks.slice(4, 10)) // get's the tracks from 4 - 10, which then get's found in the remove function to be removed
137
+ *
138
+ * // I still highly suggest to use .splice!
139
+ *
140
+ * await player.queue.splice(4, 10); // removes at index 4, 10 tracks
141
+ *
142
+ * await player.queue.splice(1, 1); // removes at index 1, 1 track
143
+ *
144
+ * await player.queue.splice(4, 0, ...tracks) // removes 0 tracks at position 4, and then inserts all tracks after position 4.
145
+ * ```
146
+ */
147
+ remove<T extends Track | UnresolvedTrack | number | Track[] | UnresolvedTrack[] | number[] | (number | Track | UnresolvedTrack)[]>(removeQueryTrack: T): Promise<{
148
+ removed: (Track | UnresolvedTrack)[];
149
+ } | null>;
150
+ /**
151
+ * Shifts the previous array, to return the last previous track & thus remove it from the previous queue
152
+ * @returns
153
+ *
154
+ * @example
155
+ * ```js
156
+ * // example on how to play the previous track again
157
+ * const previous = await player.queue.shiftPrevious(); // get the previous track and remove it from the previous queue array!!
158
+ * if(!previous) return console.error("No previous track found");
159
+ * await player.play({ clientTrack: previous }); // play it again
160
+ * ```
161
+ */
162
+ shiftPrevious(): Promise<Track>;
116
163
  }
@@ -118,7 +118,7 @@ export class Queue {
118
118
  if (this.tracks.length <= 1)
119
119
  return this.tracks.length;
120
120
  // swap #1 and #2 if only 2 tracks.
121
- if (this.tracks.length == 2) {
121
+ if (this.tracks.length === 2) {
122
122
  [this.tracks[0], this.tracks[1]] = [this.tracks[1], this.tracks[0]];
123
123
  }
124
124
  else { // randomly swap places.
@@ -192,4 +192,107 @@ export class Queue {
192
192
  // return the things
193
193
  return spliced.length === 1 ? spliced[0] : spliced;
194
194
  }
195
+ /**
196
+ * Remove stuff from the queue.tracks array
197
+ * - single Track | UnresolvedTrack
198
+ * - multiple Track | UnresovedTrack
199
+ * - at the index or multiple indexes
200
+ * @param removeQueryTrack
201
+ * @returns null (if nothing was removed) / { removed } where removed is an array with all removed elements
202
+ *
203
+ * @example
204
+ * ```js
205
+ * // remove single track
206
+ *
207
+ * const track = player.queue.tracks[4];
208
+ * await player.queue.remove(track);
209
+ *
210
+ * // if you already have the index you can straight up pass it too
211
+ * await player.queue.remove(4);
212
+ *
213
+ *
214
+ * // if you want to remove multiple tracks, e.g. from position 4 to position 10 you can do smt like this
215
+ * await player.queue.remove(player.queue.tracks.slice(4, 10)) // get's the tracks from 4 - 10, which then get's found in the remove function to be removed
216
+ *
217
+ * // I still highly suggest to use .splice!
218
+ *
219
+ * await player.queue.splice(4, 10); // removes at index 4, 10 tracks
220
+ *
221
+ * await player.queue.splice(1, 1); // removes at index 1, 1 track
222
+ *
223
+ * await player.queue.splice(4, 0, ...tracks) // removes 0 tracks at position 4, and then inserts all tracks after position 4.
224
+ * ```
225
+ */
226
+ async remove(removeQueryTrack) {
227
+ if (typeof removeQueryTrack === "number") {
228
+ const toRemove = this.tracks[removeQueryTrack];
229
+ if (!toRemove)
230
+ return null;
231
+ const removed = this.tracks.splice(removeQueryTrack, 1);
232
+ await this.utils.save();
233
+ console.log("0st", removed, toRemove);
234
+ return { removed };
235
+ }
236
+ if (Array.isArray(removeQueryTrack)) {
237
+ if (removeQueryTrack.every(v => typeof v === "number")) {
238
+ const removed = [];
239
+ for (const i of removeQueryTrack) {
240
+ if (this.tracks[i])
241
+ removed.push(...this.tracks.splice(i, 1));
242
+ }
243
+ console.log("1st", removed, removeQueryTrack);
244
+ if (!removed.length)
245
+ return null;
246
+ await this.utils.save();
247
+ return { removed };
248
+ }
249
+ const tracksToRemove = this.tracks.map((v, i) => ({ v, i })).filter(({ v, i }) => removeQueryTrack.find(t => typeof t === "number" && (t === i) ||
250
+ typeof t === "object" && (t.encoded && t.encoded === v.encoded ||
251
+ t.info?.identifier && t.info.identifier === v.info?.identifier ||
252
+ t.info?.uri && t.info.uri === v.info?.uri ||
253
+ t.info?.title && t.info.title === v.info?.title ||
254
+ t.info?.isrc && t.info.isrc === v.info?.isrc ||
255
+ t.info?.artworkUrl && t.info.artworkUrl === v.info?.artworkUrl)));
256
+ if (!tracksToRemove.length)
257
+ return null;
258
+ const removed = [];
259
+ for (const { i } of tracksToRemove) {
260
+ if (this.tracks[i])
261
+ removed.push(...this.tracks.splice(i, 1));
262
+ }
263
+ await this.utils.save();
264
+ console.log("2nd", removed, tracksToRemove);
265
+ return { removed };
266
+ }
267
+ const toRemove = this.tracks.findIndex((v) => removeQueryTrack.encoded && removeQueryTrack.encoded === v.encoded ||
268
+ removeQueryTrack.info?.identifier && removeQueryTrack.info.identifier === v.info?.identifier ||
269
+ removeQueryTrack.info?.uri && removeQueryTrack.info.uri === v.info?.uri ||
270
+ removeQueryTrack.info?.title && removeQueryTrack.info.title === v.info?.title ||
271
+ removeQueryTrack.info?.isrc && removeQueryTrack.info.isrc === v.info?.isrc ||
272
+ removeQueryTrack.info?.artworkUrl && removeQueryTrack.info.artworkUrl === v.info?.artworkUrl);
273
+ if (toRemove < 0)
274
+ return null;
275
+ const removed = this.tracks.splice(toRemove, 1);
276
+ await this.utils.save();
277
+ console.log("3rd", removed, toRemove);
278
+ return { removed };
279
+ }
280
+ /**
281
+ * Shifts the previous array, to return the last previous track & thus remove it from the previous queue
282
+ * @returns
283
+ *
284
+ * @example
285
+ * ```js
286
+ * // example on how to play the previous track again
287
+ * const previous = await player.queue.shiftPrevious(); // get the previous track and remove it from the previous queue array!!
288
+ * if(!previous) return console.error("No previous track found");
289
+ * await player.play({ clientTrack: previous }); // play it again
290
+ * ```
291
+ */
292
+ async shiftPrevious() {
293
+ const removed = this.previous.shift();
294
+ if (removed)
295
+ await this.utils.save();
296
+ return removed ?? null;
297
+ }
195
298
  }
@@ -81,6 +81,7 @@ export interface PluginInfo {
81
81
  uri?: string;
82
82
  /** You can put specific track information here, to transform the tracks... */
83
83
  clientData?: {
84
+ previousTrack?: boolean;
84
85
  [key: string]: any;
85
86
  };
86
87
  }
@@ -339,10 +339,11 @@ export class MiniMap extends Map {
339
339
  }
340
340
  }
341
341
  export async function queueTrackEnd(player) {
342
- if (player.queue.current) { // if there was a current Track -> Add it
342
+ if (player.queue.current && !player.queue.current?.pluginInfo?.clientData?.previousTrack) { // If there was a current Track already and repeatmode === true, add it to the queue.
343
343
  player.queue.previous.unshift(player.queue.current);
344
344
  if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
345
345
  player.queue.previous.splice(player.queue.options.maxPreviousTracks, player.queue.previous.length);
346
+ await player.queue.utils.save();
346
347
  }
347
348
  // and if repeatMode == queue, add it back to the queue!
348
349
  if (player.repeatMode === "queue" && player.queue.current)
@@ -371,11 +372,11 @@ async function applyUnresolvedData(resTrack, data, utils) {
371
372
  resTrack.info.author = data.info.author;
372
373
  }
373
374
  else { // only overwrite if undefined / invalid
374
- if ((resTrack.info.title == 'Unknown title' || resTrack.info.title == "Unspecified description") && resTrack.info.title != data.info.title)
375
+ if ((resTrack.info.title === 'Unknown title' || resTrack.info.title === "Unspecified description") && resTrack.info.title != data.info.title)
375
376
  resTrack.info.title = data.info.title;
376
- if (resTrack.info.author != data.info.author)
377
+ if (resTrack.info.author !== data.info.author)
377
378
  resTrack.info.author = data.info.author;
378
- if (resTrack.info.artworkUrl != data.info.artworkUrl)
379
+ if (resTrack.info.artworkUrl !== data.info.artworkUrl)
379
380
  resTrack.info.artworkUrl = data.info.artworkUrl;
380
381
  }
381
382
  for (const key of Object.keys(data.info))
@@ -95,7 +95,7 @@ export interface LavalinkManagerEvents {
95
95
  * Emitted when a Track finished.
96
96
  * @event Manager#trackEnd
97
97
  */
98
- "trackEnd": (player: Player, track: Track, payload: TrackEndEvent) => void;
98
+ "trackEnd": (player: Player, track: Track | null, payload: TrackEndEvent) => void;
99
99
  /**
100
100
  * Emitted when a Track got stuck while playing.
101
101
  * @event Manager#trackStuck
@@ -110,7 +110,7 @@ export interface LavalinkManagerEvents {
110
110
  * Emitted when the Playing finished and no more tracks in the queue.
111
111
  * @event Manager#queueEnd
112
112
  */
113
- "queueEnd": (player: Player, track: Track, payload: TrackEndEvent | TrackStuckEvent | TrackExceptionEvent) => void;
113
+ "queueEnd": (player: Player, track: Track | UnresolvedTrack | null, payload: TrackEndEvent | TrackStuckEvent | TrackExceptionEvent) => void;
114
114
  /**
115
115
  * Emitted when a Player is created.
116
116
  * @event Manager#playerCreate
@@ -147,28 +147,28 @@ export interface LavalinkManagerEvents {
147
147
  * @link https://github.com/topi314/Sponsorblock-Plugin#segmentsloaded
148
148
  * @event Manager#trackError
149
149
  */
150
- "SegmentsLoaded": (player: Player, track: Track | UnresolvedTrack, payload: SponsorBlockSegmentsLoaded) => void;
150
+ "SegmentsLoaded": (player: Player, track: Track | UnresolvedTrack | null, payload: SponsorBlockSegmentsLoaded) => void;
151
151
  /**
152
152
  * SPONSORBLOCK-PLUGIN EVENT
153
153
  * Emitted when a specific Segment was skipped
154
154
  * @link https://github.com/topi314/Sponsorblock-Plugin#segmentskipped
155
155
  * @event Manager#trackError
156
156
  */
157
- "SegmentSkipped": (player: Player, track: Track | UnresolvedTrack, payload: SponsorBlockSegmentSkipped) => void;
157
+ "SegmentSkipped": (player: Player, track: Track | UnresolvedTrack | null, payload: SponsorBlockSegmentSkipped) => void;
158
158
  /**
159
159
  * SPONSORBLOCK-PLUGIN EVENT
160
160
  * Emitted when a specific Chapter starts playing
161
161
  * @link https://github.com/topi314/Sponsorblock-Plugin#chapterstarted
162
162
  * @event Manager#trackError
163
163
  */
164
- "ChapterStarted": (player: Player, track: Track | UnresolvedTrack, payload: SponsorBlockChapterStarted) => void;
164
+ "ChapterStarted": (player: Player, track: Track | UnresolvedTrack | null, payload: SponsorBlockChapterStarted) => void;
165
165
  /**
166
166
  * SPONSORBLOCK-PLUGIN EVENT
167
167
  * Emitted when Chapters are loaded
168
168
  * @link https://github.com/topi314/Sponsorblock-Plugin#chaptersloaded
169
169
  * @event Manager#trackError
170
170
  */
171
- "ChaptersLoaded": (player: Player, track: Track | UnresolvedTrack, payload: SponsorBlockChaptersLoaded) => void;
171
+ "ChaptersLoaded": (player: Player, track: Track | UnresolvedTrack | null, payload: SponsorBlockChaptersLoaded) => void;
172
172
  }
173
173
  export interface LavalinkManager {
174
174
  /** @private */
@@ -113,4 +113,51 @@ export declare class Queue {
113
113
  * @returns {Track} Spliced Track
114
114
  */
115
115
  splice(index: number, amount: number, TrackOrTracks?: Track | UnresolvedTrack | (Track | UnresolvedTrack)[]): any;
116
+ /**
117
+ * Remove stuff from the queue.tracks array
118
+ * - single Track | UnresolvedTrack
119
+ * - multiple Track | UnresovedTrack
120
+ * - at the index or multiple indexes
121
+ * @param removeQueryTrack
122
+ * @returns null (if nothing was removed) / { removed } where removed is an array with all removed elements
123
+ *
124
+ * @example
125
+ * ```js
126
+ * // remove single track
127
+ *
128
+ * const track = player.queue.tracks[4];
129
+ * await player.queue.remove(track);
130
+ *
131
+ * // if you already have the index you can straight up pass it too
132
+ * await player.queue.remove(4);
133
+ *
134
+ *
135
+ * // if you want to remove multiple tracks, e.g. from position 4 to position 10 you can do smt like this
136
+ * await player.queue.remove(player.queue.tracks.slice(4, 10)) // get's the tracks from 4 - 10, which then get's found in the remove function to be removed
137
+ *
138
+ * // I still highly suggest to use .splice!
139
+ *
140
+ * await player.queue.splice(4, 10); // removes at index 4, 10 tracks
141
+ *
142
+ * await player.queue.splice(1, 1); // removes at index 1, 1 track
143
+ *
144
+ * await player.queue.splice(4, 0, ...tracks) // removes 0 tracks at position 4, and then inserts all tracks after position 4.
145
+ * ```
146
+ */
147
+ remove<T extends Track | UnresolvedTrack | number | Track[] | UnresolvedTrack[] | number[] | (number | Track | UnresolvedTrack)[]>(removeQueryTrack: T): Promise<{
148
+ removed: (Track | UnresolvedTrack)[];
149
+ } | null>;
150
+ /**
151
+ * Shifts the previous array, to return the last previous track & thus remove it from the previous queue
152
+ * @returns
153
+ *
154
+ * @example
155
+ * ```js
156
+ * // example on how to play the previous track again
157
+ * const previous = await player.queue.shiftPrevious(); // get the previous track and remove it from the previous queue array!!
158
+ * if(!previous) return console.error("No previous track found");
159
+ * await player.play({ clientTrack: previous }); // play it again
160
+ * ```
161
+ */
162
+ shiftPrevious(): Promise<Track>;
116
163
  }
@@ -81,6 +81,7 @@ export interface PluginInfo {
81
81
  uri?: string;
82
82
  /** You can put specific track information here, to transform the tracks... */
83
83
  clientData?: {
84
+ previousTrack?: boolean;
84
85
  [key: string]: any;
85
86
  };
86
87
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lavalink-client",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
4
4
  "description": "Easy, flexible and feature-rich lavalink@v4 Client. Both for Beginners and Proficients.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",