lavalink-client 1.1.25 → 1.2.1

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
@@ -82,6 +82,10 @@ Check out the [Documentation](https://lc4.gitbook.io/lavalink-client) for **Exam
82
82
  - 🛡️ Lavalink Validations
83
83
  - It only let's you use the filters / plugins / sources, if Lavalink actually has it enabled
84
84
 
85
+ - 🛡️ Client Validations
86
+ - Allows you to whitelist links and even blacklist links / words / domain names, so that it doesn't allow requests you don't want!
87
+ - Checks almost all Lavalink Requests for out of bound errors, right before the request is made to prevent process breaking errors.
88
+
85
89
  - 🧑‍💻 Memory friendly and easy style
86
90
  - Only the required data is displayed, and the store-way & types match Lavalink#IMPLEMENTATION.md
87
91
 
@@ -91,4 +95,38 @@ Check out the [Documentation](https://lc4.gitbook.io/lavalink-client) for **Exam
91
95
  - Pauses / resumes the player if it get's muted / unmuted (server-wide) [soon]
92
96
  - ...
93
97
 
94
- - 😁 Much much more!
98
+ - 😁 Much much more!
99
+
100
+ # UpdateLog
101
+
102
+ ## **Version 1.2.0**
103
+ - Added `player.stopPlaying()`: When executed it **clears the Queue** and **stops playing**, **without destroying the Player**
104
+ - Adjusted `Player.skip()`
105
+ - Added `throwError` Property to: `player.skip(skipTo?:number = 0, throwError?:boolean = true)`.
106
+ - If throwError = false, and no more tracks are in the queue, it won't throw an error and "ignore it". same thing as stopPlaying.
107
+ - Added all Events and Methods from the [SponsorBlock Plugin](https://github.com/topi314/Sponsorblock-Plugin).
108
+ - It also validates if the plugin is in the bot, in order so that you can use the functions:
109
+ - `player.getSponsorBlock()` / `node.getSponsorBlock()`
110
+ - `player.setSponsorBlock(segments:SponsorBlockSegment[])` / `node.setSponsorBlock(segments:SponsorBlockSegment[])`
111
+ - `player.deleteSponsorBlock()` / `node.deleteSponsorBlock()`
112
+ - That Plugin adds following **Events** to the **Manager**: `"SegmentsLoaded"`, `"SegmentSkipped"`, `"ChapterStarted"`, `"ChaptersLoaded"`
113
+ - Example Bot show example in autoplayFunction how to "disable" / "enable" Autoplay with bot data variables.
114
+ - 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.
115
+ - 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)
116
+ - 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)
117
+ - Added `ManagerOptions#linksAllowed` if set to false, it does not allow requests which are links
118
+ - Moved `ManaagerOptions#debugOptions` to `ManaagerOptions#advancedOptions.debugOptions`
119
+
120
+ ### **Version 1.2.1**
121
+ - Adjusted `player.stopPlaying()`
122
+ - There are now following parameters. `stopPlaying(clearQueue:boolean = true, executeAutoplay:boolean = false)`.
123
+ - 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.
124
+ - Fixed that it looped the current track if repeatmode === "track" / "queue". (it stops playing and loop stays)
125
+ - Implemented a `parseLavalinkConnUrl(connectionUrl:string)` Util Function.
126
+ - It allows you to parse Lavalink Connection Data of a Lavalink Connection Url.
127
+ Pattern: `lavalink://<nodeId>:<nodeAuthorization(Password)>@<NodeHost>:<NodePort>`
128
+ - Note that the nodeId and NodeAuthorization must be encoded via encodeURIComponents before you provide it into the function.
129
+ - The function will return the following: `{ id: string, authorization: string, host: string, port: number }`
130
+ - Example: `parseLavalinkConnUrl("lavalink://LavalinkNode_1:strong%23password1@localhost:2345")` will give you:
131
+ `{ id: "LavalinkNode_1", authorization: "strong#password1", host: "localhost", port: 2345 }`
132
+ - Note that the password "strong#password1" when encoded turns into "strong%23password1". For more information check the example bot
@@ -5,6 +5,7 @@ const undici_1 = require("undici");
5
5
  const bandCampSearch = async (player, query, requestUser) => {
6
6
  let error = null;
7
7
  let tracks = [];
8
+ player.LavalinkManager.utils.validateQueryString(player.node, query);
8
9
  try {
9
10
  const data = await (0, undici_1.fetch)(`https://bandcamp.com/api/nusearch/2/autocomplete?q=${encodeURIComponent(query)}`, {
10
11
  headers: {
@@ -5,7 +5,7 @@ import { NodeManager } from "./NodeManager";
5
5
  import { DestroyReasonsType, Player, PlayerJson, PlayerOptions } from "./Player";
6
6
  import { ManagerQueueOptions } from "./Queue";
7
7
  import { Track, UnresolvedTrack } from "./Track";
8
- import { ChannelDeletePacket, GuildShardPayload, ManagerUtils, MiniMap, SearchPlatform, TrackEndEvent, TrackExceptionEvent, TrackStartEvent, TrackStuckEvent, VoicePacket, VoiceServer, VoiceState, WebSocketClosedEvent } from "./Utils";
8
+ import { ChannelDeletePacket, GuildShardPayload, ManagerUtils, MiniMap, SearchPlatform, SponsorBlockChaptersLoaded, SponsorBlockChapterStarted, SponsorBlockSegmentSkipped, SponsorBlockSegmentsLoaded, TrackEndEvent, TrackExceptionEvent, TrackStartEvent, TrackStuckEvent, VoicePacket, VoiceServer, VoiceState, WebSocketClosedEvent } from "./Utils";
9
9
  export interface LavalinkManager {
10
10
  nodeManager: NodeManager;
11
11
  utils: ManagerUtils;
@@ -33,7 +33,7 @@ export interface ManagerPlayerOptions {
33
33
  onDisconnect?: {
34
34
  /** Try to reconnect? -> If fails -> Destroy */
35
35
  autoReconnect?: boolean;
36
- /** Instantly destroy player (overrides autoReconnect) */
36
+ /** Instantly destroy player (overrides autoReconnect) | Don't provide == disable feature*/
37
37
  destroyPlayer?: boolean;
38
38
  };
39
39
  onEmptyQueue?: {
@@ -56,16 +56,27 @@ export interface ManagerOptions {
56
56
  playerOptions?: ManagerPlayerOptions;
57
57
  /** If it should skip to the next Track on TrackEnd / TrackError etc. events */
58
58
  autoSkip?: boolean;
59
- /** optional */
60
- debugOptions?: {
61
- /** logs for debugging the "no-Audio" playing error */
62
- noAudio?: boolean;
63
- /** For Logging the Destroy function */
64
- playerDestroy?: {
65
- /** To show the debug reason at all times. */
66
- debugLog?: boolean;
67
- /** If you get 'Error: Use Player#destroy("reason") not LavalinkManager#deletePlayer() to stop the Player' put it on true */
68
- dontThrowError?: boolean;
59
+ /** If it should emit only new (unique) songs and not when a looping track (or similar) is plaid, default false */
60
+ emitNewSongsOnly?: boolean;
61
+ /** Only allow link requests with links either matching some of that regExp or including some of that string */
62
+ linksWhitelist?: (RegExp | string)[];
63
+ /** Never allow link requests with links either matching some of that regExp or including some of that string (doesn't even allow if it's whitelisted) */
64
+ linksBlacklist?: (RegExp | string)[];
65
+ /** If links should be allowed or not. If set to false, it will throw an error if a link was provided. */
66
+ linksAllowed?: boolean;
67
+ /** Advanced Options for the Library, which may or may not be "library breaking" */
68
+ advancedOptions?: {
69
+ /** optional */
70
+ debugOptions?: {
71
+ /** logs for debugging the "no-Audio" playing error */
72
+ noAudio?: boolean;
73
+ /** For Logging the Destroy function */
74
+ playerDestroy?: {
75
+ /** To show the debug reason at all times. */
76
+ debugLog?: boolean;
77
+ /** If you get 'Error: Use Player#destroy("reason") not LavalinkManager#deletePlayer() to stop the Player' put it on true */
78
+ dontThrowError?: boolean;
79
+ };
69
80
  };
70
81
  };
71
82
  }
@@ -125,6 +136,34 @@ interface LavalinkManagerEvents {
125
136
  * @event Manager#playerUpdate
126
137
  */
127
138
  "playerUpdate": (oldPlayerJson: PlayerJson, newPlayer: Player) => void;
139
+ /**
140
+ * SPONSORBLOCK-PLUGIN EVENT
141
+ * Emitted when Segments are loaded
142
+ * @link https://github.com/topi314/Sponsorblock-Plugin#segmentsloaded
143
+ * @event Manager#trackError
144
+ */
145
+ "SegmentsLoaded": (player: Player, track: Track | UnresolvedTrack, payload: SponsorBlockSegmentsLoaded) => void;
146
+ /**
147
+ * SPONSORBLOCK-PLUGIN EVENT
148
+ * Emitted when a specific Segment was skipped
149
+ * @link https://github.com/topi314/Sponsorblock-Plugin#segmentskipped
150
+ * @event Manager#trackError
151
+ */
152
+ "SegmentSkipped": (player: Player, track: Track | UnresolvedTrack, payload: SponsorBlockSegmentSkipped) => void;
153
+ /**
154
+ * SPONSORBLOCK-PLUGIN EVENT
155
+ * Emitted when a specific Chapter starts playing
156
+ * @link https://github.com/topi314/Sponsorblock-Plugin#chapterstarted
157
+ * @event Manager#trackError
158
+ */
159
+ "ChapterStarted": (player: Player, track: Track | UnresolvedTrack, payload: SponsorBlockChapterStarted) => void;
160
+ /**
161
+ * SPONSORBLOCK-PLUGIN EVENT
162
+ * Emitted when Chapters are loaded
163
+ * @link https://github.com/topi314/Sponsorblock-Plugin#chaptersloaded
164
+ * @event Manager#trackError
165
+ */
166
+ "ChaptersLoaded": (player: Player, track: Track | UnresolvedTrack, payload: SponsorBlockChaptersLoaded) => void;
128
167
  }
129
168
  export interface LavalinkManager {
130
169
  options: ManagerOptions;
@@ -37,17 +37,22 @@ class LavalinkManager extends events_1.EventEmitter {
37
37
  requesterTransformer: options?.playerOptions?.requesterTransformer ?? null,
38
38
  useUnresolvedData: options?.playerOptions?.useUnresolvedData ?? false,
39
39
  },
40
+ linksWhitelist: options?.linksWhitelist ?? [],
41
+ linksBlacklist: options?.linksBlacklist ?? [],
40
42
  autoSkip: options?.autoSkip ?? true,
43
+ emitNewSongsOnly: options?.emitNewSongsOnly ?? false,
41
44
  queueOptions: {
42
45
  maxPreviousTracks: options?.queueOptions?.maxPreviousTracks ?? 25,
43
46
  queueChangesWatcher: options?.queueOptions?.queueChangesWatcher ?? null,
44
47
  queueStore: options?.queueOptions?.queueStore ?? new Queue_1.DefaultQueueStore(),
45
48
  },
46
- debugOptions: {
47
- noAudio: options?.debugOptions?.noAudio ?? false,
48
- playerDestroy: {
49
- dontThrowError: options?.debugOptions?.playerDestroy?.dontThrowError ?? false,
50
- debugLog: options?.debugOptions?.playerDestroy?.debugLog ?? false,
49
+ advancedOptions: {
50
+ debugOptions: {
51
+ noAudio: options?.advancedOptions?.debugOptions?.noAudio ?? false,
52
+ playerDestroy: {
53
+ dontThrowError: options?.advancedOptions?.debugOptions?.playerDestroy?.dontThrowError ?? false,
54
+ debugLog: options?.advancedOptions?.debugOptions?.playerDestroy?.debugLog ?? false,
55
+ }
51
56
  }
52
57
  }
53
58
  };
@@ -60,6 +65,8 @@ class LavalinkManager extends events_1.EventEmitter {
60
65
  // if(typeof options?.client !== "object" || typeof options?.client.id !== "string") throw new SyntaxError("ManagerOption.client = { id: string, username?:string } was not provided, which is required");
61
66
  if (options?.autoSkip && typeof options?.autoSkip !== "boolean")
62
67
  throw new SyntaxError("ManagerOption.autoSkip must be either false | true aka boolean");
68
+ if (options?.emitNewSongsOnly && typeof options?.emitNewSongsOnly !== "boolean")
69
+ throw new SyntaxError("ManagerOption.emitNewSongsOnly must be either false | true aka boolean");
63
70
  if (!options?.nodes || !Array.isArray(options?.nodes) || !options?.nodes.every(node => this.utils.isNodeOptions(node)))
64
71
  throw new SyntaxError("ManagerOption.nodes must be an Array of NodeOptions and is required of at least 1 Node");
65
72
  /* QUEUE STORE */
@@ -113,7 +120,7 @@ class LavalinkManager extends events_1.EventEmitter {
113
120
  return;
114
121
  // oldPlayer.connected is operational. you could also do oldPlayer.voice?.token
115
122
  if (oldPlayer.voiceChannelId === "string" && oldPlayer.connected && !oldPlayer.get("internal_destroywithoutdisconnect")) {
116
- if (!this.options?.debugOptions?.playerDestroy?.dontThrowError)
123
+ if (!this.options?.advancedOptions?.debugOptions?.playerDestroy?.dontThrowError)
117
124
  throw new Error(`Use Player#destroy() not LavalinkManager#deletePlayer() to stop the Player ${JSON.stringify(oldPlayer.toJSON?.())}`);
118
125
  else
119
126
  console.error("Use Player#destroy() not LavalinkManager#deletePlayer() to stop the Player", oldPlayer.toJSON?.());
@@ -159,12 +166,12 @@ class LavalinkManager extends events_1.EventEmitter {
159
166
  */
160
167
  async sendRawData(data) {
161
168
  if (!this.initiated) {
162
- if (this.options?.debugOptions?.noAudio === true)
169
+ if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
163
170
  console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, manager is not initated yet");
164
171
  return;
165
172
  }
166
173
  if (!("t" in data)) {
167
- if (this.options?.debugOptions?.noAudio === true)
174
+ if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
168
175
  console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, no 't' in payload-data of the raw event:", data);
169
176
  return;
170
177
  }
@@ -181,23 +188,23 @@ class LavalinkManager extends events_1.EventEmitter {
181
188
  if (["VOICE_STATE_UPDATE", "VOICE_SERVER_UPDATE"].includes(data.t)) {
182
189
  const update = ("d" in data ? data.d : data);
183
190
  if (!update) {
184
- if (this.options?.debugOptions?.noAudio === true)
191
+ if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
185
192
  console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, no update data found in payload:", data);
186
193
  return;
187
194
  }
188
195
  if (!("token" in update) && !("session_id" in update)) {
189
- if (this.options?.debugOptions?.noAudio === true)
196
+ if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
190
197
  console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, no 'token' nor 'session_id' found in payload:", data);
191
198
  return;
192
199
  }
193
200
  const player = this.getPlayer(update.guild_id);
194
201
  if (!player) {
195
- if (this.options?.debugOptions?.noAudio === true)
202
+ if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
196
203
  console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, No Lavalink Player found via key: 'guild_id' of update-data:", update);
197
204
  return;
198
205
  }
199
206
  if (player.get("internal_destroystatus") === true) {
200
- if (this.options?.debugOptions?.noAudio === true)
207
+ if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
201
208
  console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Player is in a destroying state. can't signal the voice states");
202
209
  return;
203
210
  }
@@ -214,13 +221,13 @@ class LavalinkManager extends events_1.EventEmitter {
214
221
  }
215
222
  }
216
223
  });
217
- if (this.options?.debugOptions?.noAudio === true)
224
+ if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
218
225
  console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Sent updatePlayer for voice token session", { voice: { token: update.token, endpoint: update.endpoint, sessionId: player.voice?.sessionId, } });
219
226
  return;
220
227
  }
221
228
  /* voice state update */
222
229
  if (update.user_id !== this.options?.client.id) {
223
- if (this.options?.debugOptions?.noAudio === true)
230
+ if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
224
231
  console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, voice update user is not equal to provided client id of the manageroptions#client#id", "user:", update.user_id, "manager client id:", this.options?.client.id);
225
232
  return;
226
233
  }
@@ -2,11 +2,13 @@
2
2
  import internal from "stream";
3
3
  import { Dispatcher, Pool } from "undici";
4
4
  import { NodeManager } from "./NodeManager";
5
- import { DestroyReasonsType } from "./Player";
5
+ import { DestroyReasonsType, Player } from "./Player";
6
6
  import { Track } from "./Track";
7
7
  import { Base64, InvalidLavalinkRestRequest, LavalinkPlayer, LavaSearchQuery, LavaSearchResponse, PlayerUpdateInfo, RoutePlanner, SearchQuery, SearchResult, Session } from "./Utils";
8
8
  /** Modifies any outgoing REST requests. */
9
9
  export type ModifyRequest = (options: Dispatcher.RequestOptions) => void;
10
+ export declare const validSponsorBlocks: string[];
11
+ export type SponsorBlockSegment = "sponsor" | "selfpromo" | "interaction" | "intro" | "outro" | "preview" | "music_offtopic" | "filler";
10
12
  export interface LavalinkNodeOptions {
11
13
  /** The Lavalink Server-Ip / Domain-URL */
12
14
  host: string;
@@ -240,8 +242,15 @@ export declare class LavalinkNode {
240
242
  private handleEvent;
241
243
  private trackStart;
242
244
  private trackEnd;
243
- private queueEnd;
244
245
  private trackStuck;
245
246
  private trackError;
246
247
  private socketClosed;
248
+ private SponsorBlockSegmentLoaded;
249
+ private SponsorBlockSegmentkipped;
250
+ private SponsorBlockChaptersLoaded;
251
+ private SponsorBlockChapterStarted;
252
+ getSponsorBlock(player: Player): Promise<SponsorBlockSegment[]>;
253
+ setSponsorBlock(player: Player, segments?: SponsorBlockSegment[]): Promise<void>;
254
+ deleteSponsorBlock(player: Player): Promise<void>;
255
+ private queueEnd;
247
256
  }
@@ -1,12 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LavalinkNode = void 0;
3
+ exports.LavalinkNode = exports.validSponsorBlocks = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const path_1 = require("path");
6
6
  const undici_1 = require("undici");
7
7
  const ws_1 = tslib_1.__importDefault(require("ws"));
8
8
  const Player_1 = require("./Player");
9
9
  const Utils_1 = require("./Utils");
10
+ exports.validSponsorBlocks = ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "filler"];
10
11
  class LavalinkNode {
11
12
  /** The provided Options of the Node */
12
13
  options;
@@ -98,9 +99,8 @@ class LavalinkNode {
98
99
  }
99
100
  async search(query, requestUser) {
100
101
  const Query = this.NodeManager.LavalinkManager.utils.transformQuery(query);
101
- if (/^https?:\/\//.test(Query.query))
102
- this.NodeManager.LavalinkManager.utils.validateQueryString(this, Query.source);
103
- else if (Query.source)
102
+ this.NodeManager.LavalinkManager.utils.validateQueryString(this, Query.query);
103
+ if (Query.source)
104
104
  this.NodeManager.LavalinkManager.utils.validateSourceString(this, Query.source);
105
105
  if (["bcsearch", "bandcamp"].includes(Query.source)) {
106
106
  throw new Error("Bandcamp Search only works on the player!");
@@ -566,6 +566,7 @@ class LavalinkNode {
566
566
  return;
567
567
  }
568
568
  }
569
+ // LAVALINK EVENT HANDLING UTIL FUNCTION
569
570
  async handleEvent(payload) {
570
571
  if (!payload.guildId)
571
572
  return;
@@ -588,20 +589,36 @@ class LavalinkNode {
588
589
  case "WebSocketClosedEvent":
589
590
  this.socketClosed(player, payload);
590
591
  break;
592
+ case "SegmentsLoaded":
593
+ this.SponsorBlockSegmentLoaded(player, player.queue.current, payload);
594
+ break;
595
+ case "SegmentSkipped":
596
+ this.SponsorBlockSegmentkipped(player, player.queue.current, payload);
597
+ break;
598
+ case "ChaptersLoaded":
599
+ this.SponsorBlockChaptersLoaded(player, player.queue.current, payload);
600
+ break;
601
+ case "ChapterStarted":
602
+ this.SponsorBlockChapterStarted(player, player.queue.current, payload);
603
+ break;
591
604
  default:
592
605
  this.NodeManager.emit("error", this, new Error(`Node#event unknown event '${payload.type}'.`), payload);
593
606
  break;
594
607
  }
595
608
  return;
596
609
  }
610
+ // LAVALINK EVENT HANDLING FUNCTIONS
597
611
  trackStart(player, track, payload) {
598
612
  player.playing = true;
599
613
  player.paused = false;
614
+ // don't emit the event if previous track == new track aka track loop
615
+ if (this.NodeManager.LavalinkManager.options?.emitNewSongsOnly === true && player.queue.previous[0]?.info?.identifier === track?.info?.identifier)
616
+ return;
600
617
  return this.NodeManager.LavalinkManager.emit("trackStart", player, track, payload);
601
618
  }
602
619
  async trackEnd(player, track, payload) {
603
620
  // If there are no songs in the queue
604
- if (!player.queue.tracks.length && player.repeatMode === "off")
621
+ if (!player.queue.tracks.length && (player.repeatMode === "off" || player.get("internal_stopPlaying")))
605
622
  return this.queueEnd(player, track, payload);
606
623
  // If a track was forcibly played
607
624
  if (payload.reason === "replaced")
@@ -620,6 +637,12 @@ class LavalinkNode {
620
637
  // remove tracks from the queue
621
638
  if (player.repeatMode !== "track")
622
639
  await (0, Utils_1.queueTrackEnd)(player);
640
+ else if (player.queue.current) { // If there was a current Track already and repeatmode === true, add it to the queue.
641
+ player.queue.previous.unshift(player.queue.current);
642
+ if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
643
+ player.queue.previous.splice(player.queue.options.maxPreviousTracks, player.queue.previous.length);
644
+ await player.queue.utils.save();
645
+ }
623
646
  // if no track available, end queue
624
647
  if (!player.queue.current)
625
648
  return this.queueEnd(player, track, payload);
@@ -628,11 +651,92 @@ class LavalinkNode {
628
651
  // play track if autoSkip is true
629
652
  return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ noReplace: true });
630
653
  }
654
+ async trackStuck(player, track, payload) {
655
+ this.NodeManager.LavalinkManager.emit("trackStuck", player, track, payload);
656
+ // If there are no songs in the queue
657
+ if (!player.queue.tracks.length && (player.repeatMode === "off" || player.get("internal_stopPlaying")))
658
+ return this.queueEnd(player, track, payload);
659
+ // remove the current track, and enqueue the next one
660
+ await (0, Utils_1.queueTrackEnd)(player);
661
+ // if no track available, end queue
662
+ if (!player.queue.current)
663
+ return this.queueEnd(player, track, payload);
664
+ // play track if autoSkip is true
665
+ return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
666
+ }
667
+ async trackError(player, track, payload) {
668
+ this.NodeManager.LavalinkManager.emit("trackError", player, track, payload);
669
+ // If there are no songs in the queue
670
+ if (!player.queue.tracks.length && (player.repeatMode === "off" || player.get("internal_stopPlaying")))
671
+ return this.queueEnd(player, track, payload);
672
+ // remove the current track, and enqueue the next one
673
+ await (0, Utils_1.queueTrackEnd)(player);
674
+ // if no track available, end queue
675
+ if (!player.queue.current)
676
+ return this.queueEnd(player, track, payload);
677
+ // play track if autoSkip is true
678
+ return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
679
+ }
680
+ socketClosed(player, payload) {
681
+ return this.NodeManager.LavalinkManager.emit("playerSocketClosed", player, payload);
682
+ }
683
+ // SPONSOR BLOCK EVENT FUNCTIONS
684
+ SponsorBlockSegmentLoaded(player, track, payload) {
685
+ return this.NodeManager.LavalinkManager.emit("SegmentsLoaded", player, track, payload);
686
+ }
687
+ SponsorBlockSegmentkipped(player, track, payload) {
688
+ return this.NodeManager.LavalinkManager.emit("SegmentSkipped", player, track, payload);
689
+ }
690
+ SponsorBlockChaptersLoaded(player, track, payload) {
691
+ return this.NodeManager.LavalinkManager.emit("ChaptersLoaded", player, track, payload);
692
+ }
693
+ SponsorBlockChapterStarted(player, track, payload) {
694
+ return this.NodeManager.LavalinkManager.emit("ChapterStarted", player, track, payload);
695
+ }
696
+ // SPONSOR BLOCK EXECUTE FUNCTIONS
697
+ async getSponsorBlock(player) {
698
+ // no plugin enabled
699
+ if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
700
+ throw new RangeError(`there is no sponsorblock-plugin available in the lavalink node: ${this.id}`);
701
+ // do the request
702
+ return await this.request(`/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`);
703
+ }
704
+ async setSponsorBlock(player, segments = ["sponsor", "selfpromo"]) {
705
+ // no plugin enabled
706
+ if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
707
+ throw new RangeError(`there is no sponsorblock-plugin available in the lavalink node: ${this.id}`);
708
+ // no segments length
709
+ if (!segments.length)
710
+ throw new RangeError("No Segments provided. Did you ment to use 'deleteSponsorBlock'?");
711
+ // a not valid segment
712
+ if (segments.some(v => !exports.validSponsorBlocks.includes(v.toLowerCase())))
713
+ throw new SyntaxError(`You provided a sponsorblock which isn't valid, valid ones are: ${exports.validSponsorBlocks.map(v => `'${v}'`).join(", ")}`);
714
+ // do the request
715
+ await this.request(`/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`, (request) => {
716
+ request.method = "PUT";
717
+ request.body = JSON.stringify(segments.map(v => v.toLowerCase()));
718
+ return request;
719
+ });
720
+ return;
721
+ }
722
+ async deleteSponsorBlock(player) {
723
+ // no plugin enabled
724
+ if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
725
+ throw new RangeError(`there is no sponsorblock-plugin available in the lavalink node: ${this.id}`);
726
+ // do the request
727
+ await this.request(`/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`, (request) => {
728
+ request.method = "DELETE";
729
+ return request;
730
+ });
731
+ return;
732
+ }
733
+ // UTIL FOR QUEUE END
631
734
  async queueEnd(player, track, payload) {
632
735
  // add previous track to the queue!
633
736
  player.queue.current = null;
634
737
  player.playing = false;
635
- if (typeof this.NodeManager.LavalinkManager.options?.playerOptions?.onEmptyQueue?.autoPlayFunction === "function") {
738
+ player.set("internal_stopPlaying", undefined);
739
+ if (typeof this.NodeManager.LavalinkManager.options?.playerOptions?.onEmptyQueue?.autoPlayFunction === "function" && typeof player.get("internal_autoplayStopPlaying") === "undefined") {
636
740
  await this.NodeManager.LavalinkManager.options?.playerOptions?.onEmptyQueue?.autoPlayFunction(player, track);
637
741
  if (player.queue.tracks.length > 0)
638
742
  await (0, Utils_1.queueTrackEnd)(player);
@@ -642,6 +746,7 @@ class LavalinkNode {
642
746
  return player.play({ noReplace: true, paused: false });
643
747
  }
644
748
  }
749
+ player.set("internal_autoplayStopPlaying", undefined);
645
750
  player.queue.previous.unshift(track);
646
751
  if (payload?.reason !== "stopped") {
647
752
  await player.queue.utils.save();
@@ -661,31 +766,5 @@ class LavalinkNode {
661
766
  }
662
767
  return this.NodeManager.LavalinkManager.emit("queueEnd", player, track, payload);
663
768
  }
664
- async trackStuck(player, track, payload) {
665
- this.NodeManager.LavalinkManager.emit("trackStuck", player, track, payload);
666
- // If there are no songs in the queue
667
- if (!player.queue.tracks.length && player.repeatMode === "off")
668
- return;
669
- // remove the current track, and enqueue the next one
670
- await (0, Utils_1.queueTrackEnd)(player);
671
- // if no track available, end queue
672
- if (!player.queue.current)
673
- return this.queueEnd(player, track, payload);
674
- // play track if autoSkip is true
675
- return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
676
- }
677
- async trackError(player, track, payload) {
678
- this.NodeManager.LavalinkManager.emit("trackError", player, track, payload);
679
- // remove the current track, and enqueue the next one
680
- await (0, Utils_1.queueTrackEnd)(player);
681
- // if no track available, end queue
682
- if (!player.queue.current)
683
- return this.queueEnd(player, track, payload);
684
- // play track if autoSkip is true
685
- return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
686
- }
687
- socketClosed(player, payload) {
688
- return this.NodeManager.LavalinkManager.emit("playerSocketClosed", player, payload);
689
- }
690
769
  }
691
770
  exports.LavalinkNode = LavalinkNode;
@@ -1,6 +1,6 @@
1
1
  import { EQBand, FilterData, FilterManager, LavalinkFilterData } from "./Filters";
2
2
  import { LavalinkManager } from "./LavalinkManager";
3
- import { LavalinkNode } from "./Node";
3
+ import { LavalinkNode, SponsorBlockSegment } from "./Node";
4
4
  import { Queue } from "./Queue";
5
5
  import { Track, UnresolvedTrack } from "./Track";
6
6
  import { LavalinkPlayerVoiceOptions, LavaSearchQuery, SearchQuery } from "./Utils";
@@ -149,6 +149,9 @@ export declare class Player {
149
149
  */
150
150
  setVolume(volume: number, ignoreVolumeDecrementer?: boolean): Promise<this>;
151
151
  lavaSearch(query: LavaSearchQuery, requestUser: unknown): Promise<import("./Utils").SearchResult | import("./Utils").LavaSearchResponse>;
152
+ setSponsorBlock(segments?: SponsorBlockSegment[]): Promise<void>;
153
+ getSponsorBlock(): Promise<SponsorBlockSegment[]>;
154
+ deleteSponsorBlock(): Promise<void>;
152
155
  /**
153
156
  *
154
157
  * @param query Query for your data
@@ -177,12 +180,22 @@ export declare class Player {
177
180
  * Skip the current song, or a specific amount of songs
178
181
  * @param amount provide the index of the next track to skip to
179
182
  */
180
- skip(skipTo?: number): Promise<any>;
183
+ skip(skipTo?: number, throwError?: boolean): Promise<any>;
184
+ /**
185
+ * Clears the queue and stops playing. Does not destroy the Player and not leave the channel
186
+ * @returns
187
+ */
188
+ stopPlaying(clearQueue?: boolean, executeAutoplay?: boolean): Promise<this>;
181
189
  /**
182
190
  * Connects the Player to the Voice Channel
183
191
  * @returns
184
192
  */
185
193
  connect(): Promise<this>;
194
+ changeVoiceState(data: {
195
+ voiceChannelId?: string;
196
+ selfDeaf?: boolean;
197
+ selfMute?: boolean;
198
+ }): Promise<this>;
186
199
  /**
187
200
  * Disconnects the Player from the Voice Channel, but keeps the player in the cache
188
201
  * @param force If false it throws an error, if player thinks it's already disconnected