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 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
- * Makes an API call to the Node
75
- * @param endpoint The endpoint that we will make the call to
76
- * @param modify Used to modify the request before being sent
77
- * @returns The returned data
76
+ * Raw Request util function
77
+ * @param endpoint endpoint string
78
+ * @param modify modify the request
79
+ * @returns
78
80
  */
79
- async request(endpoint, modify, parseAsText = false) {
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-URL: ${url} | headers: ${JSON.stringify(request.headers)}`);
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 res = await this.request(`/loadsearch?query=${Query.source ? `${Query.source}:` : ""}${encodeURIComponent(Query.query)}${Query.types?.length ? `&types=${Query.types.join(",")}` : ""}`);
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?.toString() || "false");
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 { MiniMap } from "./Utils";
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 interface PlayOptions {
55
- /** Which Track to play | don't provide, if it should pick from the Queue */
56
- track?: Track | UnresolvedTrack;
57
- /** Encoded Track to use, instead of the queue system... */
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 (options?.track && (this.LavalinkManager.utils.isTrack(options?.track) || this.LavalinkManager.utils.isUnresolvedTrack(options.track))) {
131
- if (this.LavalinkManager.utils.isUnresolvedTrack(options.track))
132
- await options.track.resolve(this);
133
- await this.queue.add(options?.track, 0);
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
- if (options && "encodedTrack" in options)
148
- delete options.encodedTrack;
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
- const track = this.queue.current;
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
- encodedTrack: track.encoded,
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
- ...options,
170
- };
171
- if ("track" in finalOptions)
172
- delete finalOptions.track;
173
- if ((typeof finalOptions.position !== "undefined" && isNaN(finalOptions.position)) || (typeof finalOptions.position === "number" && (finalOptions.position < 0 || finalOptions.position >= track.info.duration)))
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 >= track.info.duration)))
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: { encodedTrack: null } });
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: { encodedTrack: null } });
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
  }