magmastream 2.9.0-dev.4 → 2.9.0-dev.6
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.d.ts +3 -1
- package/dist/structures/Manager.js +3 -0
- package/dist/structures/Utils.js +292 -171
- package/dist/utils/managerCheck.js +11 -11
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1135,7 +1135,7 @@ interface ManagerOptions {
|
|
|
1135
1135
|
playNextOnEnd?: boolean;
|
|
1136
1136
|
/** An array of search platforms to use for autoplay. First to last matters
|
|
1137
1137
|
* Use enum `AutoPlayPlatform`.
|
|
1138
|
-
|
|
1138
|
+
*/
|
|
1139
1139
|
autoPlaySearchPlatforms?: AutoPlayPlatform[];
|
|
1140
1140
|
/** The client ID to use. */
|
|
1141
1141
|
clientId?: string;
|
|
@@ -1222,6 +1222,8 @@ declare enum AutoPlayPlatform {
|
|
|
1222
1222
|
Spotify = "spotify",
|
|
1223
1223
|
Deezer = "deezer",
|
|
1224
1224
|
SoundCloud = "soundcloud",
|
|
1225
|
+
Tidal = "tidal",
|
|
1226
|
+
VKMusic = "vkmusic",
|
|
1225
1227
|
YouTube = "youtube"
|
|
1226
1228
|
}
|
|
1227
1229
|
declare enum PlayerStateEventTypes {
|
|
@@ -391,6 +391,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
391
391
|
voiceChannelId: state.options.voiceChannelId,
|
|
392
392
|
selfDeafen: state.options.selfDeafen,
|
|
393
393
|
volume: lavaPlayer.volume || state.options.volume,
|
|
394
|
+
node: nodeId,
|
|
394
395
|
};
|
|
395
396
|
this.emit(ManagerEventTypes.Debug, `[MANAGER] Recreating player: ${state.guildId} from saved file: ${JSON.stringify(state.options)}`);
|
|
396
397
|
const player = this.create(playerOptions);
|
|
@@ -928,6 +929,8 @@ var AutoPlayPlatform;
|
|
|
928
929
|
AutoPlayPlatform["Spotify"] = "spotify";
|
|
929
930
|
AutoPlayPlatform["Deezer"] = "deezer";
|
|
930
931
|
AutoPlayPlatform["SoundCloud"] = "soundcloud";
|
|
932
|
+
AutoPlayPlatform["Tidal"] = "tidal";
|
|
933
|
+
AutoPlayPlatform["VKMusic"] = "vkmusic";
|
|
931
934
|
AutoPlayPlatform["YouTube"] = "youtube";
|
|
932
935
|
})(AutoPlayPlatform || (exports.AutoPlayPlatform = AutoPlayPlatform = {}));
|
|
933
936
|
var PlayerStateEventTypes;
|
package/dist/structures/Utils.js
CHANGED
|
@@ -262,9 +262,73 @@ class AutoPlayUtils {
|
|
|
262
262
|
static async getRecommendedTracksFromSource(track, platform) {
|
|
263
263
|
switch (platform) {
|
|
264
264
|
case "spotify":
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
{
|
|
266
|
+
try {
|
|
267
|
+
if (!track.uri.includes("spotify")) {
|
|
268
|
+
const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.Spotify }, track.requester);
|
|
269
|
+
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
if (res.loadType === LoadTypes.Playlist) {
|
|
273
|
+
res.tracks = res.playlist.tracks;
|
|
274
|
+
}
|
|
275
|
+
if (!res.tracks.length) {
|
|
276
|
+
return [];
|
|
277
|
+
}
|
|
278
|
+
track = res.tracks[0];
|
|
279
|
+
}
|
|
280
|
+
const TOTP_SECRET = new Uint8Array([
|
|
281
|
+
53, 53, 48, 55, 49, 52, 53, 56, 53, 51, 52, 56, 55, 52, 57, 57, 53, 57, 50, 50, 52, 56, 54, 51, 48, 51, 50, 57, 51, 52, 55,
|
|
282
|
+
]);
|
|
283
|
+
const hmac = crypto_1.default.createHmac("sha1", TOTP_SECRET);
|
|
284
|
+
function generateTotp() {
|
|
285
|
+
const counter = Math.floor(Date.now() / 30000);
|
|
286
|
+
const counterBuffer = Buffer.alloc(8);
|
|
287
|
+
counterBuffer.writeBigInt64BE(BigInt(counter));
|
|
288
|
+
hmac.update(counterBuffer);
|
|
289
|
+
const hmacResult = hmac.digest();
|
|
290
|
+
const offset = hmacResult[hmacResult.length - 1] & 15;
|
|
291
|
+
const truncatedValue = ((hmacResult[offset] & 127) << 24) | ((hmacResult[offset + 1] & 255) << 16) | ((hmacResult[offset + 2] & 255) << 8) | (hmacResult[offset + 3] & 255);
|
|
292
|
+
const totp = (truncatedValue % 1000000).toString().padStart(6, "0");
|
|
293
|
+
return [totp, counter * 30000];
|
|
294
|
+
}
|
|
295
|
+
const [totp, timestamp] = generateTotp();
|
|
296
|
+
const params = {
|
|
297
|
+
reason: "transport",
|
|
298
|
+
productType: "embed",
|
|
299
|
+
totp: totp,
|
|
300
|
+
totpVer: 5,
|
|
301
|
+
ts: timestamp,
|
|
302
|
+
};
|
|
303
|
+
let body;
|
|
304
|
+
try {
|
|
305
|
+
const response = await axios_1.default.get("https://open.spotify.com/get_access_token", { params });
|
|
306
|
+
body = response.data;
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
console.error("[AutoPlay] Failed to get spotify access token:", error.response?.status, error.response?.data || error.message);
|
|
310
|
+
return [];
|
|
311
|
+
}
|
|
312
|
+
let json;
|
|
313
|
+
try {
|
|
314
|
+
const response = await axios_1.default.get(`https://api.spotify.com/v1/recommendations`, {
|
|
315
|
+
params: { limit: 10, seed_tracks: track.identifier },
|
|
316
|
+
headers: {
|
|
317
|
+
Authorization: `Bearer ${body.accessToken}`,
|
|
318
|
+
"Content-Type": "application/json",
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
json = response.data;
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
console.error("[AutoPlay] Failed to fetch spotify recommendations:", error.response?.status, error.response?.data || error.message);
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
if (!json.tracks || !json.tracks.length) {
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
const recommendedTrackId = json.tracks[Math.floor(Math.random() * json.tracks.length)].id;
|
|
331
|
+
const res = await this.manager.search({ query: `https://open.spotify.com/track/${recommendedTrackId}`, source: Manager_1.SearchPlatform.Spotify }, track.requester);
|
|
268
332
|
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
269
333
|
return [];
|
|
270
334
|
}
|
|
@@ -274,214 +338,271 @@ class AutoPlayUtils {
|
|
|
274
338
|
if (!res.tracks.length) {
|
|
275
339
|
return [];
|
|
276
340
|
}
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
const TOTP_SECRET = new Uint8Array([
|
|
280
|
-
53, 53, 48, 55, 49, 52, 53, 56, 53, 51, 52, 56, 55, 52, 57, 57, 53, 57, 50, 50, 52, 56, 54, 51, 48, 51, 50, 57, 51, 52, 55,
|
|
281
|
-
]);
|
|
282
|
-
const hmac = crypto_1.default.createHmac("sha1", TOTP_SECRET);
|
|
283
|
-
function generateTotp() {
|
|
284
|
-
const counter = Math.floor(Date.now() / 30000);
|
|
285
|
-
const counterBuffer = Buffer.alloc(8);
|
|
286
|
-
counterBuffer.writeBigInt64BE(BigInt(counter));
|
|
287
|
-
hmac.update(counterBuffer);
|
|
288
|
-
const hmacResult = hmac.digest();
|
|
289
|
-
const offset = hmacResult[hmacResult.length - 1] & 15;
|
|
290
|
-
const truncatedValue = ((hmacResult[offset] & 127) << 24) | ((hmacResult[offset + 1] & 255) << 16) | ((hmacResult[offset + 2] & 255) << 8) | (hmacResult[offset + 3] & 255);
|
|
291
|
-
const totp = (truncatedValue % 1000000).toString().padStart(6, "0");
|
|
292
|
-
return [totp, counter * 30000];
|
|
293
|
-
}
|
|
294
|
-
const [totp, timestamp] = generateTotp();
|
|
295
|
-
const params = {
|
|
296
|
-
reason: "transport",
|
|
297
|
-
productType: "embed",
|
|
298
|
-
totp: totp,
|
|
299
|
-
totpVer: 5,
|
|
300
|
-
ts: timestamp,
|
|
301
|
-
};
|
|
302
|
-
let body;
|
|
303
|
-
try {
|
|
304
|
-
const response = await axios_1.default.get("https://open.spotify.com/get_access_token", { params });
|
|
305
|
-
body = response.data;
|
|
341
|
+
return res.tracks;
|
|
306
342
|
}
|
|
307
343
|
catch (error) {
|
|
308
|
-
console.error("[AutoPlay]
|
|
344
|
+
console.error("[AutoPlay] Unexpected spotify error:", error.message || error);
|
|
309
345
|
return [];
|
|
310
346
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
case "deezer":
|
|
350
|
+
{
|
|
351
|
+
if (!track.uri.includes("deezer")) {
|
|
352
|
+
const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.Deezer }, track.requester);
|
|
353
|
+
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
354
|
+
return [];
|
|
355
|
+
}
|
|
356
|
+
if (res.loadType === LoadTypes.Playlist) {
|
|
357
|
+
res.tracks = res.playlist.tracks;
|
|
358
|
+
}
|
|
359
|
+
if (!res.tracks.length) {
|
|
360
|
+
return [];
|
|
361
|
+
}
|
|
362
|
+
track = res.tracks[0];
|
|
321
363
|
}
|
|
322
|
-
|
|
323
|
-
|
|
364
|
+
const identifier = `dzrec:${track.identifier}`;
|
|
365
|
+
const recommendedResult = (await this.manager.useableNode.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
|
|
366
|
+
if (!recommendedResult) {
|
|
324
367
|
return [];
|
|
325
368
|
}
|
|
326
|
-
|
|
327
|
-
|
|
369
|
+
let tracks = [];
|
|
370
|
+
let playlist = null;
|
|
371
|
+
const requester = track.requester;
|
|
372
|
+
switch (recommendedResult.loadType) {
|
|
373
|
+
case LoadTypes.Search:
|
|
374
|
+
tracks = recommendedResult.data.map((track) => TrackUtils.build(track, requester));
|
|
375
|
+
break;
|
|
376
|
+
case LoadTypes.Track:
|
|
377
|
+
tracks = [TrackUtils.build(recommendedResult.data, requester)];
|
|
378
|
+
break;
|
|
379
|
+
case LoadTypes.Playlist: {
|
|
380
|
+
const playlistData = recommendedResult.data;
|
|
381
|
+
tracks = playlistData.tracks.map((track) => TrackUtils.build(track, requester));
|
|
382
|
+
playlist = {
|
|
383
|
+
name: playlistData.info.name,
|
|
384
|
+
playlistInfo: playlistData.pluginInfo,
|
|
385
|
+
requester: requester,
|
|
386
|
+
tracks,
|
|
387
|
+
duration: tracks.reduce((acc, cur) => acc + (cur.duration || 0), 0),
|
|
388
|
+
};
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
328
391
|
}
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
392
|
+
const result = { loadType: recommendedResult.loadType, tracks, playlist };
|
|
393
|
+
if (result.loadType === LoadTypes.Empty || result.loadType === LoadTypes.Error) {
|
|
332
394
|
return [];
|
|
333
395
|
}
|
|
334
|
-
if (
|
|
335
|
-
|
|
396
|
+
if (result.loadType === LoadTypes.Playlist) {
|
|
397
|
+
result.tracks = result.playlist.tracks;
|
|
336
398
|
}
|
|
337
|
-
if (!
|
|
399
|
+
if (!result.tracks.length) {
|
|
338
400
|
return [];
|
|
339
401
|
}
|
|
340
|
-
return
|
|
341
|
-
}
|
|
342
|
-
catch (error) {
|
|
343
|
-
console.error("[AutoPlay] Unexpected spotify error:", error.message || error);
|
|
344
|
-
return [];
|
|
402
|
+
return result.tracks;
|
|
345
403
|
}
|
|
346
404
|
break;
|
|
347
|
-
case "
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
405
|
+
case "soundcloud":
|
|
406
|
+
{
|
|
407
|
+
if (!track.uri.includes("soundcloud")) {
|
|
408
|
+
const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.SoundCloud }, track.requester);
|
|
409
|
+
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
410
|
+
return [];
|
|
411
|
+
}
|
|
412
|
+
if (res.loadType === LoadTypes.Playlist) {
|
|
413
|
+
res.tracks = res.playlist.tracks;
|
|
414
|
+
}
|
|
415
|
+
if (!res.tracks.length) {
|
|
416
|
+
return [];
|
|
417
|
+
}
|
|
418
|
+
track = res.tracks[0];
|
|
352
419
|
}
|
|
353
|
-
|
|
354
|
-
|
|
420
|
+
try {
|
|
421
|
+
const recommendedRes = await axios_1.default.get(`${track.uri}/recommended`).catch((err) => {
|
|
422
|
+
console.error(`[AutoPlay] Failed to fetch SoundCloud recommendations. Status: ${err.response?.status || "Unknown"}`, err.message);
|
|
423
|
+
return null;
|
|
424
|
+
});
|
|
425
|
+
if (!recommendedRes) {
|
|
426
|
+
return [];
|
|
427
|
+
}
|
|
428
|
+
const html = recommendedRes.data;
|
|
429
|
+
const dom = new jsdom_1.JSDOM(html);
|
|
430
|
+
const document = dom.window.document;
|
|
431
|
+
const secondNoscript = document.querySelectorAll("noscript")[1];
|
|
432
|
+
const sectionElement = secondNoscript.querySelector("section");
|
|
433
|
+
const articleElements = sectionElement.querySelectorAll("article");
|
|
434
|
+
if (!articleElements || articleElements.length === 0) {
|
|
435
|
+
return [];
|
|
436
|
+
}
|
|
437
|
+
const urls = Array.from(articleElements)
|
|
438
|
+
.map((articleElement) => {
|
|
439
|
+
const h2Element = articleElement.querySelector('h2[itemprop="name"]');
|
|
440
|
+
const aElement = h2Element?.querySelector('a[itemprop="url"]');
|
|
441
|
+
return aElement ? `https://soundcloud.com${aElement.getAttribute("href")}` : null;
|
|
442
|
+
})
|
|
443
|
+
.filter(Boolean);
|
|
444
|
+
if (!urls.length) {
|
|
445
|
+
return [];
|
|
446
|
+
}
|
|
447
|
+
const randomUrl = urls[Math.floor(Math.random() * urls.length)];
|
|
448
|
+
const res = await this.manager.search({ query: randomUrl, source: Manager_1.SearchPlatform.SoundCloud }, track.requester);
|
|
449
|
+
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
450
|
+
return [];
|
|
451
|
+
}
|
|
452
|
+
if (res.loadType === LoadTypes.Playlist) {
|
|
453
|
+
res.tracks = res.playlist.tracks;
|
|
454
|
+
}
|
|
455
|
+
if (!res.tracks.length) {
|
|
456
|
+
return [];
|
|
457
|
+
}
|
|
458
|
+
return res.tracks;
|
|
355
459
|
}
|
|
356
|
-
|
|
460
|
+
catch (error) {
|
|
461
|
+
console.error("[AutoPlay] Error occurred while fetching soundcloud recommendations:", error);
|
|
357
462
|
return [];
|
|
358
463
|
}
|
|
359
|
-
track = res.tracks[0];
|
|
360
|
-
}
|
|
361
|
-
const identifier = `dzrec:${track.identifier}`;
|
|
362
|
-
const recommendedResult = (await this.manager.useableNode.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
|
|
363
|
-
if (!recommendedResult) {
|
|
364
|
-
return [];
|
|
365
|
-
}
|
|
366
|
-
let tracks = [];
|
|
367
|
-
let playlist = null;
|
|
368
|
-
const requester = track.requester;
|
|
369
|
-
switch (recommendedResult.loadType) {
|
|
370
|
-
case LoadTypes.Search:
|
|
371
|
-
tracks = recommendedResult.data.map((track) => TrackUtils.build(track, requester));
|
|
372
|
-
break;
|
|
373
|
-
case LoadTypes.Track:
|
|
374
|
-
tracks = [TrackUtils.build(recommendedResult.data, requester)];
|
|
375
|
-
break;
|
|
376
|
-
case LoadTypes.Playlist: {
|
|
377
|
-
const playlistData = recommendedResult.data;
|
|
378
|
-
tracks = playlistData.tracks.map((track) => TrackUtils.build(track, requester));
|
|
379
|
-
playlist = {
|
|
380
|
-
name: playlistData.info.name,
|
|
381
|
-
playlistInfo: playlistData.pluginInfo,
|
|
382
|
-
requester: requester,
|
|
383
|
-
tracks,
|
|
384
|
-
duration: tracks.reduce((acc, cur) => acc + (cur.duration || 0), 0),
|
|
385
|
-
};
|
|
386
|
-
break;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
const result = { loadType: recommendedResult.loadType, tracks, playlist };
|
|
390
|
-
if (result.loadType === LoadTypes.Empty || result.loadType === LoadTypes.Error) {
|
|
391
|
-
return [];
|
|
392
|
-
}
|
|
393
|
-
if (result.loadType === LoadTypes.Playlist) {
|
|
394
|
-
result.tracks = result.playlist.tracks;
|
|
395
464
|
}
|
|
396
|
-
if (!result.tracks.length) {
|
|
397
|
-
return [];
|
|
398
|
-
}
|
|
399
|
-
return result.tracks;
|
|
400
465
|
break;
|
|
401
|
-
case "
|
|
402
|
-
|
|
403
|
-
const
|
|
466
|
+
case "youtube":
|
|
467
|
+
{
|
|
468
|
+
const hasYouTubeURL = ["youtube.com", "youtu.be"].some((url) => track.uri.includes(url));
|
|
469
|
+
let videoID = null;
|
|
470
|
+
if (hasYouTubeURL) {
|
|
471
|
+
videoID = track.uri.split("=").pop();
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
const searchResult = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.YouTube }, track.requester);
|
|
475
|
+
videoID = searchResult.tracks[0]?.uri.split("=").pop();
|
|
476
|
+
}
|
|
477
|
+
if (!videoID) {
|
|
478
|
+
return [];
|
|
479
|
+
}
|
|
480
|
+
let randomIndex;
|
|
481
|
+
let searchURI;
|
|
482
|
+
do {
|
|
483
|
+
randomIndex = Math.floor(Math.random() * 23) + 2;
|
|
484
|
+
searchURI = `https://www.youtube.com/watch?v=${videoID}&list=RD${videoID}&index=${randomIndex}`;
|
|
485
|
+
} while (track.uri.includes(searchURI));
|
|
486
|
+
const res = await this.manager.search({ query: searchURI, source: Manager_1.SearchPlatform.YouTube }, track.requester);
|
|
404
487
|
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
405
488
|
return [];
|
|
406
489
|
}
|
|
407
|
-
|
|
408
|
-
|
|
490
|
+
const filteredTracks = res.tracks.filter((t) => t.uri !== track.uri);
|
|
491
|
+
return filteredTracks;
|
|
492
|
+
}
|
|
493
|
+
break;
|
|
494
|
+
case "tidal":
|
|
495
|
+
{
|
|
496
|
+
if (!track.uri.includes("tidal")) {
|
|
497
|
+
const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.Tidal }, track.requester);
|
|
498
|
+
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
499
|
+
return [];
|
|
500
|
+
}
|
|
501
|
+
if (res.loadType === LoadTypes.Playlist) {
|
|
502
|
+
res.tracks = res.playlist.tracks;
|
|
503
|
+
}
|
|
504
|
+
if (!res.tracks.length) {
|
|
505
|
+
return [];
|
|
506
|
+
}
|
|
507
|
+
track = res.tracks[0];
|
|
409
508
|
}
|
|
410
|
-
|
|
509
|
+
const identifier = `tdrec:${track.identifier}`;
|
|
510
|
+
const recommendedResult = (await this.manager.useableNode.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
|
|
511
|
+
if (!recommendedResult) {
|
|
411
512
|
return [];
|
|
412
513
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
514
|
+
let tracks = [];
|
|
515
|
+
let playlist = null;
|
|
516
|
+
const requester = track.requester;
|
|
517
|
+
switch (recommendedResult.loadType) {
|
|
518
|
+
case LoadTypes.Search:
|
|
519
|
+
tracks = recommendedResult.data.map((track) => TrackUtils.build(track, requester));
|
|
520
|
+
break;
|
|
521
|
+
case LoadTypes.Track:
|
|
522
|
+
tracks = [TrackUtils.build(recommendedResult.data, requester)];
|
|
523
|
+
break;
|
|
524
|
+
case LoadTypes.Playlist: {
|
|
525
|
+
const playlistData = recommendedResult.data;
|
|
526
|
+
tracks = playlistData.tracks.map((track) => TrackUtils.build(track, requester));
|
|
527
|
+
playlist = {
|
|
528
|
+
name: playlistData.info.name,
|
|
529
|
+
playlistInfo: playlistData.pluginInfo,
|
|
530
|
+
requester: requester,
|
|
531
|
+
tracks,
|
|
532
|
+
duration: tracks.reduce((acc, cur) => acc + (cur.duration || 0), 0),
|
|
533
|
+
};
|
|
534
|
+
break;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
const result = { loadType: recommendedResult.loadType, tracks, playlist };
|
|
538
|
+
if (result.loadType === LoadTypes.Empty || result.loadType === LoadTypes.Error) {
|
|
421
539
|
return [];
|
|
422
540
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
const sectionElement = secondNoscript.querySelector("section");
|
|
428
|
-
const articleElements = sectionElement.querySelectorAll("article");
|
|
429
|
-
if (!articleElements || articleElements.length === 0) {
|
|
541
|
+
if (result.loadType === LoadTypes.Playlist) {
|
|
542
|
+
result.tracks = result.playlist.tracks;
|
|
543
|
+
}
|
|
544
|
+
if (!result.tracks.length) {
|
|
430
545
|
return [];
|
|
431
546
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
.
|
|
439
|
-
|
|
547
|
+
return result.tracks;
|
|
548
|
+
}
|
|
549
|
+
break;
|
|
550
|
+
case "vkmusic":
|
|
551
|
+
{
|
|
552
|
+
if (!track.uri.includes("vk.com") && !track.uri.includes("vk.ru")) {
|
|
553
|
+
const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.VKMusic }, track.requester);
|
|
554
|
+
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
555
|
+
return [];
|
|
556
|
+
}
|
|
557
|
+
if (res.loadType === LoadTypes.Playlist) {
|
|
558
|
+
res.tracks = res.playlist.tracks;
|
|
559
|
+
}
|
|
560
|
+
if (!res.tracks.length) {
|
|
561
|
+
return [];
|
|
562
|
+
}
|
|
563
|
+
track = res.tracks[0];
|
|
564
|
+
}
|
|
565
|
+
const identifier = `vkrec:${track.identifier}`;
|
|
566
|
+
const recommendedResult = (await this.manager.useableNode.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
|
|
567
|
+
if (!recommendedResult) {
|
|
440
568
|
return [];
|
|
441
569
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
570
|
+
let tracks = [];
|
|
571
|
+
let playlist = null;
|
|
572
|
+
const requester = track.requester;
|
|
573
|
+
switch (recommendedResult.loadType) {
|
|
574
|
+
case LoadTypes.Search:
|
|
575
|
+
tracks = recommendedResult.data.map((track) => TrackUtils.build(track, requester));
|
|
576
|
+
break;
|
|
577
|
+
case LoadTypes.Track:
|
|
578
|
+
tracks = [TrackUtils.build(recommendedResult.data, requester)];
|
|
579
|
+
break;
|
|
580
|
+
case LoadTypes.Playlist: {
|
|
581
|
+
const playlistData = recommendedResult.data;
|
|
582
|
+
tracks = playlistData.tracks.map((track) => TrackUtils.build(track, requester));
|
|
583
|
+
playlist = {
|
|
584
|
+
name: playlistData.info.name,
|
|
585
|
+
playlistInfo: playlistData.pluginInfo,
|
|
586
|
+
requester: requester,
|
|
587
|
+
tracks,
|
|
588
|
+
duration: tracks.reduce((acc, cur) => acc + (cur.duration || 0), 0),
|
|
589
|
+
};
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
const result = { loadType: recommendedResult.loadType, tracks, playlist };
|
|
594
|
+
if (result.loadType === LoadTypes.Empty || result.loadType === LoadTypes.Error) {
|
|
445
595
|
return [];
|
|
446
596
|
}
|
|
447
|
-
if (
|
|
448
|
-
|
|
597
|
+
if (result.loadType === LoadTypes.Playlist) {
|
|
598
|
+
result.tracks = result.playlist.tracks;
|
|
449
599
|
}
|
|
450
|
-
if (!
|
|
600
|
+
if (!result.tracks.length) {
|
|
451
601
|
return [];
|
|
452
602
|
}
|
|
453
|
-
return
|
|
454
|
-
}
|
|
455
|
-
catch (error) {
|
|
456
|
-
console.error("[AutoPlay] Error occurred while fetching soundcloud recommendations:", error);
|
|
457
|
-
return [];
|
|
603
|
+
return result.tracks;
|
|
458
604
|
}
|
|
459
605
|
break;
|
|
460
|
-
case "youtube":
|
|
461
|
-
const hasYouTubeURL = ["youtube.com", "youtu.be"].some((url) => track.uri.includes(url));
|
|
462
|
-
let videoID = null;
|
|
463
|
-
if (hasYouTubeURL) {
|
|
464
|
-
videoID = track.uri.split("=").pop();
|
|
465
|
-
}
|
|
466
|
-
else {
|
|
467
|
-
const searchResult = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.YouTube }, track.requester);
|
|
468
|
-
videoID = searchResult.tracks[0]?.uri.split("=").pop();
|
|
469
|
-
}
|
|
470
|
-
if (!videoID) {
|
|
471
|
-
return [];
|
|
472
|
-
}
|
|
473
|
-
let randomIndex;
|
|
474
|
-
let searchURI;
|
|
475
|
-
do {
|
|
476
|
-
randomIndex = Math.floor(Math.random() * 23) + 2;
|
|
477
|
-
searchURI = `https://www.youtube.com/watch?v=${videoID}&list=RD${videoID}&index=${randomIndex}`;
|
|
478
|
-
} while (track.uri.includes(searchURI));
|
|
479
|
-
const res = await this.manager.search({ query: searchURI, source: Manager_1.SearchPlatform.YouTube }, track.requester);
|
|
480
|
-
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
481
|
-
return [];
|
|
482
|
-
}
|
|
483
|
-
const filteredTracks = res.tracks.filter((t) => t.uri !== track.uri);
|
|
484
|
-
return filteredTracks;
|
|
485
606
|
default:
|
|
486
607
|
return [];
|
|
487
608
|
}
|
|
@@ -16,19 +16,19 @@ function managerCheck(options) {
|
|
|
16
16
|
throw new TypeError('Manager option "playNextOnEnd" must be a boolean.');
|
|
17
17
|
}
|
|
18
18
|
// Validate clientName option
|
|
19
|
-
if (typeof clientName !== undefined) {
|
|
19
|
+
if (typeof clientName !== "undefined") {
|
|
20
20
|
if (typeof clientName !== "string" || clientName.trim().length === 0) {
|
|
21
21
|
throw new TypeError('Manager option "clientName" must be a non-empty string.');
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
// Validate defaultSearchPlatform option
|
|
25
|
-
if (typeof defaultSearchPlatform !== undefined) {
|
|
25
|
+
if (typeof defaultSearchPlatform !== "undefined") {
|
|
26
26
|
if (!Object.values(Manager_1.SearchPlatform).includes(defaultSearchPlatform)) {
|
|
27
27
|
throw new TypeError(`Manager option "defaultSearchPlatform" must be one of: ${Object.values(Manager_1.SearchPlatform).join(", ")}.`);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
// Validate autoPlaySearchPlatforms
|
|
31
|
-
if (autoPlaySearchPlatforms !== undefined) {
|
|
31
|
+
if (typeof autoPlaySearchPlatforms !== "undefined") {
|
|
32
32
|
if (!Array.isArray(autoPlaySearchPlatforms)) {
|
|
33
33
|
throw new TypeError('Manager option "autoPlaySearchPlatforms" must be an array.');
|
|
34
34
|
}
|
|
@@ -37,11 +37,11 @@ function managerCheck(options) {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
// Validate nodes option
|
|
40
|
-
if (typeof nodes === undefined || !Array.isArray(nodes)) {
|
|
40
|
+
if (typeof nodes === "undefined" || !Array.isArray(nodes)) {
|
|
41
41
|
throw new TypeError('Manager option "nodes" must be an array.');
|
|
42
42
|
}
|
|
43
43
|
// Validate enabledPlugins option
|
|
44
|
-
if (typeof enabledPlugins !== undefined && !Array.isArray(enabledPlugins)) {
|
|
44
|
+
if (typeof enabledPlugins !== "undefined" && !Array.isArray(enabledPlugins)) {
|
|
45
45
|
throw new TypeError('Manager option "enabledPlugins" must be a Plugin array.');
|
|
46
46
|
}
|
|
47
47
|
// Validate send option
|
|
@@ -49,7 +49,7 @@ function managerCheck(options) {
|
|
|
49
49
|
throw new TypeError('Manager option "send" must be present and a function.');
|
|
50
50
|
}
|
|
51
51
|
// Validate trackPartial option
|
|
52
|
-
if (typeof trackPartial !== undefined) {
|
|
52
|
+
if (typeof trackPartial !== "undefined") {
|
|
53
53
|
if (!Array.isArray(trackPartial)) {
|
|
54
54
|
throw new TypeError('Manager option "trackPartial" must be an array.');
|
|
55
55
|
}
|
|
@@ -58,7 +58,7 @@ function managerCheck(options) {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
// Validate enablePriorityMode option
|
|
61
|
-
if (typeof enablePriorityMode !== undefined && typeof enablePriorityMode !== "boolean") {
|
|
61
|
+
if (typeof enablePriorityMode !== "undefined" && typeof enablePriorityMode !== "boolean") {
|
|
62
62
|
throw new TypeError('Manager option "enablePriorityMode" must be a boolean.');
|
|
63
63
|
}
|
|
64
64
|
// Validate node priority if enablePriorityMode is enabled
|
|
@@ -70,7 +70,7 @@ function managerCheck(options) {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
// Validate useNode option
|
|
73
|
-
if (typeof useNode !== undefined) {
|
|
73
|
+
if (typeof useNode !== "undefined") {
|
|
74
74
|
if (typeof useNode !== "string") {
|
|
75
75
|
throw new TypeError('Manager option "useNode" must be a string "leastLoad" or "leastPlayers".');
|
|
76
76
|
}
|
|
@@ -79,15 +79,15 @@ function managerCheck(options) {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
// Validate normalizeYouTubeTitles option
|
|
82
|
-
if (typeof normalizeYouTubeTitles !== undefined && typeof normalizeYouTubeTitles !== "boolean") {
|
|
82
|
+
if (typeof normalizeYouTubeTitles !== "undefined" && typeof normalizeYouTubeTitles !== "boolean") {
|
|
83
83
|
throw new TypeError('Manager option "normalizeYouTubeTitles" must be a boolean.');
|
|
84
84
|
}
|
|
85
85
|
// Validate lastFmApiKey option
|
|
86
|
-
if (typeof lastFmApiKey !== undefined && (typeof lastFmApiKey !== "string" || lastFmApiKey.trim().length === 0)) {
|
|
86
|
+
if (typeof lastFmApiKey !== "undefined" && (typeof lastFmApiKey !== "string" || lastFmApiKey.trim().length === 0)) {
|
|
87
87
|
throw new TypeError('Manager option "lastFmApiKey" must be a non-empty string.');
|
|
88
88
|
}
|
|
89
89
|
// Validate maxPreviousTracks option
|
|
90
|
-
if (typeof maxPreviousTracks !== undefined) {
|
|
90
|
+
if (typeof maxPreviousTracks !== "undefined") {
|
|
91
91
|
if (typeof maxPreviousTracks !== "number" || isNaN(maxPreviousTracks)) {
|
|
92
92
|
throw new TypeError('Manager option "maxPreviousTracks" must be a number.');
|
|
93
93
|
}
|