lavalink-client 1.1.24 → 1.2.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 +26 -1
- package/dist/cjs/structures/CustomSearches/BandCampSearch.js +1 -0
- package/dist/cjs/structures/LavalinkManager.d.ts +54 -14
- package/dist/cjs/structures/LavalinkManager.js +32 -18
- package/dist/cjs/structures/Node.d.ts +10 -1
- package/dist/cjs/structures/Node.js +75 -6
- package/dist/cjs/structures/Player.d.ts +16 -3
- package/dist/cjs/structures/Player.js +56 -14
- package/dist/cjs/structures/Queue.d.ts +1 -1
- package/dist/cjs/structures/Utils.d.ts +45 -2
- package/dist/cjs/structures/Utils.js +13 -0
- package/dist/esm/structures/CustomSearches/BandCampSearch.js +1 -0
- package/dist/esm/structures/LavalinkManager.d.ts +54 -14
- package/dist/esm/structures/LavalinkManager.js +32 -18
- package/dist/esm/structures/Node.d.ts +10 -1
- package/dist/esm/structures/Node.js +74 -5
- package/dist/esm/structures/Player.d.ts +16 -3
- package/dist/esm/structures/Player.js +56 -14
- package/dist/esm/structures/Queue.d.ts +1 -1
- package/dist/esm/structures/Utils.d.ts +45 -2
- package/dist/esm/structures/Utils.js +13 -0
- package/dist/types/structures/LavalinkManager.d.ts +54 -14
- package/dist/types/structures/Node.d.ts +10 -1
- package/dist/types/structures/Player.d.ts +16 -3
- package/dist/types/structures/Queue.d.ts +1 -1
- package/dist/types/structures/Utils.d.ts +45 -2
- package/package.json +1 -1
|
@@ -152,7 +152,7 @@ export declare class MiniMap<K, V> extends Map<K, V> {
|
|
|
152
152
|
map<T>(fn: (value: V, key: K, miniMap: this) => T): T[];
|
|
153
153
|
map<This, T>(fn: (this: This, value: V, key: K, miniMap: this) => T, thisArg: This): T[];
|
|
154
154
|
}
|
|
155
|
-
export type PlayerEvents = TrackStartEvent | TrackEndEvent | TrackStuckEvent | TrackExceptionEvent | WebSocketClosedEvent;
|
|
155
|
+
export type PlayerEvents = TrackStartEvent | TrackEndEvent | TrackStuckEvent | TrackExceptionEvent | WebSocketClosedEvent | SponsorBlockSegmentEvents;
|
|
156
156
|
export type Severity = "COMMON" | "SUSPICIOUS" | "FAULT";
|
|
157
157
|
export interface Exception {
|
|
158
158
|
severity: Severity;
|
|
@@ -188,9 +188,52 @@ export interface WebSocketClosedEvent extends PlayerEvent {
|
|
|
188
188
|
byRemote: boolean;
|
|
189
189
|
reason: string;
|
|
190
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Types & Events for Sponsorblock-plugin from Lavalink: https://github.com/topi314/Sponsorblock-Plugin#segmentsloaded
|
|
193
|
+
*/
|
|
194
|
+
export type SponsorBlockSegmentEvents = SponsorBlockSegmentSkipped | SponsorBlockSegmentsLoaded | SponsorBlockChapterStarted | SponsorBlockChaptersLoaded;
|
|
195
|
+
export type SponsorBlockSegmentEventType = "SegmentSkipped" | "SegmentsLoaded" | "ChaptersLoaded" | "ChapterStarted";
|
|
196
|
+
export interface SponsorBlockSegmentsLoaded extends PlayerEvent {
|
|
197
|
+
type: "SegmentsLoaded";
|
|
198
|
+
segments: {
|
|
199
|
+
category: string;
|
|
200
|
+
start: number;
|
|
201
|
+
end: number;
|
|
202
|
+
}[];
|
|
203
|
+
}
|
|
204
|
+
export interface SponsorBlockSegmentSkipped extends PlayerEvent {
|
|
205
|
+
type: "SegmentSkipped";
|
|
206
|
+
segment: {
|
|
207
|
+
category: string;
|
|
208
|
+
start: number;
|
|
209
|
+
end: number;
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
export interface SponsorBlockChapterStarted extends PlayerEvent {
|
|
213
|
+
type: "ChapterStarted";
|
|
214
|
+
/** The Chapter which started */
|
|
215
|
+
chapter: {
|
|
216
|
+
/** The Name of the Chapter */
|
|
217
|
+
name: string;
|
|
218
|
+
start: number;
|
|
219
|
+
end: number;
|
|
220
|
+
duration: number;
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
export interface SponsorBlockChaptersLoaded extends PlayerEvent {
|
|
224
|
+
type: "ChaptersLoaded";
|
|
225
|
+
/** All Chapters loaded */
|
|
226
|
+
chapters: {
|
|
227
|
+
/** The Name of the Chapter */
|
|
228
|
+
name: string;
|
|
229
|
+
start: number;
|
|
230
|
+
end: number;
|
|
231
|
+
duration: number;
|
|
232
|
+
}[];
|
|
233
|
+
}
|
|
191
234
|
export type LoadTypes = "track" | "playlist" | "search" | "error" | "empty";
|
|
192
235
|
export type State = "CONNECTED" | "CONNECTING" | "DISCONNECTED" | "DISCONNECTING" | "DESTROYING";
|
|
193
|
-
export type PlayerEventType = "TrackStartEvent" | "TrackEndEvent" | "TrackExceptionEvent" | "TrackStuckEvent" | "WebSocketClosedEvent";
|
|
236
|
+
export type PlayerEventType = "TrackStartEvent" | "TrackEndEvent" | "TrackExceptionEvent" | "TrackStuckEvent" | "WebSocketClosedEvent" | SponsorBlockSegmentEventType;
|
|
194
237
|
export type TrackEndReason = "finished" | "loadFailed" | "stopped" | "replaced" | "cleanup";
|
|
195
238
|
export interface InvalidLavalinkRestRequest {
|
|
196
239
|
timestamp: number;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.queueTrackEnd = exports.MiniMap = exports.ManagerUtils = exports.NodeSymbol = exports.QueueSymbol = exports.UnresolvedTrackSymbol = exports.TrackSymbol = void 0;
|
|
4
|
+
const types_1 = require("util/types");
|
|
4
5
|
const LavalinkManagerStatics_1 = require("./LavalinkManagerStatics");
|
|
5
6
|
exports.TrackSymbol = Symbol("LC-Track");
|
|
6
7
|
exports.UnresolvedTrackSymbol = Symbol("LC-Track-Unresolved");
|
|
@@ -166,6 +167,18 @@ class ManagerUtils {
|
|
|
166
167
|
throw new Error("No Lavalink Node was provided");
|
|
167
168
|
if (!node.info.sourceManagers?.length)
|
|
168
169
|
throw new Error("Lavalink Node, has no sourceManagers enabled");
|
|
170
|
+
// checks for blacklisted links / domains / queries
|
|
171
|
+
if (this.LavalinkManager.options?.linksBlacklist?.length > 0 && this.LavalinkManager.options?.linksBlacklist.some(v => (typeof v === "string" && (queryString.toLowerCase().includes(v.toLowerCase()) || v.toLowerCase().includes(queryString.toLowerCase()))) || (0, types_1.isRegExp)(v) && v.test(queryString))) {
|
|
172
|
+
throw new Error(`Query string contains a link / word which is blacklisted.`);
|
|
173
|
+
}
|
|
174
|
+
if (!/^https?:\/\//.test(queryString))
|
|
175
|
+
return;
|
|
176
|
+
else if (this.LavalinkManager.options?.linksAllowed === false)
|
|
177
|
+
throw new Error("Using links to make a request is not allowed.");
|
|
178
|
+
// checks for if the query is whitelisted (should only work for links, so it skips the check for no link queries)
|
|
179
|
+
if (this.LavalinkManager.options?.linksWhitelist?.length > 0 && !this.LavalinkManager.options?.linksWhitelist.some(v => (typeof v === "string" && (queryString.toLowerCase().includes(v.toLowerCase()) || v.toLowerCase().includes(queryString.toLowerCase()))) || (0, types_1.isRegExp)(v) && v.test(queryString))) {
|
|
180
|
+
throw new Error(`Query string contains a link / word which isn't whitelisted.`);
|
|
181
|
+
}
|
|
169
182
|
// missing links: beam.pro local getyarn.io clypit pornhub reddit ocreamix soundgasm
|
|
170
183
|
if ((LavalinkManagerStatics_1.SourceLinksRegexes.YoutubeMusicRegex.test(queryString) || LavalinkManagerStatics_1.SourceLinksRegexes.YoutubeRegex.test(queryString)) && !node.info?.sourceManagers?.includes("youtube")) {
|
|
171
184
|
throw new Error("Lavalink Node has not 'youtube' enabled");
|
|
@@ -2,6 +2,7 @@ import { fetch } from "undici";
|
|
|
2
2
|
export const bandCampSearch = async (player, query, requestUser) => {
|
|
3
3
|
let error = null;
|
|
4
4
|
let tracks = [];
|
|
5
|
+
player.LavalinkManager.utils.validateQueryString(player.node, query);
|
|
5
6
|
try {
|
|
6
7
|
const data = await fetch(`https://bandcamp.com/api/nusearch/2/autocomplete?q=${encodeURIComponent(query)}`, {
|
|
7
8
|
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
|
-
/**
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
}
|
|
@@ -102,7 +113,7 @@ interface LavalinkManagerEvents {
|
|
|
102
113
|
"playerCreate": (player: Player) => void;
|
|
103
114
|
/**
|
|
104
115
|
* Emitted when a Player is moved within the channel.
|
|
105
|
-
* @event Manager
|
|
116
|
+
* @event Manager#playerMove
|
|
106
117
|
*/
|
|
107
118
|
"playerMove": (player: Player, oldVoiceChannelId: string, newVoiceChannelId: string) => void;
|
|
108
119
|
/**
|
|
@@ -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;
|
|
@@ -141,7 +180,8 @@ export declare class LavalinkManager extends EventEmitter {
|
|
|
141
180
|
constructor(options: ManagerOptions);
|
|
142
181
|
createPlayer(options: PlayerOptions): Player;
|
|
143
182
|
getPlayer(guildId: string): Player;
|
|
144
|
-
|
|
183
|
+
destroyPlayer(guildId: string, destroyReason?: string): Promise<Player>;
|
|
184
|
+
deletePlayer(guildId: string): boolean;
|
|
145
185
|
get useable(): boolean;
|
|
146
186
|
/**
|
|
147
187
|
* Initiates the Manager.
|
|
@@ -34,17 +34,22 @@ export class LavalinkManager extends EventEmitter {
|
|
|
34
34
|
requesterTransformer: options?.playerOptions?.requesterTransformer ?? null,
|
|
35
35
|
useUnresolvedData: options?.playerOptions?.useUnresolvedData ?? false,
|
|
36
36
|
},
|
|
37
|
+
linksWhitelist: options?.linksWhitelist ?? [],
|
|
38
|
+
linksBlacklist: options?.linksBlacklist ?? [],
|
|
37
39
|
autoSkip: options?.autoSkip ?? true,
|
|
40
|
+
emitNewSongsOnly: options?.emitNewSongsOnly ?? false,
|
|
38
41
|
queueOptions: {
|
|
39
42
|
maxPreviousTracks: options?.queueOptions?.maxPreviousTracks ?? 25,
|
|
40
43
|
queueChangesWatcher: options?.queueOptions?.queueChangesWatcher ?? null,
|
|
41
44
|
queueStore: options?.queueOptions?.queueStore ?? new DefaultQueueStore(),
|
|
42
45
|
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
advancedOptions: {
|
|
47
|
+
debugOptions: {
|
|
48
|
+
noAudio: options?.advancedOptions?.debugOptions?.noAudio ?? false,
|
|
49
|
+
playerDestroy: {
|
|
50
|
+
dontThrowError: options?.advancedOptions?.debugOptions?.playerDestroy?.dontThrowError ?? false,
|
|
51
|
+
debugLog: options?.advancedOptions?.debugOptions?.playerDestroy?.debugLog ?? false,
|
|
52
|
+
}
|
|
48
53
|
}
|
|
49
54
|
}
|
|
50
55
|
};
|
|
@@ -57,6 +62,8 @@ export class LavalinkManager extends EventEmitter {
|
|
|
57
62
|
// 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");
|
|
58
63
|
if (options?.autoSkip && typeof options?.autoSkip !== "boolean")
|
|
59
64
|
throw new SyntaxError("ManagerOption.autoSkip must be either false | true aka boolean");
|
|
65
|
+
if (options?.emitNewSongsOnly && typeof options?.emitNewSongsOnly !== "boolean")
|
|
66
|
+
throw new SyntaxError("ManagerOption.emitNewSongsOnly must be either false | true aka boolean");
|
|
60
67
|
if (!options?.nodes || !Array.isArray(options?.nodes) || !options?.nodes.every(node => this.utils.isNodeOptions(node)))
|
|
61
68
|
throw new SyntaxError("ManagerOption.nodes must be an Array of NodeOptions and is required of at least 1 Node");
|
|
62
69
|
/* QUEUE STORE */
|
|
@@ -98,15 +105,22 @@ export class LavalinkManager extends EventEmitter {
|
|
|
98
105
|
getPlayer(guildId) {
|
|
99
106
|
return this.players.get(guildId);
|
|
100
107
|
}
|
|
101
|
-
|
|
108
|
+
destroyPlayer(guildId, destroyReason) {
|
|
109
|
+
const oldPlayer = this.getPlayer(guildId);
|
|
110
|
+
if (!oldPlayer)
|
|
111
|
+
return;
|
|
112
|
+
return oldPlayer.destroy(destroyReason);
|
|
113
|
+
}
|
|
114
|
+
deletePlayer(guildId) {
|
|
102
115
|
const oldPlayer = this.getPlayer(guildId);
|
|
103
116
|
if (!oldPlayer)
|
|
104
117
|
return;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
// oldPlayer.connected is operational. you could also do oldPlayer.voice?.token
|
|
119
|
+
if (oldPlayer.voiceChannelId === "string" && oldPlayer.connected && !oldPlayer.get("internal_destroywithoutdisconnect")) {
|
|
120
|
+
if (!this.options?.advancedOptions?.debugOptions?.playerDestroy?.dontThrowError)
|
|
121
|
+
throw new Error(`Use Player#destroy() not LavalinkManager#deletePlayer() to stop the Player ${JSON.stringify(oldPlayer.toJSON?.())}`);
|
|
108
122
|
else
|
|
109
|
-
console.error("Use Player#destroy(
|
|
123
|
+
console.error("Use Player#destroy() not LavalinkManager#deletePlayer() to stop the Player", oldPlayer.toJSON?.());
|
|
110
124
|
}
|
|
111
125
|
return this.players.delete(guildId);
|
|
112
126
|
}
|
|
@@ -149,12 +163,12 @@ export class LavalinkManager extends EventEmitter {
|
|
|
149
163
|
*/
|
|
150
164
|
async sendRawData(data) {
|
|
151
165
|
if (!this.initiated) {
|
|
152
|
-
if (this.options?.debugOptions?.noAudio === true)
|
|
166
|
+
if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
|
|
153
167
|
console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, manager is not initated yet");
|
|
154
168
|
return;
|
|
155
169
|
}
|
|
156
170
|
if (!("t" in data)) {
|
|
157
|
-
if (this.options?.debugOptions?.noAudio === true)
|
|
171
|
+
if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
|
|
158
172
|
console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, no 't' in payload-data of the raw event:", data);
|
|
159
173
|
return;
|
|
160
174
|
}
|
|
@@ -171,23 +185,23 @@ export class LavalinkManager extends EventEmitter {
|
|
|
171
185
|
if (["VOICE_STATE_UPDATE", "VOICE_SERVER_UPDATE"].includes(data.t)) {
|
|
172
186
|
const update = ("d" in data ? data.d : data);
|
|
173
187
|
if (!update) {
|
|
174
|
-
if (this.options?.debugOptions?.noAudio === true)
|
|
188
|
+
if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
|
|
175
189
|
console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, no update data found in payload:", data);
|
|
176
190
|
return;
|
|
177
191
|
}
|
|
178
192
|
if (!("token" in update) && !("session_id" in update)) {
|
|
179
|
-
if (this.options?.debugOptions?.noAudio === true)
|
|
193
|
+
if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
|
|
180
194
|
console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, no 'token' nor 'session_id' found in payload:", data);
|
|
181
195
|
return;
|
|
182
196
|
}
|
|
183
197
|
const player = this.getPlayer(update.guild_id);
|
|
184
198
|
if (!player) {
|
|
185
|
-
if (this.options?.debugOptions?.noAudio === true)
|
|
199
|
+
if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
|
|
186
200
|
console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, No Lavalink Player found via key: 'guild_id' of update-data:", update);
|
|
187
201
|
return;
|
|
188
202
|
}
|
|
189
203
|
if (player.get("internal_destroystatus") === true) {
|
|
190
|
-
if (this.options?.debugOptions?.noAudio === true)
|
|
204
|
+
if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
|
|
191
205
|
console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Player is in a destroying state. can't signal the voice states");
|
|
192
206
|
return;
|
|
193
207
|
}
|
|
@@ -204,13 +218,13 @@ export class LavalinkManager extends EventEmitter {
|
|
|
204
218
|
}
|
|
205
219
|
}
|
|
206
220
|
});
|
|
207
|
-
if (this.options?.debugOptions?.noAudio === true)
|
|
221
|
+
if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
|
|
208
222
|
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, } });
|
|
209
223
|
return;
|
|
210
224
|
}
|
|
211
225
|
/* voice state update */
|
|
212
226
|
if (update.user_id !== this.options?.client.id) {
|
|
213
|
-
if (this.options?.debugOptions?.noAudio === true)
|
|
227
|
+
if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
|
|
214
228
|
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);
|
|
215
229
|
return;
|
|
216
230
|
}
|
|
@@ -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;
|
|
@@ -238,8 +240,15 @@ export declare class LavalinkNode {
|
|
|
238
240
|
private error;
|
|
239
241
|
private message;
|
|
240
242
|
private handleEvent;
|
|
243
|
+
private SponsorBlockSegmentLoaded;
|
|
244
|
+
private SponsorBlockSegmentkipped;
|
|
245
|
+
private SponsorBlockChaptersLoaded;
|
|
246
|
+
private SponsorBlockChapterStarted;
|
|
241
247
|
private trackStart;
|
|
242
248
|
private trackEnd;
|
|
249
|
+
getSponsorBlock(player: Player): Promise<SponsorBlockSegment[]>;
|
|
250
|
+
setSponsorBlock(player: Player, segments?: SponsorBlockSegment[]): Promise<void>;
|
|
251
|
+
deleteSponsorBlock(player: Player): Promise<void>;
|
|
243
252
|
private queueEnd;
|
|
244
253
|
private trackStuck;
|
|
245
254
|
private trackError;
|
|
@@ -3,6 +3,7 @@ import { Pool } from "undici";
|
|
|
3
3
|
import WebSocket from "ws";
|
|
4
4
|
import { DestroyReasons } from "./Player";
|
|
5
5
|
import { NodeSymbol, queueTrackEnd } from "./Utils";
|
|
6
|
+
export const validSponsorBlocks = ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "filler"];
|
|
6
7
|
export class LavalinkNode {
|
|
7
8
|
/** The provided Options of the Node */
|
|
8
9
|
options;
|
|
@@ -83,7 +84,7 @@ export class LavalinkNode {
|
|
|
83
84
|
modify?.(options);
|
|
84
85
|
const url = new URL(`${this.poolAddress}${options.path}`);
|
|
85
86
|
url.searchParams.append("trace", "true");
|
|
86
|
-
options.path = url.
|
|
87
|
+
options.path = url.pathname + url.search;
|
|
87
88
|
const request = await this.rest.request(options);
|
|
88
89
|
this.calls++;
|
|
89
90
|
if (options.method === "DELETE")
|
|
@@ -94,9 +95,8 @@ export class LavalinkNode {
|
|
|
94
95
|
}
|
|
95
96
|
async search(query, requestUser) {
|
|
96
97
|
const Query = this.NodeManager.LavalinkManager.utils.transformQuery(query);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
else if (Query.source)
|
|
98
|
+
this.NodeManager.LavalinkManager.utils.validateQueryString(this, Query.query);
|
|
99
|
+
if (Query.source)
|
|
100
100
|
this.NodeManager.LavalinkManager.utils.validateSourceString(this, Query.source);
|
|
101
101
|
if (["bcsearch", "bandcamp"].includes(Query.source)) {
|
|
102
102
|
throw new Error("Bandcamp Search only works on the player!");
|
|
@@ -167,7 +167,7 @@ export class LavalinkNode {
|
|
|
167
167
|
if (data.noReplace) {
|
|
168
168
|
const url = new URL(`${this.poolAddress}${r.path}`);
|
|
169
169
|
url.searchParams.append("noReplace", data.noReplace?.toString() || "false");
|
|
170
|
-
r.path = url.
|
|
170
|
+
r.path = url.pathname + url.search;
|
|
171
171
|
}
|
|
172
172
|
});
|
|
173
173
|
return this.syncPlayerData({}, res), res;
|
|
@@ -584,15 +584,42 @@ export class LavalinkNode {
|
|
|
584
584
|
case "WebSocketClosedEvent":
|
|
585
585
|
this.socketClosed(player, payload);
|
|
586
586
|
break;
|
|
587
|
+
case "SegmentsLoaded":
|
|
588
|
+
this.SponsorBlockSegmentLoaded(player, player.queue.current, payload);
|
|
589
|
+
break;
|
|
590
|
+
case "SegmentSkipped":
|
|
591
|
+
this.SponsorBlockSegmentkipped(player, player.queue.current, payload);
|
|
592
|
+
break;
|
|
593
|
+
case "ChaptersLoaded":
|
|
594
|
+
this.SponsorBlockChaptersLoaded(player, player.queue.current, payload);
|
|
595
|
+
break;
|
|
596
|
+
case "ChapterStarted":
|
|
597
|
+
this.SponsorBlockChapterStarted(player, player.queue.current, payload);
|
|
598
|
+
break;
|
|
587
599
|
default:
|
|
588
600
|
this.NodeManager.emit("error", this, new Error(`Node#event unknown event '${payload.type}'.`), payload);
|
|
589
601
|
break;
|
|
590
602
|
}
|
|
591
603
|
return;
|
|
592
604
|
}
|
|
605
|
+
SponsorBlockSegmentLoaded(player, track, payload) {
|
|
606
|
+
return this.NodeManager.LavalinkManager.emit("SegmentsLoaded", player, track, payload);
|
|
607
|
+
}
|
|
608
|
+
SponsorBlockSegmentkipped(player, track, payload) {
|
|
609
|
+
return this.NodeManager.LavalinkManager.emit("SegmentSkipped", player, track, payload);
|
|
610
|
+
}
|
|
611
|
+
SponsorBlockChaptersLoaded(player, track, payload) {
|
|
612
|
+
return this.NodeManager.LavalinkManager.emit("ChaptersLoaded", player, track, payload);
|
|
613
|
+
}
|
|
614
|
+
SponsorBlockChapterStarted(player, track, payload) {
|
|
615
|
+
return this.NodeManager.LavalinkManager.emit("ChapterStarted", player, track, payload);
|
|
616
|
+
}
|
|
593
617
|
trackStart(player, track, payload) {
|
|
594
618
|
player.playing = true;
|
|
595
619
|
player.paused = false;
|
|
620
|
+
// don't emit the event if previous track == new track aka track loop
|
|
621
|
+
if (this.NodeManager.LavalinkManager.options?.emitNewSongsOnly === true && player.queue.previous[0]?.info?.identifier === track?.info?.identifier)
|
|
622
|
+
return;
|
|
596
623
|
return this.NodeManager.LavalinkManager.emit("trackStart", player, track, payload);
|
|
597
624
|
}
|
|
598
625
|
async trackEnd(player, track, payload) {
|
|
@@ -616,6 +643,12 @@ export class LavalinkNode {
|
|
|
616
643
|
// remove tracks from the queue
|
|
617
644
|
if (player.repeatMode !== "track")
|
|
618
645
|
await queueTrackEnd(player);
|
|
646
|
+
else if (player.queue.current) { // If there was a current Track already and repeatmode === true, add it to the queue.
|
|
647
|
+
player.queue.previous.unshift(player.queue.current);
|
|
648
|
+
if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
|
|
649
|
+
player.queue.previous.splice(player.queue.options.maxPreviousTracks, player.queue.previous.length);
|
|
650
|
+
await player.queue.utils.save();
|
|
651
|
+
}
|
|
619
652
|
// if no track available, end queue
|
|
620
653
|
if (!player.queue.current)
|
|
621
654
|
return this.queueEnd(player, track, payload);
|
|
@@ -624,6 +657,42 @@ export class LavalinkNode {
|
|
|
624
657
|
// play track if autoSkip is true
|
|
625
658
|
return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ noReplace: true });
|
|
626
659
|
}
|
|
660
|
+
async getSponsorBlock(player) {
|
|
661
|
+
// no plugin enabled
|
|
662
|
+
if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
|
|
663
|
+
throw new RangeError(`there is no sponsorblock-plugin available in the lavalink node: ${this.id}`);
|
|
664
|
+
// do the request
|
|
665
|
+
return await this.request(`/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`);
|
|
666
|
+
}
|
|
667
|
+
async setSponsorBlock(player, segments = ["sponsor", "selfpromo"]) {
|
|
668
|
+
// no plugin enabled
|
|
669
|
+
if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
|
|
670
|
+
throw new RangeError(`there is no sponsorblock-plugin available in the lavalink node: ${this.id}`);
|
|
671
|
+
// no segments length
|
|
672
|
+
if (!segments.length)
|
|
673
|
+
throw new RangeError("No Segments provided. Did you ment to use 'deleteSponsorBlock'?");
|
|
674
|
+
// a not valid segment
|
|
675
|
+
if (segments.some(v => !validSponsorBlocks.includes(v.toLowerCase())))
|
|
676
|
+
throw new SyntaxError(`You provided a sponsorblock which isn't valid, valid ones are: ${validSponsorBlocks.map(v => `'${v}'`).join(", ")}`);
|
|
677
|
+
// do the request
|
|
678
|
+
await this.request(`/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`, (request) => {
|
|
679
|
+
request.method = "PUT";
|
|
680
|
+
request.body = JSON.stringify(segments.map(v => v.toLowerCase()));
|
|
681
|
+
return request;
|
|
682
|
+
});
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
async deleteSponsorBlock(player) {
|
|
686
|
+
// no plugin enabled
|
|
687
|
+
if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
|
|
688
|
+
throw new RangeError(`there is no sponsorblock-plugin available in the lavalink node: ${this.id}`);
|
|
689
|
+
// do the request
|
|
690
|
+
await this.request(`/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`, (request) => {
|
|
691
|
+
request.method = "DELETE";
|
|
692
|
+
return request;
|
|
693
|
+
});
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
627
696
|
async queueEnd(player, track, payload) {
|
|
628
697
|
// add previous track to the queue!
|
|
629
698
|
player.queue.current = null;
|
|
@@ -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(): 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
|
|
@@ -192,7 +205,7 @@ export declare class Player {
|
|
|
192
205
|
/**
|
|
193
206
|
* Destroy the player and disconnect from the voice channel
|
|
194
207
|
*/
|
|
195
|
-
destroy(reason?: string): Promise<this>;
|
|
208
|
+
destroy(reason?: string, disconnect?: boolean): Promise<this>;
|
|
196
209
|
/**
|
|
197
210
|
* Move the player on a different Audio-Node
|
|
198
211
|
* @param newNode New Node / New Node Id
|