lavalink-client 2.9.11 → 2.10.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.
package/dist/index.js CHANGED
@@ -339,9 +339,6 @@ var NodeLinkExclusiveEvents = [
339
339
  "LyricsNotFoundEvent"
340
340
  ];
341
341
 
342
- // src/structures/NodeManager.ts
343
- import { EventEmitter } from "events";
344
-
345
342
  // src/structures/Node.ts
346
343
  import { isAbsolute } from "path";
347
344
  import WebSocket from "ws";
@@ -354,6 +351,11 @@ var ReconnectionState = /* @__PURE__ */ ((ReconnectionState2) => {
354
351
  ReconnectionState2["DESTROYING"] = "DESTROYING";
355
352
  return ReconnectionState2;
356
353
  })(ReconnectionState || {});
354
+ var NodeType = /* @__PURE__ */ ((NodeType2) => {
355
+ NodeType2["Lavalink"] = "Lavalink";
356
+ NodeType2["NodeLink"] = "NodeLink";
357
+ return NodeType2;
358
+ })(NodeType || {});
357
359
 
358
360
  // src/structures/Utils.ts
359
361
  import { URL as URL2 } from "url";
@@ -525,12 +527,14 @@ var QueueSymbol = /* @__PURE__ */ Symbol("LC-Queue");
525
527
  var NodeSymbol = /* @__PURE__ */ Symbol("LC-Node");
526
528
  var escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
527
529
  function parseLavalinkConnUrl(connectionUrl) {
528
- if (!connectionUrl.startsWith("lavalink://") && !connectionUrl.startsWith("nodelink://"))
530
+ if (!connectionUrl) throw new Error("ConnectionUrl is required");
531
+ const lowered = connectionUrl.toLowerCase();
532
+ if (!lowered.startsWith("lavalink://") && !lowered.startsWith("nodelink://"))
529
533
  throw new Error(`ConnectionUrl (${connectionUrl}) must start with 'lavalink://' or 'nodelink://'`);
530
534
  const parsed = new URL2(connectionUrl);
531
535
  return {
532
536
  authorization: parsed.password,
533
- nodeType: connectionUrl.startsWith("lavalink://") ? "Lavalink" : "NodeLink",
537
+ nodeType: lowered.startsWith("lavalink://") ? "Lavalink" /* Lavalink */ : "NodeLink" /* NodeLink */,
534
538
  id: parsed.username,
535
539
  host: parsed.hostname,
536
540
  port: Number(parsed.port)
@@ -1184,7 +1188,7 @@ var LavalinkNode = class _LavalinkNode {
1184
1188
  heartBeatPongTimestamp = 0;
1185
1189
  heartBeatInterval;
1186
1190
  pingTimeout;
1187
- nodeType = "Lavalink";
1191
+ nodeType = "Lavalink" /* Lavalink */;
1188
1192
  isAlive = false;
1189
1193
  static _NodeLinkClass = null;
1190
1194
  /** The provided Options of the Node */
@@ -1258,7 +1262,7 @@ var LavalinkNode = class _LavalinkNode {
1258
1262
  * Returns wether the plugin validations are enabled or not
1259
1263
  */
1260
1264
  get _checkForPlugins() {
1261
- if (this.nodeType === "NodeLink") return false;
1265
+ if (this.nodeType === "NodeLink" /* NodeLink */) return false;
1262
1266
  return !!this.options?.autoChecks?.pluginValidations;
1263
1267
  }
1264
1268
  /**
@@ -1328,16 +1332,17 @@ var LavalinkNode = class _LavalinkNode {
1328
1332
  heartBeatInterval: 3e4,
1329
1333
  enablePingOnStatsCheck: true,
1330
1334
  closeOnError: true,
1335
+ nodeType: "Lavalink" /* Lavalink */,
1331
1336
  ...options,
1332
1337
  autoChecks: {
1333
1338
  sourcesValidations: options?.autoChecks?.sourcesValidations ?? true,
1334
1339
  pluginValidations: options?.autoChecks?.pluginValidations ?? true
1335
1340
  }
1336
1341
  };
1337
- if (this.options.nodeType === "NodeLink" && this.constructor.name === "LavalinkNode" && _LavalinkNode._NodeLinkClass) {
1342
+ if (this.options.nodeType === "NodeLink" /* NodeLink */ && this.constructor.name === "LavalinkNode" && _LavalinkNode._NodeLinkClass) {
1338
1343
  return new _LavalinkNode._NodeLinkClass(options, manager);
1339
1344
  }
1340
- this.nodeType = this.options.nodeType || "Lavalink";
1345
+ this.nodeType = this.options.nodeType;
1341
1346
  this.NodeManager = manager;
1342
1347
  this.validate();
1343
1348
  if (this.options.secure && this.options.port !== 443)
@@ -1476,10 +1481,8 @@ var LavalinkNode = class _LavalinkNode {
1476
1481
  if (Query.source) this._LManager.utils.validateSourceString(this, Query.source);
1477
1482
  if (/^https?:\/\//.test(Query.query))
1478
1483
  return this.search({ query: Query.query, source: Query.source }, requestUser);
1479
- if (!["spsearch", "sprec", "amsearch", "dzsearch", "dzisrc", "ytmsearch", "ytsearch"].includes(Query.source))
1480
- throw new SyntaxError(
1481
- `Query.source must be a source from LavaSrc: "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "ytmsearch" | "ytsearch"`
1482
- );
1484
+ if (!this.isLavaSrcSource(Query.source))
1485
+ throw new SyntaxError(`Query.source must be an available source from LavaSrc`);
1483
1486
  if (this._checkForPlugins && !this.info?.plugins?.find?.((v) => v.name === "lavasearch-plugin"))
1484
1487
  throw new RangeError(`there is no lavasearch-plugin available in the lavalink node: ${this.id}`);
1485
1488
  if (this._checkForPlugins && !this.info?.plugins?.find?.((v) => v.name === "lavasrc-plugin"))
@@ -1905,7 +1908,10 @@ var LavalinkNode = class _LavalinkNode {
1905
1908
  throw new RangeError(
1906
1909
  `there is no lyrics source (via lavasrc-plugin / java-lyrics-plugin) available in the lavalink node (required for lyrics): ${this.id}`
1907
1910
  );
1908
- const url = `/lyrics?track=${track.encoded}&skipTrackSource=${skipTrackSource}`;
1911
+ let url = `/lyrics?track=${track.encoded}&skipTrackSource=${skipTrackSource}`;
1912
+ if (this.nodeType === "NodeLink" /* NodeLink */) {
1913
+ url = `/loadlyrics?encodedTrack=${track.encoded}`;
1914
+ }
1909
1915
  return await this.request(url);
1910
1916
  },
1911
1917
  /**
@@ -1931,7 +1937,10 @@ var LavalinkNode = class _LavalinkNode {
1931
1937
  throw new RangeError(
1932
1938
  `there is no lyrics source (via lavasrc-plugin / java-lyrics-plugin) available in the lavalink node (required for lyrics): ${this.id}`
1933
1939
  );
1934
- const url = `/sessions/${this.sessionId}/players/${guildId}/track/lyrics?skipTrackSource=${skipTrackSource}`;
1940
+ let url = `/sessions/${this.sessionId}/players/${guildId}/track/lyrics?skipTrackSource=${skipTrackSource}`;
1941
+ if (this.nodeType === "NodeLink" /* NodeLink */) {
1942
+ url = `/loadlyrics?encodedTrack=${this._LManager.getPlayer(guildId)?.queue.current?.encoded}`;
1943
+ }
1935
1944
  return await this.request(url);
1936
1945
  },
1937
1946
  /**
@@ -2280,20 +2289,22 @@ var LavalinkNode = class _LavalinkNode {
2280
2289
  throw new SyntaxError("LavalinkNode.autoChecks.pluginValidations must be either false | true aka boolean");
2281
2290
  if (this.options.regions !== void 0 && (!Array.isArray(this.options.regions) || !this.options.regions.every((r) => typeof r === "string")))
2282
2291
  throw new SyntaxError("LavalinkNode.regions must be an Array of strings");
2292
+ if (this.options.nodeType && !NodeType[this.options.nodeType])
2293
+ throw new SyntaxError("LavalinkNode.nodeType must be a valid NodeType enum value");
2283
2294
  }
2284
2295
  /**
2285
2296
  * Checks if the node is a NodeLink node
2286
2297
  * @returns true if the node is a NodeLink node
2287
2298
  */
2288
2299
  isNodeLink() {
2289
- return this.nodeType === "NodeLink";
2300
+ return this.nodeType === "NodeLink" /* NodeLink */;
2290
2301
  }
2291
2302
  /**
2292
2303
  * Checks if the node is a Lavalink node
2293
2304
  * @returns true if the node is a Lavalink node
2294
2305
  */
2295
2306
  isLavalinkNode() {
2296
- return this.nodeType === "Lavalink";
2307
+ return this.nodeType === "Lavalink" /* Lavalink */;
2297
2308
  }
2298
2309
  /**
2299
2310
  * Sync the data of the player you make an action to lavalink to
@@ -3062,17 +3073,36 @@ var LavalinkNode = class _LavalinkNode {
3062
3073
  this._LManager.emit("LyricsNotFound", player, track, payload);
3063
3074
  return;
3064
3075
  }
3076
+ /**
3077
+ * @private
3078
+ * util function to check if a provided source is valid with current node.
3079
+ * @param {LavalinkSearchPlatform} src
3080
+ * @returns {boolean} True if provided source is valid.
3081
+ */
3082
+ isLavaSrcSource(src) {
3083
+ const source = /* @__PURE__ */ new Set([]);
3084
+ if (this.info?.sourceManagers.includes("spotify")) source.add("spsearch").add("sprec");
3085
+ if (this.info?.sourceManagers.includes("applemusic")) source.add("amsearch");
3086
+ if (this.info?.sourceManagers.includes("deezer")) source.add("dzsearch").add("dzrec").add("dzisrc");
3087
+ if (this.info?.sourceManagers.includes("yandexmusic")) source.add("ymsearch").add("ymrec");
3088
+ if (this.info?.sourceManagers.includes("vkmusic")) source.add("vksearch").add("vkrec");
3089
+ if (this.info?.sourceManagers.includes("tidal")) source.add("tdsearch").add("tdrec");
3090
+ if (this.info?.sourceManagers.includes("qobuz")) source.add("qbsearch").add("qbisrc").add("qbrec");
3091
+ if (this.info?.sourceManagers.includes("pandora")) source.add("pdsearch").add("pdisrc").add("pdrec");
3092
+ if (this.info?.sourceManagers.includes("youtube")) source.add("ytsearch").add("ytmsearch");
3093
+ return typeof src === "string" && source.has(src);
3094
+ }
3065
3095
  };
3066
3096
 
3067
3097
  // src/structures/NodeLink.ts
3068
3098
  var NodeLinkNode = class extends LavalinkNode {
3069
- nodeType = "NodeLink";
3099
+ nodeType = "NodeLink" /* NodeLink */;
3070
3100
  constructor(options, manager) {
3071
3101
  super(options, manager);
3072
- if (this.options.nodeType === "Lavalink" && this.constructor.name === "NodeLink") {
3102
+ if (this.options.nodeType === "Lavalink" /* Lavalink */ && (this.constructor.name === "NodeLinkNode" || this.constructor.name === "NodeLink")) {
3073
3103
  return new LavalinkNode(options, manager);
3074
3104
  }
3075
- this.nodeType = "NodeLink";
3105
+ this.nodeType = "NodeLink" /* NodeLink */;
3076
3106
  }
3077
3107
  /**
3078
3108
  * Uses the gapless feature to set the next track to be played.
@@ -3413,6 +3443,7 @@ var NodeLinkNode = class extends LavalinkNode {
3413
3443
  LavalinkNode._NodeLinkClass = NodeLinkNode;
3414
3444
 
3415
3445
  // src/structures/NodeManager.ts
3446
+ import { EventEmitter } from "events";
3416
3447
  var NodeManager = class extends EventEmitter {
3417
3448
  /**
3418
3449
  * Emit an event
@@ -3474,9 +3505,7 @@ var NodeManager = class extends EventEmitter {
3474
3505
  super();
3475
3506
  this.LavalinkManager = LavalinkManager2;
3476
3507
  if (this.LavalinkManager.options.nodes)
3477
- this.LavalinkManager.options.nodes.forEach((node) => {
3478
- this.createNode(node);
3479
- });
3508
+ this.LavalinkManager.options.nodes.forEach((node) => this.createNode(node));
3480
3509
  }
3481
3510
  /**
3482
3511
  * Disconnects all Nodes from lavalink ws sockets
@@ -3537,9 +3566,21 @@ var NodeManager = class extends EventEmitter {
3537
3566
  * @returns The node that was created
3538
3567
  */
3539
3568
  createNode(options) {
3569
+ if (options instanceof NodeLinkNode) {
3570
+ const preExistingNode = this.nodes.get(options.id);
3571
+ if (preExistingNode) return preExistingNode;
3572
+ this.nodes.set(options.id, options);
3573
+ return options;
3574
+ }
3575
+ if (options instanceof LavalinkNode) {
3576
+ const preExistingNode = this.nodes.get(options.id);
3577
+ if (preExistingNode) return preExistingNode;
3578
+ this.nodes.set(options.id, options);
3579
+ return options;
3580
+ }
3540
3581
  if (this.nodes.has(options.id || `${options.host}:${options.port}`))
3541
3582
  return this.nodes.get(options.id || `${options.host}:${options.port}`);
3542
- const newNode = options.nodeType === "NodeLink" ? new NodeLinkNode(options, this) : new LavalinkNode(options, this);
3583
+ const newNode = options.nodeType === "NodeLink" /* NodeLink */ ? new NodeLinkNode(options, this) : new LavalinkNode(options, this);
3543
3584
  this.nodes.set(newNode.id, newNode);
3544
3585
  return newNode;
3545
3586
  }
@@ -3548,48 +3589,57 @@ var NodeManager = class extends EventEmitter {
3548
3589
  * @param sortType The type of sorting to use
3549
3590
  * @returns
3550
3591
  */
3551
- leastUsedNodes(sortType = "players") {
3592
+ leastUsedNodes(sortType = "players", filterForNodeTypes) {
3593
+ const normalizedFilterForNodeTypes = filterForNodeTypes?.length ? filterForNodeTypes : ["Lavalink" /* Lavalink */, "NodeLink" /* NodeLink */];
3552
3594
  const connectedNodes = Array.from(this.nodes.values()).filter((node) => node.connected);
3595
+ const normalizedNodeTypes = new Set(
3596
+ normalizedFilterForNodeTypes.map(
3597
+ (nodeTypeFilter) => Object.values(NodeType).includes(nodeTypeFilter) ? nodeTypeFilter : nodeTypeFilter.nodeType
3598
+ )
3599
+ );
3600
+ const filteredConnectedNodes = connectedNodes.filter((node) => normalizedNodeTypes.has(node.nodeType));
3553
3601
  switch (sortType) {
3554
3602
  case "memory":
3555
3603
  {
3556
- return connectedNodes.sort((a, b) => (a.stats?.memory?.used || 0) - (b.stats?.memory?.used || 0));
3604
+ return filteredConnectedNodes.sort(
3605
+ (a, b) => (a.stats?.memory?.used || 0) - (b.stats?.memory?.used || 0)
3606
+ );
3557
3607
  }
3558
3608
  break;
3559
3609
  case "cpuLavalink":
3560
3610
  {
3561
- return connectedNodes.sort(
3611
+ return filteredConnectedNodes.sort(
3562
3612
  (a, b) => (a.stats?.cpu?.lavalinkLoad || 0) - (b.stats?.cpu?.lavalinkLoad || 0)
3563
3613
  );
3564
3614
  }
3565
3615
  break;
3566
3616
  case "cpuSystem":
3567
3617
  {
3568
- return connectedNodes.sort(
3618
+ return filteredConnectedNodes.sort(
3569
3619
  (a, b) => (a.stats?.cpu?.systemLoad || 0) - (b.stats?.cpu?.systemLoad || 0)
3570
3620
  );
3571
3621
  }
3572
3622
  break;
3573
3623
  case "calls":
3574
3624
  {
3575
- return connectedNodes.sort((a, b) => a.calls - b.calls);
3625
+ return filteredConnectedNodes.sort((a, b) => a.calls - b.calls);
3576
3626
  }
3577
3627
  break;
3578
3628
  case "playingPlayers":
3579
3629
  {
3580
- return connectedNodes.sort(
3630
+ return filteredConnectedNodes.sort(
3581
3631
  (a, b) => (a.stats?.playingPlayers || 0) - (b.stats?.playingPlayers || 0)
3582
3632
  );
3583
3633
  }
3584
3634
  break;
3585
3635
  case "players":
3586
3636
  {
3587
- return connectedNodes.sort((a, b) => (a.stats?.players || 0) - (b.stats?.players || 0));
3637
+ return filteredConnectedNodes.sort((a, b) => (a.stats?.players || 0) - (b.stats?.players || 0));
3588
3638
  }
3589
3639
  break;
3590
3640
  default:
3591
3641
  {
3592
- return connectedNodes.sort((a, b) => (a.stats?.players || 0) - (b.stats?.players || 0));
3642
+ return filteredConnectedNodes.sort((a, b) => (a.stats?.players || 0) - (b.stats?.players || 0));
3593
3643
  }
3594
3644
  break;
3595
3645
  }
@@ -3622,13 +3672,22 @@ var NodeManager = class extends EventEmitter {
3622
3672
  }
3623
3673
  /**
3624
3674
  * Get a node from the nodeManager
3625
- * @param node The node to get
3675
+ * @param node The node to get either by idetnifier, by class or by enum
3626
3676
  * @returns The node that was retrieved
3627
3677
  */
3628
3678
  getNode(node) {
3679
+ if (!!node && Object.values(NodeType).includes(node)) {
3680
+ return this.leastUsedNodes().filter((node2) => node2.nodeType === node2)[0];
3681
+ }
3682
+ if (!!node && node instanceof NodeLinkNode) {
3683
+ return this.leastUsedNodes().filter((node2) => node2 instanceof NodeLinkNode)[0];
3684
+ }
3685
+ if (!!node && node instanceof LavalinkNode) {
3686
+ return this.leastUsedNodes().filter((node2) => node2 instanceof LavalinkNode)[0];
3687
+ }
3629
3688
  const decodeNode = typeof node === "string" ? this.nodes.get(node) : node;
3630
3689
  if (!decodeNode) return void 0;
3631
- if (decodeNode.nodeType === "NodeLink") return decodeNode;
3690
+ if (decodeNode.nodeType === "NodeLink" /* NodeLink */) return decodeNode;
3632
3691
  return decodeNode;
3633
3692
  }
3634
3693
  };
@@ -5991,7 +6050,7 @@ var Player = class {
5991
6050
  );
5992
6051
  if (this.queue.current || this.queue.tracks.length) {
5993
6052
  const trackSources = new Set(
5994
- [this.queue.current, ...this.queue.tracks].map((track) => track.info.sourceName)
6053
+ [this.queue.current, ...this.queue.tracks].map((track) => track?.info?.sourceName).filter(Boolean)
5995
6054
  );
5996
6055
  const missingSources = [...trackSources].filter(
5997
6056
  (source) => !updateNode.info?.sourceManagers.includes(source)
@@ -6267,9 +6326,11 @@ var LavalinkManager = class _LavalinkManager extends EventEmitter2 {
6267
6326
  throw new SyntaxError("ManagerOption.autoSkipOnResolveError must be either false | true aka boolean");
6268
6327
  if (options?.emitNewSongsOnly && typeof options?.emitNewSongsOnly !== "boolean")
6269
6328
  throw new SyntaxError("ManagerOption.emitNewSongsOnly must be either false | true aka boolean");
6270
- if (!options?.nodes || !Array.isArray(options?.nodes) || !options?.nodes.every((node) => this.utils.isNodeOptions(node)))
6329
+ if (!options?.nodes || !Array.isArray(options?.nodes) || !options?.nodes.every(
6330
+ (node) => node instanceof NodeLinkNode || node instanceof LavalinkNode || this.utils.isNodeOptions(node)
6331
+ ))
6271
6332
  throw new SyntaxError(
6272
- "ManagerOption.nodes must be an Array of NodeOptions and is required of at least 1 Node"
6333
+ "ManagerOption.nodes must be an Array of NodeOptions or the Node-Classes 'NodeLinkNode' or 'LavalinkNode' and is required of at least 1 Node"
6273
6334
  );
6274
6335
  if (options?.queueOptions?.queueStore) {
6275
6336
  const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(options?.queueOptions?.queueStore));
@@ -6325,48 +6386,50 @@ var LavalinkManager = class _LavalinkManager extends EventEmitter2 {
6325
6386
  * port: 2333,
6326
6387
  * id: "testnode"
6327
6388
  * },
6389
+ * // you can also use the util like this, and it will return a valid node option object. must start with: lavalink:// | nodelink://
6390
+ * // parseLavalinkConnUrl("nodelink://<nodeId>:<nodeAuthorization(Password)>@<NodeHost>:<NodePort>")
6328
6391
  * sendToShard(guildId, payload) => client.guilds.cache.get(guildId)?.shard?.send(payload),
6329
- * client: {
6330
- * id: process.env.CLIENT_ID,
6331
- * username: "TESTBOT"
6332
- * },
6333
- * // optional Options:
6334
- * autoSkip: true,
6335
- * playerOptions: {
6336
- * applyVolumeAsFilter: false,
6337
- * clientBasedPositionUpdateInterval: 150,
6338
- * defaultSearchPlatform: "ytmsearch",
6339
- * allowCustomSources: false,
6340
- * volumeDecrementer: 0.75,
6341
- * //requesterTransformer: YourRequesterTransformerFunction,
6342
- * onDisconnect: {
6343
- * autoReconnect: true,
6344
- * destroyPlayer: false
6345
- * },
6346
- * onEmptyQueue: {
6347
- * destroyAfterMs: 30_000,
6348
- * //autoPlayFunction: YourAutoplayFunction,
6349
- * },
6350
- * useUnresolvedData: true
6392
+ * ],
6393
+ * client: {
6394
+ * id: process.env.CLIENT_ID,
6395
+ * username: "TESTBOT"
6396
+ * },
6397
+ * // optional Options:
6398
+ * autoSkip: true,
6399
+ * playerOptions: {
6400
+ * applyVolumeAsFilter: false,
6401
+ * clientBasedPositionUpdateInterval: 150,
6402
+ * defaultSearchPlatform: "ytmsearch",
6403
+ * allowCustomSources: false,
6404
+ * volumeDecrementer: 0.75,
6405
+ * //requesterTransformer: YourRequesterTransformerFunction,
6406
+ * onDisconnect: {
6407
+ * autoReconnect: true,
6408
+ * destroyPlayer: false
6351
6409
  * },
6352
- * queueOptions: {
6353
- * maxPreviousTracks: 25,
6354
- * //queueStore: yourCustomQueueStoreManagerClass,
6355
- * //queueChangesWatcher: yourCustomQueueChangesWatcherClass
6410
+ * onEmptyQueue: {
6411
+ * destroyAfterMs: 30_000,
6412
+ * //autoPlayFunction: YourAutoplayFunction,
6356
6413
  * },
6357
- * linksBlacklist: [],
6358
- * linksWhitelist: [],
6359
- * advancedOptions: {
6360
- * maxFilterFixDuration: 600_000,
6361
- * debugOptions: {
6362
- * noAudio: false,
6363
- * playerDestroy: {
6364
- * dontThrowError: false,
6365
- * debugLogs: false
6366
- * }
6414
+ * useUnresolvedData: true
6415
+ * },
6416
+ * queueOptions: {
6417
+ * maxPreviousTracks: 25,
6418
+ * //queueStore: yourCustomQueueStoreManagerClass,
6419
+ * //queueChangesWatcher: yourCustomQueueChangesWatcherClass
6420
+ * },
6421
+ * linksBlacklist: [],
6422
+ * linksWhitelist: [],
6423
+ * advancedOptions: {
6424
+ * maxFilterFixDuration: 600_000,
6425
+ * debugOptions: {
6426
+ * noAudio: false,
6427
+ * playerDestroy: {
6428
+ * dontThrowError: false,
6429
+ * debugLogs: false
6367
6430
  * }
6368
6431
  * }
6369
- * ]
6432
+ * }
6370
6433
  * })
6371
6434
  * ```
6372
6435
  */
@@ -6774,6 +6837,7 @@ export {
6774
6837
  NodeLinkNode,
6775
6838
  NodeManager,
6776
6839
  NodeSymbol,
6840
+ NodeType,
6777
6841
  Player,
6778
6842
  Queue,
6779
6843
  QueueSaver,