lavalink-client 1.2.6 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { LavalinkFilterData } from "./Filters";
2
2
  import { LavalinkManager } from "./LavalinkManager";
3
3
  import { LavalinkNode, LavalinkNodeOptions, NodeStats } from "./Node";
4
- import { Player, PlayOptions } from "./Player";
4
+ import { LavalinkPlayOptions, Player } from "./Player";
5
5
  import { LavalinkTrack, PluginInfo, Track, UnresolvedQuery, UnresolvedTrack } from "./Track";
6
6
  export declare const TrackSymbol: unique symbol;
7
7
  export declare const UnresolvedTrackSymbol: unique symbol;
@@ -247,17 +247,29 @@ export type State = "CONNECTED" | "CONNECTING" | "DISCONNECTED" | "DISCONNECTING
247
247
  export type PlayerEventType = "TrackStartEvent" | "TrackEndEvent" | "TrackExceptionEvent" | "TrackStuckEvent" | "WebSocketClosedEvent" | SponsorBlockSegmentEventType;
248
248
  export type TrackEndReason = "finished" | "loadFailed" | "stopped" | "replaced" | "cleanup";
249
249
  export interface InvalidLavalinkRestRequest {
250
+ /** Rest Request Data for when it was made */
250
251
  timestamp: number;
252
+ /** Status of the request */
251
253
  status: number;
254
+ /** Specific Errro which was sent */
252
255
  error: string;
256
+ /** Specific Message which was created */
253
257
  message?: string;
258
+ /** The specific error trace from the request */
259
+ trace?: unknown;
260
+ /** Path of where it's from */
254
261
  path: string;
255
262
  }
256
263
  export interface LavalinkPlayerVoice {
264
+ /** The Voice Token */
257
265
  token: string;
266
+ /** The Voice Server Endpoint */
258
267
  endpoint: string;
268
+ /** The Voice SessionId */
259
269
  sessionId: string;
270
+ /** Wether or not the player is connected */
260
271
  connected?: boolean;
272
+ /** The Ping to the voice server */
261
273
  ping?: number;
262
274
  }
263
275
  export interface LavalinkPlayerVoiceOptions extends Omit<LavalinkPlayerVoice, 'connected' | 'ping'> {
@@ -296,84 +308,121 @@ export interface RoutePlanner {
296
308
  };
297
309
  }
298
310
  export interface Session {
311
+ /** Wether or not session is resuming or not */
299
312
  resuming: boolean;
313
+ /** For how long a session is lasting while not connected */
300
314
  timeout: number;
301
315
  }
302
316
  export interface GuildShardPayload {
303
317
  /** The OP code */
304
318
  op: number;
319
+ /** Data to send */
305
320
  d: {
321
+ /** Guild id to apply voice settings */
306
322
  guild_id: string;
323
+ /** channel to move/connect to, or null to leave it */
307
324
  channel_id: string | null;
325
+ /** wether or not mute yourself */
308
326
  self_mute: boolean;
327
+ /** wether or not deafen yourself */
309
328
  self_deaf: boolean;
310
329
  };
311
330
  }
312
331
  export interface PlayerUpdateInfo {
332
+ /** guild id of the player */
313
333
  guildId: string;
314
- playerOptions: PlayOptions;
334
+ /** Player options to provide to lavalink */
335
+ playerOptions: LavalinkPlayOptions;
336
+ /** Whether or not replace the current track with the new one (true is recommended) */
315
337
  noReplace?: boolean;
316
338
  }
317
339
  export interface LavalinkPlayer {
340
+ /** Guild Id of the player */
318
341
  guildId: string;
319
- track?: {
320
- encoded?: string;
321
- info: {
322
- identifier: string;
323
- title: string;
324
- author: string;
325
- length: number;
326
- artworkUrl: string | null;
327
- uri: string;
328
- sourceName: string;
329
- isSeekable: boolean;
330
- isStream: boolean;
331
- isrc: string | null;
332
- position?: number;
333
- };
334
- };
342
+ /** IF playing a track, all of the track information */
343
+ track?: LavalinkTrack;
344
+ /** Lavalink volume (mind volumedecrementer) */
335
345
  volume: number;
346
+ /** Wether it's paused or not */
336
347
  paused: boolean;
348
+ /** Voice Endpoint data */
337
349
  voice: LavalinkPlayerVoice;
350
+ /** All Audio Filters */
338
351
  filters: Partial<LavalinkFilterData>;
352
+ /** Lavalink-Voice-State Variables */
353
+ state: {
354
+ /** Time since connection established */
355
+ time: number;
356
+ /** Position of the track */
357
+ position: number;
358
+ /** COnnected or not */
359
+ connected: boolean;
360
+ /** Ping to voice server */
361
+ ping: number;
362
+ };
339
363
  }
340
364
  export interface ChannelDeletePacket {
365
+ /** Packet key for channel delete */
341
366
  t: "CHANNEL_DELETE";
367
+ /** data which is sent and relevant */
342
368
  d: {
369
+ /** guild id */
343
370
  guild_id: string;
371
+ /** Channel id */
344
372
  id: string;
345
373
  };
346
374
  }
347
375
  export interface VoiceState {
376
+ /** OP key from lavalink */
348
377
  op: "voiceUpdate";
378
+ /** GuildId provided by lavalink */
349
379
  guildId: string;
380
+ /** Event data */
350
381
  event: VoiceServer;
382
+ /** Session Id of the voice connection */
351
383
  sessionId?: string;
384
+ /** guild id of the voice channel */
352
385
  guild_id: string;
386
+ /** user id from the voice connection */
353
387
  user_id: string;
388
+ /** Session Id of the voice connection */
354
389
  session_id: string;
390
+ /** Voice Channel Id */
355
391
  channel_id: string;
356
392
  }
393
+ /** The Base64 decodes tring by lavalink */
357
394
  export type Base64 = string;
358
395
  export interface VoiceServer {
396
+ /** Voice Token */
359
397
  token: string;
398
+ /** Guild Id of the voice server connection */
360
399
  guild_id: string;
400
+ /** Server Endpoint */
361
401
  endpoint: string;
362
402
  }
363
403
  export interface VoicePacket {
404
+ /** Voice Packet Keys to send */
364
405
  t?: "VOICE_SERVER_UPDATE" | "VOICE_STATE_UPDATE";
406
+ /** Voice Packets to send */
365
407
  d: VoiceState | VoiceServer;
366
408
  }
367
409
  export interface NodeMessage extends NodeStats {
410
+ /** The type of the event */
368
411
  type: PlayerEventType;
412
+ /** what ops are applying to that event */
369
413
  op: "stats" | "playerUpdate" | "event";
414
+ /** The specific guild id for that message */
370
415
  guildId: string;
371
416
  }
372
417
  export declare function queueTrackEnd(player: Player): Promise<Track>;
418
+ /** Specific types to filter for lavasearch, will be filtered to correct types */
373
419
  export type LavaSearchType = "track" | "album" | "artist" | "playlist" | "text" | "tracks" | "albums" | "artists" | "playlists" | "texts";
374
420
  export interface LavaSearchFilteredResponse {
421
+ /** The Information of a playlist provided by lavasearch */
375
422
  info: PlaylistInfo;
423
+ /** additional plugin information */
376
424
  pluginInfo: PluginInfo;
425
+ /** List of tracks */
377
426
  tracks: Track[];
378
427
  }
379
428
  export interface LavaSearchResponse {
@@ -393,13 +442,20 @@ export interface LavaSearchResponse {
393
442
  /** Addition result data provided by plugins */
394
443
  pluginInfo: PluginInfo;
395
444
  }
445
+ /** SearchQuery Object for raw lavalink requests */
396
446
  export type SearchQuery = {
447
+ /** lavalink search Query / identifier string */
397
448
  query: string;
449
+ /** Source to append to the search query string */
398
450
  source?: SearchPlatform;
399
- } | string;
451
+ } | /** Our just the search query / identifier string */ string;
452
+ /** SearchQuery Object for Lavalink LavaSearch Plugin requests */
400
453
  export type LavaSearchQuery = {
454
+ /** lavalink search Query / identifier string */
401
455
  query: string;
456
+ /** Source to append to the search query string */
402
457
  source: LavaSrcSearchPlatformBase;
458
+ /** The Types to filter the search to */
403
459
  types?: LavaSearchType[];
404
460
  };
405
461
  export {};
@@ -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 */
@@ -37,6 +37,7 @@ export class LavalinkManager extends EventEmitter {
37
37
  linksWhitelist: options?.linksWhitelist ?? [],
38
38
  linksBlacklist: options?.linksBlacklist ?? [],
39
39
  autoSkip: options?.autoSkip ?? true,
40
+ autoSkipOnResolveError: options?.autoSkipOnResolveError ?? true,
40
41
  emitNewSongsOnly: options?.emitNewSongsOnly ?? false,
41
42
  queueOptions: {
42
43
  maxPreviousTracks: options?.queueOptions?.maxPreviousTracks ?? 25,
@@ -62,6 +63,8 @@ export class LavalinkManager extends EventEmitter {
62
63
  // 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");
63
64
  if (options?.autoSkip && typeof options?.autoSkip !== "boolean")
64
65
  throw new SyntaxError("ManagerOption.autoSkip must be either false | true aka boolean");
66
+ if (options?.autoSkipOnResolveError && typeof options?.autoSkipOnResolveError !== "boolean")
67
+ throw new SyntaxError("ManagerOption.autoSkipOnResolveError must be either false | true aka boolean");
65
68
  if (options?.emitNewSongsOnly && typeof options?.emitNewSongsOnly !== "boolean")
66
69
  throw new SyntaxError("ManagerOption.emitNewSongsOnly must be either false | true aka boolean");
67
70
  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
  */
@@ -31,6 +31,8 @@ export class LavalinkNode {
31
31
  }
32
32
  };
33
33
  sessionId = null;
34
+ /** Wether the node resuming is enabled or not */
35
+ resuming = { enabled: true, timeout: null };
34
36
  /** Actual Lavalink Information of the Node */
35
37
  info = null;
36
38
  /** The Node Manager of this Node */
@@ -47,8 +49,8 @@ export class LavalinkNode {
47
49
  version = "v4";
48
50
  /**
49
51
  * Create a new Node
50
- * @param options
51
- * @param manager
52
+ * @param options Lavalink Node Options
53
+ * @param manager Node Manager
52
54
  */
53
55
  constructor(options, manager) {
54
56
  this.options = {
@@ -66,6 +68,12 @@ export class LavalinkNode {
66
68
  this.options.regions = (this.options.regions || []).map(a => a.toLowerCase());
67
69
  Object.defineProperty(this, NodeSymbol, { configurable: true, value: true });
68
70
  }
71
+ /**
72
+ * Raw Request util function
73
+ * @param endpoint endpoint string
74
+ * @param modify modify the request
75
+ * @returns
76
+ */
69
77
  async rawRequest(endpoint, modify) {
70
78
  const options = {
71
79
  path: `/${this.version}/${endpoint.replace(/^\//gm, "")}`,
@@ -91,12 +99,18 @@ export class LavalinkNode {
91
99
  */
92
100
  async request(endpoint, modify, parseAsText = false) {
93
101
  const { request, options } = await this.rawRequest(endpoint, modify);
94
- if (options.method === "DELETE")
102
+ if (["DELETE", "PUT"].includes(options.method))
95
103
  return;
96
104
  if (request.statusCode === 404)
97
105
  throw new Error(`Node Request resulted into an error, request-PATH: ${options.path} | headers: ${JSON.stringify(request.headers)}`);
98
106
  return parseAsText ? await request.body.text() : await request.body.json();
99
107
  }
108
+ /**
109
+ * Search something raw on the node, please note only add tracks to players of that node
110
+ * @param query SearchQuery Object
111
+ * @param requestUser Request User for creating the player(s)
112
+ * @returns Searchresult
113
+ */
100
114
  async search(query, requestUser) {
101
115
  const Query = this.NodeManager.LavalinkManager.utils.transformQuery(query);
102
116
  this.NodeManager.LavalinkManager.utils.validateQueryString(this, Query.query, Query.source);
@@ -113,7 +127,7 @@ export class LavalinkNode {
113
127
  else
114
128
  uri += encodeURIComponent(decodeURIComponent(Query.query));
115
129
  const res = await this.request(uri);
116
- // transform the data which can be Error, Track or Track[] to enfore [Track]
130
+ // transform the data which can be Error, Track or Track[] to enfore [Track]
117
131
  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
132
  return {
119
133
  loadType: res.loadType,
@@ -168,12 +182,10 @@ export class LavalinkNode {
168
182
  r.method = "PATCH";
169
183
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
170
184
  r.headers["Content-Type"] = "application/json";
171
- if (data.playerOptions.track)
172
- delete data.playerOptions.track;
173
185
  r.body = JSON.stringify(data.playerOptions);
174
186
  if (data.noReplace) {
175
187
  const url = new URL(`${this.poolAddress}${r.path}`);
176
- url.searchParams.append("noReplace", data.noReplace?.toString() || "false");
188
+ url.searchParams.append("noReplace", data.noReplace === true && typeof data.noReplace === "boolean" ? "true" : "false");
177
189
  r.path = url.pathname + url.search;
178
190
  }
179
191
  });
@@ -279,6 +291,10 @@ export class LavalinkNode {
279
291
  data.resuming = resuming;
280
292
  if (typeof timeout === "number" && timeout > 0)
281
293
  data.timeout = timeout;
294
+ this.resuming = {
295
+ enabled: typeof resuming === "boolean" ? resuming : false,
296
+ timeout: typeof resuming === "boolean" && resuming === true ? timeout : null,
297
+ };
282
298
  return await this.request(`/sessions/${this.sessionId}`, r => {
283
299
  r.method = "PATCH";
284
300
  r.headers = { Authorization: this.options.authorization, 'Content-Type': 'application/json' };
@@ -329,6 +345,7 @@ export class LavalinkNode {
329
345
  * @returns
330
346
  */
331
347
  async fetchVersion() {
348
+ // need to adjust path for no-prefix version info
332
349
  return await this.request(`/version`, r => { r.path = "/version"; }, true);
333
350
  }
334
351
  /**
@@ -563,6 +580,10 @@ export class LavalinkNode {
563
580
  break;
564
581
  case "ready": // payload: { resumed: false, sessionId: 'ytva350aevn6n9n8', op: 'ready' }
565
582
  this.sessionId = payload.sessionId;
583
+ this.resuming.enabled = payload.resumed;
584
+ if (payload.resumed === true) {
585
+ this.NodeManager.emit("resumed", this, payload, await this.fetchAllPlayers());
586
+ }
566
587
  break;
567
588
  default:
568
589
  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;
@@ -1,7 +1,7 @@
1
1
  import { EventEmitter } from "stream";
2
2
  import { LavalinkNode } from "./Node";
3
- import { MiniMap } from "./Utils";
4
3
  import { DestroyReasons } from "./Player";
4
+ import { MiniMap } from "./Utils";
5
5
  export class NodeManager extends EventEmitter {
6
6
  nodes = new MiniMap();
7
7
  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;
@@ -119,15 +119,38 @@ export class Player {
119
119
  * Play the next track from the queue / a specific track, with playoptions for Lavalink
120
120
  * @param options
121
121
  */
122
- async play(options) {
122
+ async play(options = {}) {
123
123
  if (this.get("internal_queueempty")) {
124
124
  clearTimeout(this.get("internal_queueempty"));
125
125
  this.set("internal_queueempty", undefined);
126
126
  }
127
- if (options?.track && (this.LavalinkManager.utils.isTrack(options?.track) || this.LavalinkManager.utils.isUnresolvedTrack(options.track))) {
128
- if (this.LavalinkManager.utils.isUnresolvedTrack(options.track))
129
- await options.track.resolve(this);
130
- await this.queue.add(options?.track, 0);
127
+ // if clientTrack provided, play it
128
+ if (options?.clientTrack && (this.LavalinkManager.utils.isTrack(options?.clientTrack) || this.LavalinkManager.utils.isUnresolvedTrack(options.clientTrack))) {
129
+ if (this.LavalinkManager.utils.isUnresolvedTrack(options.clientTrack))
130
+ await options.clientTrack.resolve(this);
131
+ if (typeof options.track.userData === "object")
132
+ options.clientTrack.userData = { ...(options?.clientTrack.userData || {}), ...(options.track.userData || {}) };
133
+ await this.queue.add(options?.clientTrack, 0);
134
+ await queueTrackEnd(this);
135
+ }
136
+ else if (options?.track?.encoded) {
137
+ // handle play encoded options manually // TODO let it resolve by lavalink!
138
+ 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);
139
+ if (typeof options.track.userData === "object")
140
+ track.userData = { ...(track.userData || {}), ...(options.track.userData || {}) };
141
+ if (track)
142
+ this.queue.add(track, 0);
143
+ await queueTrackEnd(this);
144
+ }
145
+ else if (options?.track?.identifier) {
146
+ // handle play identifier options manually // TODO let it resolve by lavalink!
147
+ const res = await this.search({
148
+ query: options?.track?.identifier
149
+ }, options?.track?.identifier || this.queue?.current?.requester || this.queue.previous?.[0]?.requester || this.queue.tracks?.[0]?.requester || this.LavalinkManager.options.client);
150
+ if (typeof options.track.userData === "object")
151
+ res.tracks[0].userData = { ...(res.tracks[0].userData || {}), ...(options.track.userData || {}) };
152
+ if (res.tracks[0])
153
+ this.queue.add(res.tracks[0], 0);
131
154
  await queueTrackEnd(this);
132
155
  }
133
156
  if (!this.queue.current && this.queue.tracks.length)
@@ -136,20 +159,22 @@ export class Player {
136
159
  try {
137
160
  // resolve the unresolved track
138
161
  await this.queue.current.resolve(this);
162
+ if (typeof options.track.userData === "object")
163
+ this.queue.current.userData = { ...(this.queue.current.userData || {}), ...(options.track.userData || {}) };
139
164
  }
140
165
  catch (error) {
141
166
  this.LavalinkManager.emit("trackError", this, this.queue.current, error);
167
+ if (options && "clientTrack" in options)
168
+ delete options.clientTrack;
142
169
  if (options && "track" in options)
143
170
  delete options.track;
144
- if (options && "encodedTrack" in options)
145
- delete options.encodedTrack;
146
- if (this.queue.tracks[0])
171
+ // try to play the next track if possible
172
+ if (this.LavalinkManager.options?.autoSkipOnResolveError === true && this.queue.tracks[0])
147
173
  return this.play(options);
148
174
  return this;
149
175
  }
150
176
  }
151
- const track = this.queue.current;
152
- if (!track)
177
+ if (!this.queue.current)
153
178
  throw new Error(`There is no Track in the Queue, nor provided in the PlayOptions`);
154
179
  if (typeof options?.volume === "number" && !isNaN(options?.volume)) {
155
180
  this.volume = Math.max(Math.min(options?.volume, 500), 0);
@@ -159,24 +184,27 @@ export class Player {
159
184
  this.lavalinkVolume = Math.round(vol);
160
185
  options.volume = this.lavalinkVolume;
161
186
  }
162
- const finalOptions = {
163
- encodedTrack: track.encoded,
187
+ const finalOptions = Object.fromEntries(Object.entries({
188
+ track: {
189
+ encoded: this.queue.current?.encoded || null,
190
+ // identifier: options.identifier,
191
+ userData: options?.track?.userData || {},
192
+ },
164
193
  volume: this.lavalinkVolume,
165
- position: 0,
166
- ...options,
167
- };
168
- if ("track" in finalOptions)
169
- delete finalOptions.track;
170
- if ((typeof finalOptions.position !== "undefined" && isNaN(finalOptions.position)) || (typeof finalOptions.position === "number" && (finalOptions.position < 0 || finalOptions.position >= track.info.duration)))
194
+ position: options?.position ?? 0,
195
+ endTime: options?.endTime ?? undefined,
196
+ filters: options?.filters ?? undefined,
197
+ paused: options?.paused ?? undefined,
198
+ voice: options?.voice ?? undefined
199
+ }).filter(v => typeof v[1] !== "undefined"));
200
+ if ((typeof finalOptions.position !== "undefined" && isNaN(finalOptions.position)) || (typeof finalOptions.position === "number" && (finalOptions.position < 0 || finalOptions.position >= this.queue.current.info.duration)))
171
201
  throw new Error("PlayerOption#position must be a positive number, less than track's duration");
172
202
  if ((typeof finalOptions.volume !== "undefined" && isNaN(finalOptions.volume) || (typeof finalOptions.volume === "number" && finalOptions.volume < 0)))
173
203
  throw new Error("PlayerOption#volume must be a positive number");
174
- if ((typeof finalOptions.endTime !== "undefined" && isNaN(finalOptions.endTime)) || (typeof finalOptions.endTime === "number" && (finalOptions.endTime < 0 || finalOptions.endTime >= track.info.duration)))
204
+ if ((typeof finalOptions.endTime !== "undefined" && isNaN(finalOptions.endTime)) || (typeof finalOptions.endTime === "number" && (finalOptions.endTime < 0 || finalOptions.endTime >= this.queue.current.info.duration)))
175
205
  throw new Error("PlayerOption#endTime must be a positive number, less than track's duration");
176
206
  if (typeof finalOptions.position === "number" && typeof finalOptions.endTime === "number" && finalOptions.endTime < finalOptions.position)
177
207
  throw new Error("PlayerOption#endTime must be bigger than PlayerOption#position");
178
- if ("noReplace" in finalOptions)
179
- delete finalOptions.noReplace;
180
208
  const now = performance.now();
181
209
  await this.node.updatePlayer({
182
210
  guildId: this.guildId,
@@ -302,7 +330,7 @@ export class Player {
302
330
  if (!this.playing)
303
331
  return await this.play();
304
332
  const now = performance.now();
305
- await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { encodedTrack: null } });
333
+ await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { track: { encoded: null } } });
306
334
  this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
307
335
  return this;
308
336
  }
@@ -322,7 +350,7 @@ export class Player {
322
350
  this.set("internal_autoplayStopPlaying", undefined);
323
351
  const now = performance.now();
324
352
  // send to lavalink, that it should stop playing
325
- await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { encodedTrack: null } });
353
+ await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { track: { encoded: null } } });
326
354
  this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
327
355
  return this;
328
356
  }