lavalink-client 1.2.6 → 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 +40 -3
- package/dist/cjs/structures/Node.js +27 -6
- 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 +40 -3
- package/dist/esm/structures/Node.js +27 -6
- 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 +40 -3
- 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,16 @@ 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
|
+
*/
|
|
133
164
|
private rawRequest;
|
|
134
165
|
/**
|
|
135
166
|
* Makes an API call to the Node
|
|
@@ -138,6 +169,12 @@ export declare class LavalinkNode {
|
|
|
138
169
|
* @returns The returned data
|
|
139
170
|
*/
|
|
140
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
|
+
*/
|
|
141
178
|
search(query: SearchQuery, requestUser: unknown): Promise<SearchResult>;
|
|
142
179
|
lavaSearch(query: LavaSearchQuery, requestUser: unknown, throwOnEmpty?: boolean): Promise<SearchResult | LavaSearchResponse>;
|
|
143
180
|
/**
|
|
@@ -180,7 +217,7 @@ export declare class LavalinkNode {
|
|
|
180
217
|
* @param resuming Whether resuming is enabled for this session or not
|
|
181
218
|
* @param timeout The timeout in seconds (default is 60s)
|
|
182
219
|
*/
|
|
183
|
-
updateSession(resuming?: boolean, timeout?: number): Promise<Session>;
|
|
220
|
+
updateSession(resuming?: boolean, timeout?: number): Promise<InvalidLavalinkRestRequest | Session>;
|
|
184
221
|
/**
|
|
185
222
|
* Decode Track or Tracks
|
|
186
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 = {
|
|
@@ -70,6 +72,12 @@ class LavalinkNode {
|
|
|
70
72
|
this.options.regions = (this.options.regions || []).map(a => a.toLowerCase());
|
|
71
73
|
Object.defineProperty(this, Utils_1.NodeSymbol, { configurable: true, value: true });
|
|
72
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Raw Request util function
|
|
77
|
+
* @param endpoint endpoint string
|
|
78
|
+
* @param modify modify the request
|
|
79
|
+
* @returns
|
|
80
|
+
*/
|
|
73
81
|
async rawRequest(endpoint, modify) {
|
|
74
82
|
const options = {
|
|
75
83
|
path: `/${this.version}/${endpoint.replace(/^\//gm, "")}`,
|
|
@@ -101,6 +109,12 @@ class LavalinkNode {
|
|
|
101
109
|
throw new Error(`Node Request resulted into an error, request-PATH: ${options.path} | headers: ${JSON.stringify(request.headers)}`);
|
|
102
110
|
return parseAsText ? await request.body.text() : await request.body.json();
|
|
103
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
|
+
*/
|
|
104
118
|
async search(query, requestUser) {
|
|
105
119
|
const Query = this.NodeManager.LavalinkManager.utils.transformQuery(query);
|
|
106
120
|
this.NodeManager.LavalinkManager.utils.validateQueryString(this, Query.query, Query.source);
|
|
@@ -117,7 +131,7 @@ class LavalinkNode {
|
|
|
117
131
|
else
|
|
118
132
|
uri += encodeURIComponent(decodeURIComponent(Query.query));
|
|
119
133
|
const res = await this.request(uri);
|
|
120
|
-
// 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]
|
|
121
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] : [];
|
|
122
136
|
return {
|
|
123
137
|
loadType: res.loadType,
|
|
@@ -172,12 +186,10 @@ class LavalinkNode {
|
|
|
172
186
|
r.method = "PATCH";
|
|
173
187
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
174
188
|
r.headers["Content-Type"] = "application/json";
|
|
175
|
-
if (data.playerOptions.track)
|
|
176
|
-
delete data.playerOptions.track;
|
|
177
189
|
r.body = JSON.stringify(data.playerOptions);
|
|
178
190
|
if (data.noReplace) {
|
|
179
191
|
const url = new URL(`${this.poolAddress}${r.path}`);
|
|
180
|
-
url.searchParams.append("noReplace", data.noReplace
|
|
192
|
+
url.searchParams.append("noReplace", data.noReplace === true && typeof data.noReplace === "boolean" ? "true" : "false");
|
|
181
193
|
r.path = url.pathname + url.search;
|
|
182
194
|
}
|
|
183
195
|
});
|
|
@@ -283,6 +295,10 @@ class LavalinkNode {
|
|
|
283
295
|
data.resuming = resuming;
|
|
284
296
|
if (typeof timeout === "number" && timeout > 0)
|
|
285
297
|
data.timeout = timeout;
|
|
298
|
+
this.resuming = {
|
|
299
|
+
enabled: typeof resuming === "boolean" ? resuming : false,
|
|
300
|
+
timeout: typeof resuming === "boolean" && resuming === true ? timeout : null,
|
|
301
|
+
};
|
|
286
302
|
return await this.request(`/sessions/${this.sessionId}`, r => {
|
|
287
303
|
r.method = "PATCH";
|
|
288
304
|
r.headers = { Authorization: this.options.authorization, 'Content-Type': 'application/json' };
|
|
@@ -333,6 +349,7 @@ class LavalinkNode {
|
|
|
333
349
|
* @returns
|
|
334
350
|
*/
|
|
335
351
|
async fetchVersion() {
|
|
352
|
+
// need to adjust path for no-prefix version info
|
|
336
353
|
return await this.request(`/version`, r => { r.path = "/version"; }, true);
|
|
337
354
|
}
|
|
338
355
|
/**
|
|
@@ -567,6 +584,10 @@ class LavalinkNode {
|
|
|
567
584
|
break;
|
|
568
585
|
case "ready": // payload: { resumed: false, sessionId: 'ytva350aevn6n9n8', op: 'ready' }
|
|
569
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
|
+
}
|
|
570
591
|
break;
|
|
571
592
|
default:
|
|
572
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
|
}
|