lavalink-client 1.2.5 → 2.0.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 +51 -0
- package/dist/cjs/structures/LavalinkManager.d.ts +2 -0
- package/dist/cjs/structures/LavalinkManager.js +3 -0
- package/dist/cjs/structures/Node.d.ts +42 -4
- package/dist/cjs/structures/Node.js +42 -14
- package/dist/cjs/structures/NodeManager.d.ts +12 -2
- package/dist/cjs/structures/NodeManager.js +1 -1
- package/dist/cjs/structures/Player.d.ts +25 -10
- package/dist/cjs/structures/Player.js +51 -23
- package/dist/cjs/structures/Track.d.ts +7 -1
- package/dist/cjs/structures/Utils.d.ts +75 -19
- package/dist/esm/structures/LavalinkManager.d.ts +2 -0
- package/dist/esm/structures/LavalinkManager.js +3 -0
- package/dist/esm/structures/Node.d.ts +42 -4
- package/dist/esm/structures/Node.js +42 -14
- package/dist/esm/structures/NodeManager.d.ts +12 -2
- package/dist/esm/structures/NodeManager.js +1 -1
- package/dist/esm/structures/Player.d.ts +25 -10
- package/dist/esm/structures/Player.js +51 -23
- package/dist/esm/structures/Track.d.ts +7 -1
- package/dist/esm/structures/Utils.d.ts +75 -19
- package/dist/types/structures/LavalinkManager.d.ts +2 -0
- package/dist/types/structures/Node.d.ts +42 -4
- package/dist/types/structures/NodeManager.d.ts +12 -2
- package/dist/types/structures/Player.d.ts +25 -10
- package/dist/types/structures/Track.d.ts +7 -1
- package/dist/types/structures/Utils.d.ts +75 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -130,3 +130,54 @@ Check out the [Documentation](https://lc4.gitbook.io/lavalink-client) for **Exam
|
|
|
130
130
|
- Example: `parseLavalinkConnUrl("lavalink://LavalinkNode_1:strong%23password1@localhost:2345")` will give you:
|
|
131
131
|
`{ id: "LavalinkNode_1", authorization: "strong#password1", host: "localhost", port: 2345 }`
|
|
132
132
|
- Note that the password "strong#password1" when encoded turns into "strong%23password1". For more information check the example bot
|
|
133
|
+
|
|
134
|
+
### **Version 2.0.0**
|
|
135
|
+
- Lavalink v4 released, adjusted all features from the stable release, to support it in this client!
|
|
136
|
+
```diff
|
|
137
|
+
|
|
138
|
+
# How to load tracks / stop playing has changed for the node.updatePlayer rest endpoint the Client handles it automatically
|
|
139
|
+
- await player.node.updatePlayer({ encodedTrack?: Base64|null, track?: Track|UnresolvedTrack, identifer?: string });
|
|
140
|
+
+ await player.node.updatePlayer({ track: { encoded?: Base64|null, identifier?: string }, clientTrack?: Track|UnresolvedTrack });
|
|
141
|
+
|
|
142
|
+
# To satisfy the changes from lavalink updatePlayer endpoint, player play also got adjusted for that (Most users won't need this feature!)
|
|
143
|
+
- await player.play({ encodedTrack?: Base64|null, track?: Track|UnresolvedTrack, identifer?: string });
|
|
144
|
+
+ await player.play({ track: { encoded?: Base64|null, identifier?: string }, clientTrack?: Track|UnresolvedTrack });
|
|
145
|
+
# However it' still recommended to do it like that:
|
|
146
|
+
# first add tracks to the queue
|
|
147
|
+
+ await player.queue.add(Track: Track|UnresolvedTrack|(Track|UnresolvedTrack)[]);
|
|
148
|
+
# then play the next track from the queue
|
|
149
|
+
+ await player.play();
|
|
150
|
+
|
|
151
|
+
# Node Resuming got supported
|
|
152
|
+
# First enable it by doing:
|
|
153
|
+
+ await player.node.updateResuming(true, 360_000);
|
|
154
|
+
# then when reconnecting to the node add to the node.createeOptions the sessionId: "" of the previous session
|
|
155
|
+
# and after connecting the nodeManager.on("resumed", (node, payload, players) => {}) will be executed, where you can sync the players!
|
|
156
|
+
|
|
157
|
+
# Node Options got adjusted # It's a property not a method should be treated readonly
|
|
158
|
+
+ node.resuming: { enabled: boolean, timeout: number | null };
|
|
159
|
+
|
|
160
|
+
# Player function got added to stop playing without disconnecting
|
|
161
|
+
+ player.stopPlaying(clearQueue:boolean = true, executeAutoplay:boolean = false);
|
|
162
|
+
|
|
163
|
+
# Node functions for sponsorBlock Plugin (https://github.com/topi314/Sponsorblock-Plugin) got added
|
|
164
|
+
+ deleteSponsorBlock(player:Player)
|
|
165
|
+
+ setSponsorBlock(player:Player, segments: ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "filler"])
|
|
166
|
+
# only works if you ever set the sponsor blocks once before
|
|
167
|
+
+ getSponsorBlock(player:Player)
|
|
168
|
+
# Corresponding nodeManager events got added:
|
|
169
|
+
+ nodeManager.on("ChapterStarted");
|
|
170
|
+
+ nodeManager.on("ChaptersLoaded");
|
|
171
|
+
+ nodeManager.on("SegmentsLoaded");
|
|
172
|
+
+ nodeManager.on("SegmentSkipped");
|
|
173
|
+
# Filters sending got supported for filters.pluginFilters key from lavalink api: https://lavalink.dev/api/rest.html#plugin-filters
|
|
174
|
+
# Native implementation for lavaSearch plugin officially updated https://github.com/topi314/LavaSearch
|
|
175
|
+
# Native implementation for lavaSrc plugin officially updated https://github.com/topi314/LavaSrc including floweryTTS
|
|
176
|
+
# couple other changes, which aren't noticeable by you.
|
|
177
|
+
|
|
178
|
+
# Lavalink track.userData got added (basically same feature as my custom pluginInfo.clientData system)
|
|
179
|
+
# You only get the track.userData data through playerUpdate object
|
|
180
|
+
```
|
|
181
|
+
In one of the next updates, there will be more queueWatcher options and more custom nodeevents to trace
|
|
182
|
+
|
|
183
|
+
Most features of this update got tested, but if you encounter any bugs feel free to open an issue!
|
|
@@ -56,6 +56,8 @@ 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
|
+
/** If it should skip to the next Track if track.resolve errors while trying to play a track. */
|
|
60
|
+
autoSkipOnResolveError?: boolean;
|
|
59
61
|
/** If it should emit only new (unique) songs and not when a looping track (or similar) is plaid, default false */
|
|
60
62
|
emitNewSongsOnly?: boolean;
|
|
61
63
|
/** Only allow link requests with links either matching some of that regExp or including some of that string */
|
|
@@ -40,6 +40,7 @@ class LavalinkManager extends events_1.EventEmitter {
|
|
|
40
40
|
linksWhitelist: options?.linksWhitelist ?? [],
|
|
41
41
|
linksBlacklist: options?.linksBlacklist ?? [],
|
|
42
42
|
autoSkip: options?.autoSkip ?? true,
|
|
43
|
+
autoSkipOnResolveError: options?.autoSkipOnResolveError ?? true,
|
|
43
44
|
emitNewSongsOnly: options?.emitNewSongsOnly ?? false,
|
|
44
45
|
queueOptions: {
|
|
45
46
|
maxPreviousTracks: options?.queueOptions?.maxPreviousTracks ?? 25,
|
|
@@ -65,6 +66,8 @@ class LavalinkManager extends events_1.EventEmitter {
|
|
|
65
66
|
// 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");
|
|
66
67
|
if (options?.autoSkip && typeof options?.autoSkip !== "boolean")
|
|
67
68
|
throw new SyntaxError("ManagerOption.autoSkip must be either false | true aka boolean");
|
|
69
|
+
if (options?.autoSkipOnResolveError && typeof options?.autoSkipOnResolveError !== "boolean")
|
|
70
|
+
throw new SyntaxError("ManagerOption.autoSkipOnResolveError must be either false | true aka boolean");
|
|
68
71
|
if (options?.emitNewSongsOnly && typeof options?.emitNewSongsOnly !== "boolean")
|
|
69
72
|
throw new SyntaxError("ManagerOption.emitNewSongsOnly must be either false | true aka boolean");
|
|
70
73
|
if (!options?.nodes || !Array.isArray(options?.nodes) || !options?.nodes.every(node => this.utils.isNodeOptions(node)))
|
|
@@ -78,29 +78,49 @@ export interface NodeStats extends BaseNodeStats {
|
|
|
78
78
|
frameStats: FrameStats;
|
|
79
79
|
}
|
|
80
80
|
export interface LavalinkInfo {
|
|
81
|
+
/** The version of this Lavalink server */
|
|
81
82
|
version: VersionObject;
|
|
83
|
+
/** The millisecond unix timestamp when this Lavalink jar was built */
|
|
82
84
|
buildTime: number;
|
|
85
|
+
/** The git information of this Lavalink server */
|
|
83
86
|
git: GitObject;
|
|
87
|
+
/** The JVM version this Lavalink server runs on */
|
|
84
88
|
jvm: string;
|
|
89
|
+
/** The Lavaplayer version being used by this server */
|
|
85
90
|
lavaplayer: string;
|
|
91
|
+
/** The enabled source managers for this server */
|
|
86
92
|
sourceManagers: string[];
|
|
93
|
+
/** The enabled filters for this server */
|
|
87
94
|
filters: string[];
|
|
95
|
+
/** The enabled plugins for this server */
|
|
88
96
|
plugins: PluginObject[];
|
|
89
97
|
}
|
|
90
98
|
export interface VersionObject {
|
|
99
|
+
/** The full version string of this Lavalink server */
|
|
91
100
|
semver: string;
|
|
101
|
+
/** The major version of this Lavalink server */
|
|
92
102
|
major: number;
|
|
103
|
+
/** The minor version of this Lavalink server */
|
|
93
104
|
minor: number;
|
|
105
|
+
/** The patch version of this Lavalink server */
|
|
94
106
|
patch: internal;
|
|
107
|
+
/** The pre-release version according to semver as a . separated list of identifiers */
|
|
95
108
|
preRelease?: string;
|
|
109
|
+
/** The build metadata according to semver as a . separated list of identifiers */
|
|
110
|
+
build?: string;
|
|
96
111
|
}
|
|
97
112
|
export interface GitObject {
|
|
113
|
+
/** The branch this Lavalink server was built on */
|
|
98
114
|
branch: string;
|
|
115
|
+
/** The commit this Lavalink server was built on */
|
|
99
116
|
commit: string;
|
|
117
|
+
/** The millisecond unix timestamp for when the commit was created */
|
|
100
118
|
commitTime: string;
|
|
101
119
|
}
|
|
102
120
|
export interface PluginObject {
|
|
121
|
+
/** The name of the plugin */
|
|
103
122
|
name: string;
|
|
123
|
+
/** The version of the plugin */
|
|
104
124
|
version: string;
|
|
105
125
|
}
|
|
106
126
|
export declare class LavalinkNode {
|
|
@@ -110,6 +130,11 @@ export declare class LavalinkNode {
|
|
|
110
130
|
calls: number;
|
|
111
131
|
stats: NodeStats;
|
|
112
132
|
sessionId?: string | null;
|
|
133
|
+
/** Wether the node resuming is enabled or not */
|
|
134
|
+
resuming: {
|
|
135
|
+
enabled: boolean;
|
|
136
|
+
timeout: number | null;
|
|
137
|
+
};
|
|
113
138
|
/** Actual Lavalink Information of the Node */
|
|
114
139
|
info: LavalinkInfo | null;
|
|
115
140
|
/** The Node Manager of this Node */
|
|
@@ -126,10 +151,17 @@ export declare class LavalinkNode {
|
|
|
126
151
|
private version;
|
|
127
152
|
/**
|
|
128
153
|
* Create a new Node
|
|
129
|
-
* @param options
|
|
130
|
-
* @param manager
|
|
154
|
+
* @param options Lavalink Node Options
|
|
155
|
+
* @param manager Node Manager
|
|
131
156
|
*/
|
|
132
157
|
constructor(options: LavalinkNodeOptions, manager: NodeManager);
|
|
158
|
+
/**
|
|
159
|
+
* Raw Request util function
|
|
160
|
+
* @param endpoint endpoint string
|
|
161
|
+
* @param modify modify the request
|
|
162
|
+
* @returns
|
|
163
|
+
*/
|
|
164
|
+
private rawRequest;
|
|
133
165
|
/**
|
|
134
166
|
* Makes an API call to the Node
|
|
135
167
|
* @param endpoint The endpoint that we will make the call to
|
|
@@ -137,8 +169,14 @@ export declare class LavalinkNode {
|
|
|
137
169
|
* @returns The returned data
|
|
138
170
|
*/
|
|
139
171
|
request(endpoint: string, modify?: ModifyRequest, parseAsText?: boolean): Promise<unknown>;
|
|
172
|
+
/**
|
|
173
|
+
* Search something raw on the node, please note only add tracks to players of that node
|
|
174
|
+
* @param query SearchQuery Object
|
|
175
|
+
* @param requestUser Request User for creating the player(s)
|
|
176
|
+
* @returns Searchresult
|
|
177
|
+
*/
|
|
140
178
|
search(query: SearchQuery, requestUser: unknown): Promise<SearchResult>;
|
|
141
|
-
lavaSearch(query: LavaSearchQuery, requestUser: unknown): Promise<SearchResult | LavaSearchResponse>;
|
|
179
|
+
lavaSearch(query: LavaSearchQuery, requestUser: unknown, throwOnEmpty?: boolean): Promise<SearchResult | LavaSearchResponse>;
|
|
142
180
|
/**
|
|
143
181
|
* Update the Player State on the Lavalink Server
|
|
144
182
|
* @param data
|
|
@@ -179,7 +217,7 @@ export declare class LavalinkNode {
|
|
|
179
217
|
* @param resuming Whether resuming is enabled for this session or not
|
|
180
218
|
* @param timeout The timeout in seconds (default is 60s)
|
|
181
219
|
*/
|
|
182
|
-
updateSession(resuming?: boolean, timeout?: number): Promise<Session>;
|
|
220
|
+
updateSession(resuming?: boolean, timeout?: number): Promise<InvalidLavalinkRestRequest | Session>;
|
|
183
221
|
/**
|
|
184
222
|
* Decode Track or Tracks
|
|
185
223
|
*/
|
|
@@ -35,6 +35,8 @@ class LavalinkNode {
|
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
37
|
sessionId = null;
|
|
38
|
+
/** Wether the node resuming is enabled or not */
|
|
39
|
+
resuming = { enabled: true, timeout: null };
|
|
38
40
|
/** Actual Lavalink Information of the Node */
|
|
39
41
|
info = null;
|
|
40
42
|
/** The Node Manager of this Node */
|
|
@@ -51,8 +53,8 @@ class LavalinkNode {
|
|
|
51
53
|
version = "v4";
|
|
52
54
|
/**
|
|
53
55
|
* Create a new Node
|
|
54
|
-
* @param options
|
|
55
|
-
* @param manager
|
|
56
|
+
* @param options Lavalink Node Options
|
|
57
|
+
* @param manager Node Manager
|
|
56
58
|
*/
|
|
57
59
|
constructor(options, manager) {
|
|
58
60
|
this.options = {
|
|
@@ -71,12 +73,12 @@ class LavalinkNode {
|
|
|
71
73
|
Object.defineProperty(this, Utils_1.NodeSymbol, { configurable: true, value: true });
|
|
72
74
|
}
|
|
73
75
|
/**
|
|
74
|
-
*
|
|
75
|
-
* @param endpoint
|
|
76
|
-
* @param modify
|
|
77
|
-
* @returns
|
|
76
|
+
* Raw Request util function
|
|
77
|
+
* @param endpoint endpoint string
|
|
78
|
+
* @param modify modify the request
|
|
79
|
+
* @returns
|
|
78
80
|
*/
|
|
79
|
-
async
|
|
81
|
+
async rawRequest(endpoint, modify) {
|
|
80
82
|
const options = {
|
|
81
83
|
path: `/${this.version}/${endpoint.replace(/^\//gm, "")}`,
|
|
82
84
|
method: "GET",
|
|
@@ -91,12 +93,28 @@ class LavalinkNode {
|
|
|
91
93
|
options.path = url.pathname + url.search;
|
|
92
94
|
const request = await this.rest.request(options);
|
|
93
95
|
this.calls++;
|
|
96
|
+
return { request, options };
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Makes an API call to the Node
|
|
100
|
+
* @param endpoint The endpoint that we will make the call to
|
|
101
|
+
* @param modify Used to modify the request before being sent
|
|
102
|
+
* @returns The returned data
|
|
103
|
+
*/
|
|
104
|
+
async request(endpoint, modify, parseAsText = false) {
|
|
105
|
+
const { request, options } = await this.rawRequest(endpoint, modify);
|
|
94
106
|
if (options.method === "DELETE")
|
|
95
107
|
return;
|
|
96
108
|
if (request.statusCode === 404)
|
|
97
|
-
throw new Error(`Node Request resulted into an error, request-
|
|
109
|
+
throw new Error(`Node Request resulted into an error, request-PATH: ${options.path} | headers: ${JSON.stringify(request.headers)}`);
|
|
98
110
|
return parseAsText ? await request.body.text() : await request.body.json();
|
|
99
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Search something raw on the node, please note only add tracks to players of that node
|
|
114
|
+
* @param query SearchQuery Object
|
|
115
|
+
* @param requestUser Request User for creating the player(s)
|
|
116
|
+
* @returns Searchresult
|
|
117
|
+
*/
|
|
100
118
|
async search(query, requestUser) {
|
|
101
119
|
const Query = this.NodeManager.LavalinkManager.utils.transformQuery(query);
|
|
102
120
|
this.NodeManager.LavalinkManager.utils.validateQueryString(this, Query.query, Query.source);
|
|
@@ -113,7 +131,7 @@ class LavalinkNode {
|
|
|
113
131
|
else
|
|
114
132
|
uri += encodeURIComponent(decodeURIComponent(Query.query));
|
|
115
133
|
const res = await this.request(uri);
|
|
116
|
-
// transform the data which can be Error, Track or Track[] to enfore [Track]
|
|
134
|
+
// transform the data which can be Error, Track or Track[] to enfore [Track]
|
|
117
135
|
const resTracks = res.loadType === "playlist" ? res.data?.tracks : res.loadType === "track" ? [res.data] : res.loadType === "search" ? Array.isArray(res.data) ? res.data : [res.data] : [];
|
|
118
136
|
return {
|
|
119
137
|
loadType: res.loadType,
|
|
@@ -130,7 +148,7 @@ class LavalinkNode {
|
|
|
130
148
|
tracks: (resTracks.length ? resTracks.map(t => this.NodeManager.LavalinkManager.utils.buildTrack(t, requestUser)) : [])
|
|
131
149
|
};
|
|
132
150
|
}
|
|
133
|
-
async lavaSearch(query, requestUser) {
|
|
151
|
+
async lavaSearch(query, requestUser, throwOnEmpty = false) {
|
|
134
152
|
const Query = this.NodeManager.LavalinkManager.utils.transformLavaSearchQuery(query);
|
|
135
153
|
if (Query.source)
|
|
136
154
|
this.NodeManager.LavalinkManager.utils.validateSourceString(this, Query.source);
|
|
@@ -142,7 +160,10 @@ class LavalinkNode {
|
|
|
142
160
|
throw new RangeError(`there is no lavasearch-plugin available in the lavalink node: ${this.id}`);
|
|
143
161
|
if (!this.info.plugins.find(v => v.name === "lavasrc-plugin"))
|
|
144
162
|
throw new RangeError(`there is no lavasrc-plugin available in the lavalink node: ${this.id}`);
|
|
145
|
-
const
|
|
163
|
+
const { request } = await this.rawRequest(`/loadsearch?query=${Query.source ? `${Query.source}:` : ""}${encodeURIComponent(Query.query)}${Query.types?.length ? `&types=${Query.types.join(",")}` : ""}`);
|
|
164
|
+
if (throwOnEmpty === true)
|
|
165
|
+
throw new Error("Nothing found");
|
|
166
|
+
const res = (request.statusCode === 204 ? {} : await request.body.json());
|
|
146
167
|
return {
|
|
147
168
|
tracks: res.tracks?.map(v => this.NodeManager.LavalinkManager.utils.buildTrack(v, requestUser)) || [],
|
|
148
169
|
albums: res.albums?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.NodeManager.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
|
|
@@ -165,12 +186,10 @@ class LavalinkNode {
|
|
|
165
186
|
r.method = "PATCH";
|
|
166
187
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
167
188
|
r.headers["Content-Type"] = "application/json";
|
|
168
|
-
if (data.playerOptions.track)
|
|
169
|
-
delete data.playerOptions.track;
|
|
170
189
|
r.body = JSON.stringify(data.playerOptions);
|
|
171
190
|
if (data.noReplace) {
|
|
172
191
|
const url = new URL(`${this.poolAddress}${r.path}`);
|
|
173
|
-
url.searchParams.append("noReplace", data.noReplace
|
|
192
|
+
url.searchParams.append("noReplace", data.noReplace === true && typeof data.noReplace === "boolean" ? "true" : "false");
|
|
174
193
|
r.path = url.pathname + url.search;
|
|
175
194
|
}
|
|
176
195
|
});
|
|
@@ -276,6 +295,10 @@ class LavalinkNode {
|
|
|
276
295
|
data.resuming = resuming;
|
|
277
296
|
if (typeof timeout === "number" && timeout > 0)
|
|
278
297
|
data.timeout = timeout;
|
|
298
|
+
this.resuming = {
|
|
299
|
+
enabled: typeof resuming === "boolean" ? resuming : false,
|
|
300
|
+
timeout: typeof resuming === "boolean" && resuming === true ? timeout : null,
|
|
301
|
+
};
|
|
279
302
|
return await this.request(`/sessions/${this.sessionId}`, r => {
|
|
280
303
|
r.method = "PATCH";
|
|
281
304
|
r.headers = { Authorization: this.options.authorization, 'Content-Type': 'application/json' };
|
|
@@ -326,6 +349,7 @@ class LavalinkNode {
|
|
|
326
349
|
* @returns
|
|
327
350
|
*/
|
|
328
351
|
async fetchVersion() {
|
|
352
|
+
// need to adjust path for no-prefix version info
|
|
329
353
|
return await this.request(`/version`, r => { r.path = "/version"; }, true);
|
|
330
354
|
}
|
|
331
355
|
/**
|
|
@@ -560,6 +584,10 @@ class LavalinkNode {
|
|
|
560
584
|
break;
|
|
561
585
|
case "ready": // payload: { resumed: false, sessionId: 'ytva350aevn6n9n8', op: 'ready' }
|
|
562
586
|
this.sessionId = payload.sessionId;
|
|
587
|
+
this.resuming.enabled = payload.resumed;
|
|
588
|
+
if (payload.resumed === true) {
|
|
589
|
+
this.NodeManager.emit("resumed", this, payload, await this.fetchAllPlayers());
|
|
590
|
+
}
|
|
563
591
|
break;
|
|
564
592
|
default:
|
|
565
593
|
this.NodeManager.emit("error", this, new Error(`Unexpected op "${payload.op}" with data`), payload);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { EventEmitter } from "stream";
|
|
3
|
-
import { LavalinkNode, LavalinkNodeOptions } from "./Node";
|
|
4
3
|
import { LavalinkManager } from "./LavalinkManager";
|
|
5
|
-
import {
|
|
4
|
+
import { LavalinkNode, LavalinkNodeOptions } from "./Node";
|
|
6
5
|
import { DestroyReasonsType } from "./Player";
|
|
6
|
+
import { LavalinkPlayer, MiniMap } from "./Utils";
|
|
7
7
|
type LavalinkNodeIdentifier = string;
|
|
8
8
|
interface NodeManagerEvents {
|
|
9
9
|
/**
|
|
@@ -44,6 +44,16 @@ interface NodeManagerEvents {
|
|
|
44
44
|
* @event Manager.nodeManager#raw
|
|
45
45
|
*/
|
|
46
46
|
"raw": (node: LavalinkNode, payload: unknown) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Emits when the node connects resumed. You then need to create all players within this event for your usecase.
|
|
49
|
+
* Aka for that you need to be able to save player data like vc channel + text channel in a db and then sync it again
|
|
50
|
+
* @event Manager.nodeManager#nodeResumed
|
|
51
|
+
*/
|
|
52
|
+
"resumed": (node: LavalinkNode, paylaod: {
|
|
53
|
+
resumed: true;
|
|
54
|
+
sessionId: string;
|
|
55
|
+
op: "ready";
|
|
56
|
+
}, players: LavalinkPlayer[]) => void;
|
|
47
57
|
}
|
|
48
58
|
export declare interface NodeManager {
|
|
49
59
|
on<U extends keyof NodeManagerEvents>(event: U, listener: NodeManagerEvents[U]): this;
|
|
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.NodeManager = void 0;
|
|
4
4
|
const stream_1 = require("stream");
|
|
5
5
|
const Node_1 = require("./Node");
|
|
6
|
-
const Utils_1 = require("./Utils");
|
|
7
6
|
const Player_1 = require("./Player");
|
|
7
|
+
const Utils_1 = require("./Utils");
|
|
8
8
|
class NodeManager extends stream_1.EventEmitter {
|
|
9
9
|
nodes = new Utils_1.MiniMap();
|
|
10
10
|
constructor(LavalinkManager) {
|
|
@@ -3,7 +3,7 @@ import { LavalinkManager } from "./LavalinkManager";
|
|
|
3
3
|
import { LavalinkNode, SponsorBlockSegment } from "./Node";
|
|
4
4
|
import { Queue } from "./Queue";
|
|
5
5
|
import { Track, UnresolvedTrack } from "./Track";
|
|
6
|
-
import { LavalinkPlayerVoiceOptions, LavaSearchQuery, SearchQuery } from "./Utils";
|
|
6
|
+
import { Base64, LavalinkPlayerVoiceOptions, LavaSearchQuery, SearchQuery } from "./Utils";
|
|
7
7
|
type PlayerDestroyReasons = "QueueEmpty" | "NodeDestroy" | "NodeDeleted" | "LavalinkNoVoice" | "NodeReconnectFail" | "PlayerReconnectFail" | "Disconnected" | "ChannelDeleted" | "ReconnectAllNodes" | "DisconnectAllNodes";
|
|
8
8
|
export type DestroyReasonsType = PlayerDestroyReasons | string;
|
|
9
9
|
export declare const DestroyReasons: Record<PlayerDestroyReasons, PlayerDestroyReasons>;
|
|
@@ -51,27 +51,42 @@ export interface PlayerOptions {
|
|
|
51
51
|
/** If a volume should be applied via filters instead of lavalink-volume */
|
|
52
52
|
applyVolumeAsFilter?: boolean;
|
|
53
53
|
}
|
|
54
|
-
export
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
encodedTrack?: string | null;
|
|
59
|
-
/** Encoded Track to use&search, instead of the queue system (yt only)... */
|
|
60
|
-
identifier?: string;
|
|
54
|
+
export type anyObject = {
|
|
55
|
+
[key: string | number]: string | number | null | anyObject;
|
|
56
|
+
};
|
|
57
|
+
export interface BasePlayOptions {
|
|
61
58
|
/** The position to start the track. */
|
|
62
59
|
position?: number;
|
|
63
60
|
/** The position to end the track. */
|
|
64
61
|
endTime?: number;
|
|
65
|
-
/** Whether to not replace the track if a play payload is sent. */
|
|
66
|
-
noReplace?: boolean;
|
|
67
62
|
/** If to start "paused" */
|
|
68
63
|
paused?: boolean;
|
|
69
64
|
/** The Volume to start with */
|
|
70
65
|
volume?: number;
|
|
71
66
|
/** The Lavalink Filters to use | only with the new REST API */
|
|
72
67
|
filters?: Partial<LavalinkFilterData>;
|
|
68
|
+
/** Voice Update for Lavalink */
|
|
73
69
|
voice?: LavalinkPlayerVoiceOptions;
|
|
74
70
|
}
|
|
71
|
+
export interface LavalinkPlayOptions extends BasePlayOptions {
|
|
72
|
+
/** Which Track to play | don't provide, if it should pick from the Queue */
|
|
73
|
+
track?: {
|
|
74
|
+
/** The track encoded base64 string to use instead of the one from the queue system */
|
|
75
|
+
encoded?: Base64 | null;
|
|
76
|
+
/** The identifier of the track to use */
|
|
77
|
+
identifier?: string;
|
|
78
|
+
/** Custom User Data for the track to provide, will then be on the userData object from the track */
|
|
79
|
+
userData?: anyObject;
|
|
80
|
+
/** The Track requester for when u provide encodedTrack / identifer */
|
|
81
|
+
requester?: unknown;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export interface PlayOptions extends LavalinkPlayOptions {
|
|
85
|
+
/** Whether to not replace the track if a play payload is sent. */
|
|
86
|
+
noReplace?: boolean;
|
|
87
|
+
/** Which Track to play | don't provide, if it should pick from the Queue */
|
|
88
|
+
clientTrack?: Track | UnresolvedTrack;
|
|
89
|
+
}
|
|
75
90
|
export interface Player {
|
|
76
91
|
filterManager: FilterManager;
|
|
77
92
|
LavalinkManager: LavalinkManager;
|
|
@@ -122,15 +122,38 @@ class Player {
|
|
|
122
122
|
* Play the next track from the queue / a specific track, with playoptions for Lavalink
|
|
123
123
|
* @param options
|
|
124
124
|
*/
|
|
125
|
-
async play(options) {
|
|
125
|
+
async play(options = {}) {
|
|
126
126
|
if (this.get("internal_queueempty")) {
|
|
127
127
|
clearTimeout(this.get("internal_queueempty"));
|
|
128
128
|
this.set("internal_queueempty", undefined);
|
|
129
129
|
}
|
|
130
|
-
if
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
// if clientTrack provided, play it
|
|
131
|
+
if (options?.clientTrack && (this.LavalinkManager.utils.isTrack(options?.clientTrack) || this.LavalinkManager.utils.isUnresolvedTrack(options.clientTrack))) {
|
|
132
|
+
if (this.LavalinkManager.utils.isUnresolvedTrack(options.clientTrack))
|
|
133
|
+
await options.clientTrack.resolve(this);
|
|
134
|
+
if (typeof options.track.userData === "object")
|
|
135
|
+
options.clientTrack.userData = { ...(options?.clientTrack.userData || {}), ...(options.track.userData || {}) };
|
|
136
|
+
await this.queue.add(options?.clientTrack, 0);
|
|
137
|
+
await (0, Utils_1.queueTrackEnd)(this);
|
|
138
|
+
}
|
|
139
|
+
else if (options?.track?.encoded) {
|
|
140
|
+
// handle play encoded options manually // TODO let it resolve by lavalink!
|
|
141
|
+
const track = await this.node.decode.singleTrack(options.track?.encoded, options.track?.requester || this.queue?.current?.requester || this.queue.previous?.[0]?.requester || this.queue.tracks?.[0]?.requester || this.LavalinkManager.options.client);
|
|
142
|
+
if (typeof options.track.userData === "object")
|
|
143
|
+
track.userData = { ...(track.userData || {}), ...(options.track.userData || {}) };
|
|
144
|
+
if (track)
|
|
145
|
+
this.queue.add(track, 0);
|
|
146
|
+
await (0, Utils_1.queueTrackEnd)(this);
|
|
147
|
+
}
|
|
148
|
+
else if (options?.track?.identifier) {
|
|
149
|
+
// handle play identifier options manually // TODO let it resolve by lavalink!
|
|
150
|
+
const res = await this.search({
|
|
151
|
+
query: options?.track?.identifier
|
|
152
|
+
}, options?.track?.identifier || this.queue?.current?.requester || this.queue.previous?.[0]?.requester || this.queue.tracks?.[0]?.requester || this.LavalinkManager.options.client);
|
|
153
|
+
if (typeof options.track.userData === "object")
|
|
154
|
+
res.tracks[0].userData = { ...(res.tracks[0].userData || {}), ...(options.track.userData || {}) };
|
|
155
|
+
if (res.tracks[0])
|
|
156
|
+
this.queue.add(res.tracks[0], 0);
|
|
134
157
|
await (0, Utils_1.queueTrackEnd)(this);
|
|
135
158
|
}
|
|
136
159
|
if (!this.queue.current && this.queue.tracks.length)
|
|
@@ -139,20 +162,22 @@ class Player {
|
|
|
139
162
|
try {
|
|
140
163
|
// resolve the unresolved track
|
|
141
164
|
await this.queue.current.resolve(this);
|
|
165
|
+
if (typeof options.track.userData === "object")
|
|
166
|
+
this.queue.current.userData = { ...(this.queue.current.userData || {}), ...(options.track.userData || {}) };
|
|
142
167
|
}
|
|
143
168
|
catch (error) {
|
|
144
169
|
this.LavalinkManager.emit("trackError", this, this.queue.current, error);
|
|
170
|
+
if (options && "clientTrack" in options)
|
|
171
|
+
delete options.clientTrack;
|
|
145
172
|
if (options && "track" in options)
|
|
146
173
|
delete options.track;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (this.queue.tracks[0])
|
|
174
|
+
// try to play the next track if possible
|
|
175
|
+
if (this.LavalinkManager.options?.autoSkipOnResolveError === true && this.queue.tracks[0])
|
|
150
176
|
return this.play(options);
|
|
151
177
|
return this;
|
|
152
178
|
}
|
|
153
179
|
}
|
|
154
|
-
|
|
155
|
-
if (!track)
|
|
180
|
+
if (!this.queue.current)
|
|
156
181
|
throw new Error(`There is no Track in the Queue, nor provided in the PlayOptions`);
|
|
157
182
|
if (typeof options?.volume === "number" && !isNaN(options?.volume)) {
|
|
158
183
|
this.volume = Math.max(Math.min(options?.volume, 500), 0);
|
|
@@ -162,24 +187,27 @@ class Player {
|
|
|
162
187
|
this.lavalinkVolume = Math.round(vol);
|
|
163
188
|
options.volume = this.lavalinkVolume;
|
|
164
189
|
}
|
|
165
|
-
const finalOptions = {
|
|
166
|
-
|
|
190
|
+
const finalOptions = Object.fromEntries(Object.entries({
|
|
191
|
+
track: {
|
|
192
|
+
encoded: this.queue.current?.encoded || null,
|
|
193
|
+
// identifier: options.identifier,
|
|
194
|
+
userData: options?.track?.userData || {},
|
|
195
|
+
},
|
|
167
196
|
volume: this.lavalinkVolume,
|
|
168
|
-
position: 0,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
197
|
+
position: options?.position ?? 0,
|
|
198
|
+
endTime: options?.endTime ?? undefined,
|
|
199
|
+
filters: options?.filters ?? undefined,
|
|
200
|
+
paused: options?.paused ?? undefined,
|
|
201
|
+
voice: options?.voice ?? undefined
|
|
202
|
+
}).filter(v => typeof v[1] !== "undefined"));
|
|
203
|
+
if ((typeof finalOptions.position !== "undefined" && isNaN(finalOptions.position)) || (typeof finalOptions.position === "number" && (finalOptions.position < 0 || finalOptions.position >= this.queue.current.info.duration)))
|
|
174
204
|
throw new Error("PlayerOption#position must be a positive number, less than track's duration");
|
|
175
205
|
if ((typeof finalOptions.volume !== "undefined" && isNaN(finalOptions.volume) || (typeof finalOptions.volume === "number" && finalOptions.volume < 0)))
|
|
176
206
|
throw new Error("PlayerOption#volume must be a positive number");
|
|
177
|
-
if ((typeof finalOptions.endTime !== "undefined" && isNaN(finalOptions.endTime)) || (typeof finalOptions.endTime === "number" && (finalOptions.endTime < 0 || finalOptions.endTime >=
|
|
207
|
+
if ((typeof finalOptions.endTime !== "undefined" && isNaN(finalOptions.endTime)) || (typeof finalOptions.endTime === "number" && (finalOptions.endTime < 0 || finalOptions.endTime >= this.queue.current.info.duration)))
|
|
178
208
|
throw new Error("PlayerOption#endTime must be a positive number, less than track's duration");
|
|
179
209
|
if (typeof finalOptions.position === "number" && typeof finalOptions.endTime === "number" && finalOptions.endTime < finalOptions.position)
|
|
180
210
|
throw new Error("PlayerOption#endTime must be bigger than PlayerOption#position");
|
|
181
|
-
if ("noReplace" in finalOptions)
|
|
182
|
-
delete finalOptions.noReplace;
|
|
183
211
|
const now = performance.now();
|
|
184
212
|
await this.node.updatePlayer({
|
|
185
213
|
guildId: this.guildId,
|
|
@@ -305,7 +333,7 @@ class Player {
|
|
|
305
333
|
if (!this.playing)
|
|
306
334
|
return await this.play();
|
|
307
335
|
const now = performance.now();
|
|
308
|
-
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: {
|
|
336
|
+
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { track: { encoded: null } } });
|
|
309
337
|
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
310
338
|
return this;
|
|
311
339
|
}
|
|
@@ -325,7 +353,7 @@ class Player {
|
|
|
325
353
|
this.set("internal_autoplayStopPlaying", undefined);
|
|
326
354
|
const now = performance.now();
|
|
327
355
|
// send to lavalink, that it should stop playing
|
|
328
|
-
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: {
|
|
356
|
+
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { track: { encoded: null } } });
|
|
329
357
|
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
330
358
|
return this;
|
|
331
359
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Player } from "./Player";
|
|
1
|
+
import { anyObject, Player } from "./Player";
|
|
2
2
|
import { Base64 } from "./Utils";
|
|
3
3
|
/** Sourcenames provided by lavalink server */
|
|
4
4
|
export type LavalinkSourceNames = "youtube" | "youtubemusic" | "soundcloud" | "bandcamp" | "twitch";
|
|
@@ -89,6 +89,8 @@ export interface LavalinkTrack {
|
|
|
89
89
|
info: LavalinkTrackInfo;
|
|
90
90
|
/** Plugin Information from Lavalink */
|
|
91
91
|
pluginInfo: Partial<PluginInfo>;
|
|
92
|
+
/** The userData Object from when you provide to the lavalink request */
|
|
93
|
+
userData?: anyObject;
|
|
92
94
|
}
|
|
93
95
|
export interface Track {
|
|
94
96
|
/** The Base 64 encoded String */
|
|
@@ -99,6 +101,8 @@ export interface Track {
|
|
|
99
101
|
pluginInfo: Partial<PluginInfo>;
|
|
100
102
|
/** The Track's Requester */
|
|
101
103
|
requester?: unknown;
|
|
104
|
+
/** The userData Object from when you provide to the lavalink request */
|
|
105
|
+
userData?: anyObject;
|
|
102
106
|
}
|
|
103
107
|
export interface UnresolvedTrackInfo extends Partial<TrackInfo> {
|
|
104
108
|
/** Required */
|
|
@@ -117,6 +121,8 @@ export interface UnresolvedTrack {
|
|
|
117
121
|
info: UnresolvedTrackInfo;
|
|
118
122
|
/** Plugin Information from Lavalink */
|
|
119
123
|
pluginInfo: Partial<PluginInfo>;
|
|
124
|
+
/** The userData Object from when you provide to the lavalink request */
|
|
125
|
+
userData?: anyObject;
|
|
120
126
|
/** The Track's Requester */
|
|
121
127
|
requester?: unknown;
|
|
122
128
|
}
|