magmastream 2.10.3-dev.2 → 2.10.3

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.
@@ -21,6 +21,45 @@ function escapeRegExp(value) {
21
21
  }
22
22
  const YOUTUBE_URL_PATTERN = /(youtube\.com|youtu\.be)/;
23
23
  const BLOCKED_WORDS_PATTERN = new RegExp(`\\b(${blockedWords_1.blockedWords.map(escapeRegExp).join("|")})\\b`, "gi");
24
+ const PORTABLE_SEARCH_PREFIXES = new Set(Object.values(Enums_1.SearchPlatform).map((source) => source.toLowerCase()));
25
+ const NODELINK_SEARCH_PREFIXES = new Set(Object.values(Enums_1.NodeLinkSearchPlatform).map((source) => source.toLowerCase()));
26
+ const NODELINK_RECOMMENDATION_PREFIXES = new Set(Object.values(Enums_1.NodeLinkRecommendationPlatform).map((source) => source.toLowerCase()));
27
+ const NODELINK_ONLY_SEARCH_PREFIXES = new Set([...NODELINK_SEARCH_PREFIXES].filter((source) => !PORTABLE_SEARCH_PREFIXES.has(source)));
28
+ const NODELINK_SEARCH_TYPE_MODIFIERS = new Set(Object.values(Enums_1.NodeLinkSearchType).map((type) => type.toLowerCase()));
29
+ const KNOWN_LOADTRACKS_PREFIXES = new Set([
30
+ ...Object.values(Enums_1.SearchPlatform).map((source) => source.toLowerCase()),
31
+ ...NODELINK_SEARCH_PREFIXES,
32
+ ...NODELINK_RECOMMENDATION_PREFIXES,
33
+ "search",
34
+ "local",
35
+ "sprec",
36
+ "dzrec",
37
+ "tdrec",
38
+ "vkrec",
39
+ "qbrec",
40
+ "ymrec",
41
+ "ytrec",
42
+ "jsrec",
43
+ "lmrec",
44
+ ]);
45
+ const NODELINK_ONLY_LOADTRACKS_PREFIXES = new Set(["ytrec", "jsrec", "lmrec"]);
46
+ const SEARCH_TYPE_PATTERN = /^[A-Za-z0-9]+$/;
47
+ function getIdentifierPrefix(identifier) {
48
+ const match = /^([A-Za-z0-9]+):/.exec(identifier);
49
+ return match?.[1]?.toLowerCase() ?? null;
50
+ }
51
+ function getIdentifierSearchType(identifier) {
52
+ const match = /^[A-Za-z0-9]+:([A-Za-z0-9]+):/.exec(identifier);
53
+ return match?.[1]?.toLowerCase() ?? null;
54
+ }
55
+ function getNodeLoad(node) {
56
+ const cores = node.stats.cpu?.cores ?? 0;
57
+ const nodeLinkLoad = node.stats.cpu?.nodelinkLoad;
58
+ if (typeof nodeLinkLoad === "number" && Number.isFinite(nodeLinkLoad))
59
+ return nodeLinkLoad * 100;
60
+ const load = node.stats.cpu?.lavalinkLoad ?? 0;
61
+ return cores > 0 && Number.isFinite(load) ? (load / cores) * 100 : 0;
62
+ }
24
63
  /**
25
64
  * The main hub for interacting with Lavalink and using Magmastream.
26
65
  */
@@ -202,19 +241,27 @@ class Manager extends events_1.EventEmitter {
202
241
  * @returns The search result.
203
242
  */
204
243
  async search(query, requester) {
205
- const node = this.useableNode;
244
+ const _query = typeof query === "string" ? { query } : query;
245
+ const _source = _query.source ?? this.options.defaultSearchPlatform ?? Enums_1.SearchPlatform.YouTube;
246
+ const isUrl = /^https?:\/\//.test(_query.query);
247
+ const isPrefixedIdentifier = this.isPrefixedLoadTracksIdentifier(_query.query);
248
+ const searchType = this.normalizeSearchType(_query.searchType);
249
+ const search = this.buildLoadTracksIdentifier(_source, _query.query, searchType, isUrl, isPrefixedIdentifier);
250
+ const node = this.getSearchNode(_source, search, searchType);
206
251
  if (!node) {
207
252
  throw new MagmastreamError_1.MagmaStreamError({
208
253
  code: Enums_1.MagmaStreamErrorCode.MANAGER_NO_NODES,
209
- message: "No available nodes to perform the search.",
254
+ message: this.requiresNodeLinkSearchNode(_source, search, searchType)
255
+ ? "No available NodeLink nodes to perform the NodeLink-specific search."
256
+ : this.requiresLavalinkSearchNode(search)
257
+ ? "No available Lavalink nodes to perform the Lavalink-specific search."
258
+ : "No available nodes to perform the search.",
210
259
  context: { query, requester },
211
260
  });
212
261
  }
213
- const _query = typeof query === "string" ? { query } : query;
214
- const _source = _query.source ?? this.options.defaultSearchPlatform;
215
- const isUrl = /^https?:\/\//.test(_query.query);
216
- const search = isUrl ? _query.query : `${_source}:${_query.query}`;
217
- this.emit(Enums_1.ManagerEventTypes.Debug, isUrl ? `[MANAGER] Performing search for: ${_query.query}` : `[MANAGER] Performing ${_source} search for: ${_query.query}`);
262
+ this.emit(Enums_1.ManagerEventTypes.Debug, isUrl || isPrefixedIdentifier
263
+ ? `[MANAGER] Performing search on ${node.options.identifier} for: ${search}`
264
+ : `[MANAGER] Performing ${_source}${searchType ? `:${searchType}` : ""} search on ${node.options.identifier} for: ${_query.query}`);
218
265
  try {
219
266
  const lavalinkResponse = (await node.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(search)}`));
220
267
  if (!lavalinkResponse) {
@@ -231,6 +278,7 @@ class Manager extends events_1.EventEmitter {
231
278
  result = { loadType: lavalinkResponse.loadType, tracks };
232
279
  break;
233
280
  }
281
+ case Enums_1.LoadTypes.Episode:
234
282
  case Enums_1.LoadTypes.Short:
235
283
  case Enums_1.LoadTypes.Track: {
236
284
  const track = Utils_1.TrackUtils.build(lavalinkResponse.data, requester);
@@ -250,7 +298,8 @@ class Manager extends events_1.EventEmitter {
250
298
  tracks,
251
299
  playlist: {
252
300
  name: playlistData.info.name,
253
- playlistInfo: playlistData.pluginInfo,
301
+ playlistInfo: playlistData.info,
302
+ pluginInfo: playlistData.pluginInfo,
254
303
  requester: requester,
255
304
  tracks,
256
305
  duration: tracks.reduce((acc, cur) => acc + (cur.duration || 0), 0),
@@ -263,7 +312,7 @@ class Manager extends events_1.EventEmitter {
263
312
  }
264
313
  if (this.options.normalizeYouTubeTitles && "tracks" in result) {
265
314
  const processTrack = (track) => {
266
- if (!YOUTUBE_URL_PATTERN.test(track.uri))
315
+ if (!track.uri || !YOUTUBE_URL_PATTERN.test(track.uri))
267
316
  return track;
268
317
  const { cleanTitle, cleanAuthor } = this.parseYouTubeTitle(track.title, track.author);
269
318
  track.title = cleanTitle;
@@ -406,23 +455,21 @@ class Manager extends events_1.EventEmitter {
406
455
  */
407
456
  async decodeTracks(tracks) {
408
457
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Decoding tracks: ${Utils_1.JSONUtils.safe(tracks, 2)}`);
409
- return new Promise(async (resolve, reject) => {
410
- const node = this.useableNode;
411
- if (!node) {
412
- throw new MagmastreamError_1.MagmaStreamError({
413
- code: Enums_1.MagmaStreamErrorCode.MANAGER_NO_NODES,
414
- message: "No available nodes to decode tracks.",
415
- });
416
- }
417
- const decodedTrackData = (await node.rest.post("/v4/decodetracks", Utils_1.JSONUtils.safe(tracks, 2)).catch((err) => reject(err)));
418
- if (!decodedTrackData) {
419
- throw new MagmastreamError_1.MagmaStreamError({
420
- code: Enums_1.MagmaStreamErrorCode.REST_REQUEST_FAILED,
421
- message: "No decoded tracks returned from node.",
422
- });
423
- }
424
- return resolve(decodedTrackData);
425
- });
458
+ const node = this.useableNode;
459
+ if (!node) {
460
+ throw new MagmastreamError_1.MagmaStreamError({
461
+ code: Enums_1.MagmaStreamErrorCode.MANAGER_NO_NODES,
462
+ message: "No available nodes to decode tracks.",
463
+ });
464
+ }
465
+ const decodedTrackData = await node.rest.decodeTracks(tracks);
466
+ if (!decodedTrackData) {
467
+ throw new MagmastreamError_1.MagmaStreamError({
468
+ code: Enums_1.MagmaStreamErrorCode.REST_REQUEST_FAILED,
469
+ message: "No decoded tracks returned from node.",
470
+ });
471
+ }
472
+ return decodedTrackData;
426
473
  }
427
474
  /**
428
475
  * Decodes a base64 encoded track and returns a TrackData.
@@ -607,14 +654,28 @@ class Manager extends events_1.EventEmitter {
607
654
  const filterActions = {
608
655
  bassboost: () => player.filters.bassBoost(state.filters.bassBoostlevel),
609
656
  distort: (isEnabled) => player.filters.distort(isEnabled),
657
+ pluginFilters: () => player.filters.setPluginFilters(state.filters.pluginFilters),
658
+ setChannelMix: () => player.filters.setChannelMix(state.filters.channelMix),
610
659
  setDistortion: () => player.filters.setDistortion(state.filters.distortion),
611
660
  eightD: (isEnabled) => player.filters.eightD(isEnabled),
612
661
  setKaraoke: () => player.filters.setKaraoke(state.filters.karaoke),
662
+ setLowPass: () => player.filters.setLowPass(state.filters.lowPass),
663
+ setHighPass: () => player.filters.setHighPass(state.filters.highpass),
664
+ setEcho: () => player.filters.setEcho(state.filters.echo),
665
+ setReverb: () => player.filters.setReverb(state.filters.reverb),
666
+ setChorus: () => player.filters.setChorus(state.filters.chorus),
667
+ setCompressor: () => player.filters.setCompressor(state.filters.compressor),
668
+ setFlanger: () => player.filters.setFlanger(state.filters.flanger),
669
+ setPhaser: () => player.filters.setPhaser(state.filters.phaser),
670
+ setPhonograph: () => player.filters.setPhonograph(state.filters.phonograph),
671
+ setSpatial: () => player.filters.setSpatial(state.filters.spatial),
672
+ setTape: () => player.filters.setTape(state.filters.tape),
613
673
  nightcore: (isEnabled) => player.filters.nightcore(isEnabled),
614
674
  slowmo: (isEnabled) => player.filters.slowmo(isEnabled),
615
675
  soft: (isEnabled) => player.filters.soft(isEnabled),
616
676
  trebleBass: (isEnabled) => player.filters.trebleBass(isEnabled),
617
677
  setTimescale: () => player.filters.setTimescale(state.filters.timescale),
678
+ setTremolo: () => player.filters.setTremolo(state.filters.tremolo),
618
679
  tv: (isEnabled) => player.filters.tv(isEnabled),
619
680
  vibrato: () => player.filters.setVibrato(state.filters.vibrato),
620
681
  vaporwave: (isEnabled) => player.filters.vaporwave(isEnabled),
@@ -717,6 +778,87 @@ class Manager extends events_1.EventEmitter {
717
778
  this.emit(Enums_1.ManagerEventTypes.Debug, "[MANAGER] Finished loading saved players.");
718
779
  this.emit(Enums_1.ManagerEventTypes.RestoreComplete, node);
719
780
  }
781
+ normalizeSearchType(searchType) {
782
+ if (searchType === undefined || searchType === null)
783
+ return null;
784
+ if (typeof searchType !== "string" || !SEARCH_TYPE_PATTERN.test(searchType)) {
785
+ throw new MagmastreamError_1.MagmaStreamError({
786
+ code: Enums_1.MagmaStreamErrorCode.MANAGER_SEARCH_FAILED,
787
+ message: 'Search option "searchType" must be an alphanumeric NodeLink search type modifier.',
788
+ context: { searchType },
789
+ });
790
+ }
791
+ return searchType.toLowerCase();
792
+ }
793
+ buildLoadTracksIdentifier(source, query, searchType, isUrl, isPrefixedIdentifier) {
794
+ if (isUrl)
795
+ return query;
796
+ if (isPrefixedIdentifier) {
797
+ if (!searchType || this.hasNodeLinkSearchTypeModifier(query))
798
+ return query;
799
+ const prefix = getIdentifierPrefix(query);
800
+ if (!prefix)
801
+ return query;
802
+ return `${prefix}:${searchType}:${query.slice(prefix.length + 1)}`;
803
+ }
804
+ return searchType ? `${source}:${searchType}:${query}` : `${source}:${query}`;
805
+ }
806
+ isNodeLinkSearchSource(source) {
807
+ return typeof source === "string" && (NODELINK_ONLY_SEARCH_PREFIXES.has(source.toLowerCase()) || NODELINK_RECOMMENDATION_PREFIXES.has(source.toLowerCase()));
808
+ }
809
+ isPrefixedLoadTracksIdentifier(identifier) {
810
+ const prefix = getIdentifierPrefix(identifier);
811
+ return prefix !== null && KNOWN_LOADTRACKS_PREFIXES.has(prefix);
812
+ }
813
+ hasNodeLinkSearchTypeModifier(identifier) {
814
+ const prefix = getIdentifierPrefix(identifier);
815
+ const searchType = getIdentifierSearchType(identifier);
816
+ return prefix !== null && searchType !== null && KNOWN_LOADTRACKS_PREFIXES.has(prefix) && NODELINK_SEARCH_TYPE_MODIFIERS.has(searchType);
817
+ }
818
+ requiresNodeLinkSearchNode(source, identifier, searchType) {
819
+ if (searchType)
820
+ return true;
821
+ if (this.isNodeLinkSearchSource(source))
822
+ return true;
823
+ const prefix = getIdentifierPrefix(identifier);
824
+ if (prefix === null)
825
+ return false;
826
+ if (NODELINK_ONLY_SEARCH_PREFIXES.has(prefix) || NODELINK_ONLY_LOADTRACKS_PREFIXES.has(prefix))
827
+ return true;
828
+ if (this.hasNodeLinkSearchTypeModifier(identifier))
829
+ return true;
830
+ const normalizedIdentifier = identifier.toLowerCase();
831
+ return prefix === "sprec" && !normalizedIdentifier.startsWith("sprec:mix:");
832
+ }
833
+ requiresLavalinkSearchNode(identifier) {
834
+ return identifier.toLowerCase().startsWith("sprec:mix:");
835
+ }
836
+ getSearchNode(source, identifier, searchType) {
837
+ if (this.requiresLavalinkSearchNode(identifier)) {
838
+ return this.getPreferredNode((node) => !node.isNodeLink);
839
+ }
840
+ if (this.requiresNodeLinkSearchNode(source, identifier, searchType)) {
841
+ return this.getPreferredNode((node) => node.isNodeLink);
842
+ }
843
+ return this.useableNode ?? null;
844
+ }
845
+ getPreferredNode(predicate) {
846
+ const nodes = this.nodes.filter((node) => node.connected && !node.options.isBackup && predicate(node));
847
+ if (!nodes.size)
848
+ return null;
849
+ if (this.options.enablePriorityMode) {
850
+ const priorityNodes = nodes.filter((node) => node.options.nodePriority > 0);
851
+ return (priorityNodes.size ? priorityNodes : nodes).sort((a, b) => b.options.nodePriority - a.options.nodePriority).first() ?? null;
852
+ }
853
+ if (this.options.useNode === Enums_1.UseNodeOptions.LeastLoad) {
854
+ return nodes
855
+ .sort((a, b) => {
856
+ return getNodeLoad(a) - getNodeLoad(b);
857
+ })
858
+ .first();
859
+ }
860
+ return nodes.sort((a, b) => a.stats.players - b.stats.players).first();
861
+ }
720
862
  /**
721
863
  * Returns the node to use based on the configured `useNode` and `enablePriorityMode` options.
722
864
  * If `enablePriorityMode` is true, the node is chosen based on priority, otherwise it is chosen based on the `useNode` option.
@@ -1097,10 +1239,7 @@ class Manager extends events_1.EventEmitter {
1097
1239
  return this.nodes
1098
1240
  .filter((node) => node.connected && !node.options.isBackup)
1099
1241
  .sort((a, b) => {
1100
- const aload = a.stats.cpu ? (a.stats.cpu.lavalinkLoad / a.stats.cpu.cores) * 100 : 0;
1101
- const bload = b.stats.cpu ? (b.stats.cpu.lavalinkLoad / b.stats.cpu.cores) * 100 : 0;
1102
- // Sort the nodes by their load in ascending order
1103
- return aload - bload;
1242
+ return getNodeLoad(a) - getNodeLoad(b);
1104
1243
  });
1105
1244
  }
1106
1245
  /**
@@ -2,7 +2,7 @@ import { Manager } from "./Manager";
2
2
  import { Player } from "./Player";
3
3
  import { Rest } from "./Rest";
4
4
  import WebSocket from "ws";
5
- import { LavalinkInfo, Lyrics, NodeLinkGetLyrics, NodeOptions, NodeStats, PlayerEvent, PlayerEvents, Track, TrackEndEvent, TrackExceptionEvent, TrackStartEvent, TrackStuckEvent, WebSocketClosedEvent } from "./Types";
5
+ import { LavalinkInfo, Lyrics, NodeLinkGetLyrics, NodeLinkChapter, NodeLinkEvent, NodeLinkMix, NodeLinkMeaningResult, NodeLinkSponsorBlockSegment, NodeLinkSponsorBlockUpdate, NodeOptions, NodeStats, NodeLinkSponsorBlockState, PlayerEvent, Track, TrackEndEvent, TrackExceptionEvent, TrackStartEvent, TrackStuckEvent, WebSocketClosedEvent } from "./Types";
6
6
  import { SponsorBlockSegment } from "./Enums";
7
7
  export declare class Node {
8
8
  manager: Manager;
@@ -151,13 +151,18 @@ export declare class Node {
151
151
  * @private
152
152
  */
153
153
  protected message(messagePayload: Buffer | string): Promise<void>;
154
+ private normalizeStats;
155
+ private handlePlayerUpdate;
154
156
  /**
155
157
  * Handles an event emitted from the Lavalink node.
156
158
  * @param {PlayerEvent & PlayerEvents} payload The event emitted from the node.
157
159
  * @returns {Promise<void>} A promise that resolves when the event has been handled.
158
160
  * @private
159
161
  */
160
- protected handleEvent(payload: PlayerEvent & PlayerEvents): Promise<void>;
162
+ protected handleEvent(payload: NodeLinkEvent | PlayerEvent): Promise<void>;
163
+ private markAsNodeLink;
164
+ private resolveEventTrack;
165
+ private handleNodeLinkEvent;
161
166
  /**
162
167
  * Emitted when a new track starts playing.
163
168
  * @param {Player} player The player that started playing the track.
@@ -247,6 +252,20 @@ export declare class Node {
247
252
  * @returns {Promise<Lyrics | NodeLinkGetLyrics>} A promise that resolves with the lyrics data.
248
253
  */
249
254
  getLyrics(track: Track, skipTrackSource?: boolean, language?: string): Promise<Lyrics | NodeLinkGetLyrics>;
255
+ /**
256
+ * Loads chapters for a track using NodeLink.
257
+ * @param {Track} track - The track to load chapters for.
258
+ * @returns {Promise<NodeLinkChapter[]>} A promise that resolves with the chapter list.
259
+ */
260
+ getChapters(track: Track): Promise<NodeLinkChapter[]>;
261
+ /**
262
+ * Loads chapters for a track using NodeLink.
263
+ * @param {Track} track - The track to load chapters for.
264
+ * @returns {Promise<NodeLinkChapter[]>} A promise that resolves with the chapter list.
265
+ */
266
+ getNodeLinkChapters(track: Track): Promise<NodeLinkChapter[]>;
267
+ /** Loads NodeLink meaning/background information for a track. */
268
+ getNodeLinkMeaning(track: Track, language?: string): Promise<NodeLinkMeaningResult>;
250
269
  /**
251
270
  * Subscribes to lyrics for a player.
252
271
  * @param {string} guildId - The ID of the guild to subscribe to lyrics for.
@@ -359,6 +378,74 @@ export declare class Node {
359
378
  * @throws {RangeError} If the sponsorblock-plugin is not available in the Lavalink node.
360
379
  */
361
380
  getSponsorBlock(player: Player): Promise<SponsorBlockSegment[]>;
381
+ /** Returns the complete NodeLink SponsorBlock state for a player. */
382
+ getNodeLinkSponsorBlockState(player: Player): Promise<NodeLinkSponsorBlockState>;
383
+ /** Updates NodeLink SponsorBlock behavior for a player. */
384
+ updateNodeLinkSponsorBlock(player: Player, update: NodeLinkSponsorBlockUpdate): Promise<NodeLinkSponsorBlockState>;
385
+ /** Replaces the NodeLink SponsorBlock segment list for a player. */
386
+ setNodeLinkSponsorBlockSegments(player: Player, segments: NodeLinkSponsorBlockSegment[]): Promise<NodeLinkSponsorBlockState>;
387
+ /**
388
+ * Adds a NodeLink audio mix layer to the player.
389
+ * @param {Player} player - The player to add the mix to.
390
+ * @param {Track} track - The track to mix over the current playback.
391
+ * @param {number} [volume] - The mix volume in the 0-1 range.
392
+ * @returns {Promise<NodeLinkMix>} The created mix layer.
393
+ */
394
+ addMix(player: Player, track: Track, volume?: number): Promise<NodeLinkMix>;
395
+ /**
396
+ * Adds a NodeLink audio mix layer to the player.
397
+ * @param {Player} player - The player to add the mix to.
398
+ * @param {Track} track - The track to mix over the current playback.
399
+ * @param {number} [volume] - The mix volume in the 0-1 range.
400
+ * @returns {Promise<NodeLinkMix>} The created mix layer.
401
+ */
402
+ addNodeLinkMix(player: Player, track: Track, volume?: number): Promise<NodeLinkMix>;
403
+ /**
404
+ * Gets the active NodeLink audio mix layers for the player.
405
+ * @param {Player} player - The player to fetch mixes for.
406
+ * @returns {Promise<NodeLinkMix[]>} The active mix layers.
407
+ */
408
+ getMixes(player: Player): Promise<NodeLinkMix[]>;
409
+ /**
410
+ * Gets the active NodeLink audio mix layers for the player.
411
+ * @param {Player} player - The player to fetch mixes for.
412
+ * @returns {Promise<NodeLinkMix[]>} The active mix layers.
413
+ */
414
+ getNodeLinkMixes(player: Player): Promise<NodeLinkMix[]>;
415
+ /**
416
+ * Updates a NodeLink audio mix layer volume.
417
+ * @param {Player} player - The player that owns the mix.
418
+ * @param {string} mixId - The mix layer id.
419
+ * @param {number} volume - The mix volume in the 0-1 range.
420
+ * @returns {Promise<void>}
421
+ */
422
+ updateMix(player: Player, mixId: string, volume: number): Promise<void>;
423
+ /**
424
+ * Updates a NodeLink audio mix layer volume.
425
+ * @param {Player} player - The player that owns the mix.
426
+ * @param {string} mixId - The mix layer id.
427
+ * @param {number} volume - The mix volume in the 0-1 range.
428
+ * @returns {Promise<void>}
429
+ */
430
+ updateNodeLinkMix(player: Player, mixId: string, volume: number): Promise<void>;
431
+ /**
432
+ * Removes a NodeLink audio mix layer.
433
+ * @param {Player} player - The player that owns the mix.
434
+ * @param {string} mixId - The mix layer id.
435
+ * @returns {Promise<void>}
436
+ */
437
+ removeMix(player: Player, mixId: string): Promise<void>;
438
+ /**
439
+ * Removes a NodeLink audio mix layer.
440
+ * @param {Player} player - The player that owns the mix.
441
+ * @param {string} mixId - The mix layer id.
442
+ * @returns {Promise<void>}
443
+ */
444
+ removeNodeLinkMix(player: Player, mixId: string): Promise<void>;
445
+ private assertNodeLinkFeature;
446
+ private assertMixVolume;
447
+ private assertNodeLinkSponsorBlockUpdate;
448
+ private isValidNodeLinkSponsorBlockSegment;
362
449
  /**
363
450
  * Sets the sponsorblock segments for a player.
364
451
  * @param {Player} player - The player to set the sponsor blocks for.