magmastream 2.9.0-dev.2 → 2.9.0-dev.21

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.
@@ -143,469 +143,526 @@ class AutoPlayUtils {
143
143
  throw new Error("AutoPlayUtils.init() requires a valid Manager instance.");
144
144
  this.manager = manager;
145
145
  }
146
- static async getRecommendedTracks(player, track, attempt = 0) {
147
- console.log(`[AutoPlay] Attempt ${attempt} for track: ${track.title}`);
146
+ /**
147
+ * Gets recommended tracks for the given track.
148
+ * @param track The track to get recommended tracks for.
149
+ * @returns An array of recommended tracks.
150
+ */
151
+ static async getRecommendedTracks(track) {
148
152
  const node = this.manager.useableNode;
149
153
  if (!node) {
150
- console.error("[AutoPlay] No available nodes.");
151
154
  throw new Error("No available nodes.");
152
155
  }
153
- if (!player.isAutoplay) {
154
- console.log("[AutoPlay] Autoplay is disabled. Returning an empty array.");
155
- return [];
156
- }
157
- if (attempt >= player.autoplayTries) {
158
- console.warn(`[AutoPlay] Reached max autoplay attempts (${player.autoplayTries}).`);
159
- return [];
160
- }
161
- if (!player.queue.previous.length) {
162
- console.log("[AutoPlay] No previous tracks in the queue. Cannot generate recommendations.");
163
- return [];
164
- }
165
156
  const apiKey = this.manager.options.lastFmApiKey;
166
157
  const enabledSources = node.info.sourceManagers;
167
- const { autoPlaySearchPlatform } = this.manager.options;
168
- console.log(`[AutoPlay] Enabled sources: ${enabledSources.join(", ")}`);
169
- console.log(`[AutoPlay] Preferred autoplay platform: ${autoPlaySearchPlatform}`);
170
- const supportedPlatforms = ["spotify", "deezer", "soundcloud", "youtube"];
171
- const platformMapping = {
172
- [Manager_1.SearchPlatform.AppleMusic]: "applemusic",
173
- [Manager_1.SearchPlatform.Bandcamp]: "bandcamp",
174
- [Manager_1.SearchPlatform.Deezer]: "deezer",
175
- [Manager_1.SearchPlatform.Jiosaavn]: "jiosaavn",
176
- [Manager_1.SearchPlatform.SoundCloud]: "soundcloud",
177
- [Manager_1.SearchPlatform.Spotify]: "spotify",
178
- [Manager_1.SearchPlatform.Tidal]: "tidal",
179
- [Manager_1.SearchPlatform.VKMusic]: "vkmusic",
180
- [Manager_1.SearchPlatform.YouTube]: "youtube",
181
- [Manager_1.SearchPlatform.YouTubeMusic]: "youtube",
182
- };
183
- const mappedPlatform = platformMapping[autoPlaySearchPlatform];
184
- // Last attempt fallback to YouTube
185
- if (attempt === player.autoplayTries - 1 && player.autoplayTries > 1 && enabledSources.includes("youtube")) {
186
- console.log("[AutoPlay] Final attempt: Falling back to YouTube recommendations.");
187
- return await this.getRecommendedTracksFromYouTube(track);
188
- }
189
- // Check if the preferred autoplay platform is supported and enabled
190
- if (mappedPlatform && supportedPlatforms.includes(mappedPlatform) && enabledSources.includes(mappedPlatform)) {
191
- console.log(`[AutoPlay] Using recommended platform: ${mappedPlatform}`);
192
- return await this.getRecommendedTracksFromSource(track, mappedPlatform);
158
+ const autoPlaySearchPlatforms = this.manager.options.autoPlaySearchPlatforms;
159
+ // Iterate over autoplay platforms in order of priority
160
+ for (const platform of autoPlaySearchPlatforms) {
161
+ if (enabledSources.includes(platform)) {
162
+ const recommendedTracks = await this.getRecommendedTracksFromSource(track, platform);
163
+ // If tracks are found, return them immediately
164
+ if (recommendedTracks.length > 0) {
165
+ return recommendedTracks;
166
+ }
167
+ }
193
168
  }
194
169
  // Check if Last.fm API is available
195
170
  if (apiKey) {
196
- console.log("[AutoPlay] No preferred platform found. Using Last.fm recommendations.");
197
171
  return await this.getRecommendedTracksFromLastFm(track, apiKey);
198
172
  }
199
- // Fallback to YouTube if all else fails
200
- if (enabledSources.includes("youtube")) {
201
- console.warn("[AutoPlay] No other sources available. Falling back to YouTube.");
202
- return await this.getRecommendedTracksFromYouTube(track);
203
- }
204
- console.error("[AutoPlay] No suitable platform found. Returning an empty array.");
205
173
  return [];
206
174
  }
175
+ /**
176
+ * Gets recommended tracks from Last.fm for the given track.
177
+ * @param track The track to get recommended tracks for.
178
+ * @param apiKey The API key for Last.fm.
179
+ * @returns An array of recommended tracks.
180
+ */
207
181
  static async getRecommendedTracksFromLastFm(track, apiKey) {
208
- const enabledSources = this.manager.useableNode.info.sourceManagers;
209
- const selectedSource = this.selectPlatform(enabledSources);
210
- console.log(`[AutoPlay] Selected source: ${selectedSource}`);
211
182
  let { author: artist } = track;
212
183
  const { title } = track;
213
- console.log(`[AutoPlay] Searching for recommended tracks for: ${artist} - ${title}`);
214
184
  if (!artist || !title) {
215
185
  if (!title) {
216
186
  // No title provided, search for the artist's top tracks
217
187
  const noTitleUrl = `https://ws.audioscrobbler.com/2.0/?method=artist.getTopTracks&artist=${artist}&autocorrect=1&api_key=${apiKey}&format=json`;
218
- console.log(`[AutoPlay] No title provided. Fetching artist's top tracks from: ${noTitleUrl}`);
219
188
  const response = await axios_1.default.get(noTitleUrl);
220
189
  if (response.data.error || !response.data.toptracks?.track?.length) {
221
- console.error("[AutoPlay] Error or no tracks found for the artist. Returning an empty array.");
222
190
  return [];
223
191
  }
224
- console.log("[AutoPlay] Successfully fetched artist's top tracks.");
225
192
  const randomTrack = response.data.toptracks.track[Math.floor(Math.random() * response.data.toptracks.track.length)];
226
- console.log(`[AutoPlay] Selected random track: ${randomTrack.artist.name} - ${randomTrack.name}`);
227
- const res = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: selectedSource }, track.requester);
193
+ const res = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: this.manager.options.defaultSearchPlatform }, track.requester);
228
194
  if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
229
- console.error("[AutoPlay] Search returned empty or error result. Returning an empty array.");
230
195
  return [];
231
196
  }
232
197
  const filteredTracks = res.tracks.filter((t) => t.uri !== track.uri);
233
198
  if (!filteredTracks.length) {
234
- console.error("[AutoPlay] No suitable tracks found. Returning an empty array.");
235
199
  return [];
236
200
  }
237
- console.log("[AutoPlay] Found suitable tracks.");
238
201
  return filteredTracks;
239
202
  }
240
203
  if (!artist) {
241
204
  // No artist provided, search for the track title
242
205
  const noArtistUrl = `https://ws.audioscrobbler.com/2.0/?method=track.search&track=${title}&api_key=${apiKey}&format=json`;
243
- console.log(`[AutoPlay] No artist provided. Searching for track: ${title} from: ${noArtistUrl}`);
244
206
  const response = await axios_1.default.get(noArtistUrl);
245
207
  artist = response.data.results.trackmatches?.track?.[0]?.artist;
246
208
  if (!artist) {
247
- console.error("[AutoPlay] No artist found for track. Returning an empty array.");
248
209
  return [];
249
210
  }
250
- console.log(`[AutoPlay] Found artist for track: ${artist}`);
251
211
  }
252
212
  }
253
213
  // Search for similar tracks to the current track
254
214
  const url = `https://ws.audioscrobbler.com/2.0/?method=track.getSimilar&artist=${artist}&track=${title}&limit=10&autocorrect=1&api_key=${apiKey}&format=json`;
255
- console.log(`[AutoPlay] Searching for similar tracks using URL: ${url}`);
256
215
  let response;
257
216
  try {
258
217
  response = await axios_1.default.get(url);
259
- console.log("[AutoPlay] Successfully fetched similar tracks.");
260
218
  }
261
219
  catch (error) {
262
- console.error("[AutoPlay] Error fetching similar tracks. Returning an empty array.");
263
- console.log(error);
220
+ console.error("[AutoPlay] Error fetching similar tracks from Last.fm:", error);
264
221
  return [];
265
222
  }
266
223
  if (response.data.error || !response.data.similartracks?.track?.length) {
267
- console.error("[AutoPlay] Error or no similar tracks found. Retrying with top tracks.");
268
224
  // Retry the request if the first attempt fails
269
225
  const retryUrl = `https://ws.audioscrobbler.com/2.0/?method=artist.getTopTracks&artist=${artist}&autocorrect=1&api_key=${apiKey}&format=json`;
270
226
  const retryResponse = await axios_1.default.get(retryUrl);
271
227
  if (retryResponse.data.error || !retryResponse.data.toptracks?.track?.length) {
272
- console.error("[AutoPlay] Retry failed. Returning an empty array.");
273
228
  return [];
274
229
  }
275
230
  const randomTrack = retryResponse.data.toptracks.track[Math.floor(Math.random() * retryResponse.data.toptracks.track.length)];
276
- console.log(`[AutoPlay] Selected random track from retry: ${randomTrack.artist.name} - ${randomTrack.name}`);
277
- const res = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: selectedSource }, track.requester);
231
+ const res = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: this.manager.options.defaultSearchPlatform }, track.requester);
278
232
  if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
279
- console.error("[AutoPlay] Retry search returned empty or error result. Returning an empty array.");
280
233
  return [];
281
234
  }
282
235
  const filteredTracks = res.tracks.filter((t) => t.uri !== track.uri);
283
236
  if (!filteredTracks.length) {
284
- console.error("[AutoPlay] No suitable tracks found in retry. Returning an empty array.");
285
237
  return [];
286
238
  }
287
- console.log("[AutoPlay] Found suitable tracks from retry.");
288
239
  return filteredTracks;
289
240
  }
290
241
  const randomTrack = response.data.similartracks.track.sort(() => Math.random() - 0.5).shift();
291
242
  if (!randomTrack) {
292
- console.error("[AutoPlay] No similar tracks found after filtering. Returning an empty array.");
293
243
  return [];
294
244
  }
295
- console.log(`[AutoPlay] Selected random track: ${randomTrack.name} - ${randomTrack.artist.name}`);
296
- const res = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: selectedSource }, track.requester);
245
+ const res = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: this.manager.options.defaultSearchPlatform }, track.requester);
297
246
  if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
298
- console.error("[AutoPlay] Final search returned empty or error result. Returning an empty array.");
299
247
  return [];
300
248
  }
301
249
  if (res.loadType === LoadTypes.Playlist)
302
250
  res.tracks = res.playlist.tracks;
303
251
  if (!res.tracks.length) {
304
- console.error("[AutoPlay] No tracks found in final search. Returning an empty array.");
305
252
  return [];
306
253
  }
307
254
  return res.tracks;
308
255
  }
309
- static async getRecommendedTracksFromSource(track, mappedPlatform) {
310
- switch (mappedPlatform) {
256
+ /**
257
+ * Gets recommended tracks from the given source.
258
+ * @param track The track to get recommended tracks for.
259
+ * @param platform The source to get recommended tracks from.
260
+ * @returns An array of recommended tracks.
261
+ */
262
+ static async getRecommendedTracksFromSource(track, platform) {
263
+ switch (platform) {
311
264
  case "spotify":
312
- console.log("[AutoPlay] Checking if track URI includes 'spotify':", track.uri);
313
- if (!track.uri.includes("spotify")) {
314
- console.log("[AutoPlay] Track URI does not include 'spotify'. Searching for track:", `${track.author} - ${track.title}`);
315
- const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.Spotify }, track.requester);
316
- if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
317
- console.error("[AutoPlay] Search returned empty or error result. Returning an empty array.");
318
- return [];
319
- }
320
- if (res.loadType === LoadTypes.Playlist) {
321
- console.log("[AutoPlay] Search returned a playlist. Flattening tracks.");
322
- res.tracks = res.playlist.tracks;
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);
332
+ if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
333
+ return [];
334
+ }
335
+ if (res.loadType === LoadTypes.Playlist) {
336
+ res.tracks = res.playlist.tracks;
337
+ }
338
+ if (!res.tracks.length) {
339
+ return [];
340
+ }
341
+ return res.tracks;
323
342
  }
324
- if (!res.tracks.length) {
325
- console.error("[AutoPlay] No tracks found in the search. Returning an empty array.");
343
+ catch (error) {
344
+ console.error("[AutoPlay] Unexpected spotify error:", error.message || error);
326
345
  return [];
327
346
  }
328
- console.log("[AutoPlay] Track found in search:", res.tracks[0].uri);
329
- track = res.tracks[0];
330
- }
331
- const TOTP_SECRET = new Uint8Array([
332
- 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,
333
- ]);
334
- const hmac = crypto_1.default.createHmac("sha1", TOTP_SECRET);
335
- function generateTotp() {
336
- const counter = Math.floor(Date.now() / 30000);
337
- const counterBuffer = Buffer.alloc(8);
338
- counterBuffer.writeBigInt64BE(BigInt(counter));
339
- hmac.update(counterBuffer);
340
- const hmacResult = hmac.digest();
341
- const offset = hmacResult[hmacResult.length - 1] & 15;
342
- const truncatedValue = ((hmacResult[offset] & 127) << 24) | ((hmacResult[offset + 1] & 255) << 16) | ((hmacResult[offset + 2] & 255) << 8) | (hmacResult[offset + 3] & 255);
343
- const totp = (truncatedValue % 1000000).toString().padStart(6, "0");
344
- return [totp, counter * 30000];
345
- }
346
- const [totp, timestamp] = generateTotp();
347
- console.log("[AutoPlay] Generated TOTP:", totp);
348
- const params = {
349
- reason: "transport",
350
- productType: "embed",
351
- totp: totp,
352
- totpVer: 5,
353
- ts: timestamp,
354
- };
355
- console.log("[AutoPlay] Sending request to get access token with params:", params);
356
- const { data: body } = await axios_1.default.get("https://open.spotify.com/get_access_token", { params });
357
- console.log("[AutoPlay] Access token received.");
358
- const { data: json } = await axios_1.default.get(`https://api.spotify.com/v1/recommendations`, {
359
- params: { limit: 10, seed_tracks: track.identifier },
360
- headers: {
361
- Authorization: `Bearer ${body.accessToken}`,
362
- "Content-Type": "application/json",
363
- },
364
- });
365
- if (!json.tracks || !json.tracks.length) {
366
- console.error("[AutoPlay] No recommended tracks received from Spotify API. Returning an empty array.");
367
- return [];
368
347
  }
369
- console.log("[AutoPlay] Recommended tracks received from Spotify.");
370
- // Return a random recommended track ID
371
- const recommendedTrackId = json.tracks[Math.floor(Math.random() * json.tracks.length)].id;
372
- console.log(`[AutoPlay] Selected random recommended track ID: ${recommendedTrackId}`);
373
- console.log("[AutoPlay] Searching for the recommended track:", recommendedTrackId);
374
- const res = await this.manager.search({ query: `https://open.spotify.com/track/${recommendedTrackId}`, source: Manager_1.SearchPlatform.Spotify }, track.requester);
375
- if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
376
- console.error("[AutoPlay] Final search returned empty or error result. Returning an empty array.");
377
- return [];
378
- }
379
- if (res.loadType === LoadTypes.Playlist) {
380
- console.log("[AutoPlay] Final search returned a playlist. Flattening tracks.");
381
- res.tracks = res.playlist.tracks;
382
- }
383
- if (!res.tracks.length) {
384
- console.error("[AutoPlay] No tracks found in final search. Returning an empty array.");
385
- return [];
386
- }
387
- console.log("[AutoPlay] Recommended tracks found and ready to return.");
388
- return res.tracks;
348
+ break;
389
349
  case "deezer":
390
- console.log("[AutoPlay] Checking if track URI includes 'deezer':", track.uri);
391
- if (!track.uri.includes("deezer")) {
392
- console.log("[AutoPlay] Track URI does not include 'deezer'. Searching for track:", `${track.author} - ${track.title}`);
393
- const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.Deezer }, track.requester);
394
- if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
395
- console.error("[AutoPlay] Search returned empty or error result. Returning an empty array.");
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];
363
+ }
364
+ const identifier = `dzrec:${track.identifier}`;
365
+ const recommendedResult = (await this.manager.useableNode.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
366
+ if (!recommendedResult) {
396
367
  return [];
397
368
  }
398
- if (res.loadType === LoadTypes.Playlist) {
399
- console.log("[AutoPlay] Search returned a playlist. Flattening tracks.");
400
- res.tracks = res.playlist.tracks;
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
+ }
401
391
  }
402
- if (!res.tracks.length) {
403
- console.error("[AutoPlay] No tracks found in the search. Returning an empty array.");
392
+ const result = { loadType: recommendedResult.loadType, tracks, playlist };
393
+ if (result.loadType === LoadTypes.Empty || result.loadType === LoadTypes.Error) {
404
394
  return [];
405
395
  }
406
- console.log("[AutoPlay] Track found in search:", res.tracks[0].uri);
407
- track = res.tracks[0];
408
- }
409
- const identifier = `dzrec:${track.identifier}`;
410
- console.log("[AutoPlay] Generating Deezer recommendation identifier:", identifier);
411
- const recommendedResult = (await this.manager.useableNode.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
412
- if (!recommendedResult) {
413
- console.error("[AutoPlay] No recommended result received from Deezer. Returning an empty array.");
414
- return [];
415
- }
416
- let tracks = [];
417
- let playlist = null;
418
- const requester = track.requester;
419
- switch (recommendedResult.loadType) {
420
- case LoadTypes.Search:
421
- console.log("[AutoPlay] Recommended result is of type 'Search'. Building tracks.");
422
- tracks = recommendedResult.data.map((track) => TrackUtils.build(track, requester));
423
- break;
424
- case LoadTypes.Track:
425
- console.log("[AutoPlay] Recommended result is of type 'Track'. Building a single track.");
426
- tracks = [TrackUtils.build(recommendedResult.data, requester)];
427
- break;
428
- case LoadTypes.Playlist: {
429
- console.log("[AutoPlay] Recommended result is of type 'Playlist'. Building playlist.");
430
- const playlistData = recommendedResult.data;
431
- tracks = playlistData.tracks.map((track) => TrackUtils.build(track, requester));
432
- playlist = {
433
- name: playlistData.info.name,
434
- playlistInfo: playlistData.pluginInfo,
435
- requester: requester,
436
- tracks,
437
- duration: tracks.reduce((acc, cur) => acc + (cur.duration || 0), 0),
438
- };
439
- break;
396
+ if (result.loadType === LoadTypes.Playlist) {
397
+ result.tracks = result.playlist.tracks;
440
398
  }
399
+ if (!result.tracks.length) {
400
+ return [];
401
+ }
402
+ return result.tracks;
441
403
  }
442
- const result = { loadType: recommendedResult.loadType, tracks, playlist };
443
- if (result.loadType === LoadTypes.Empty || result.loadType === LoadTypes.Error) {
444
- console.error("[AutoPlay] Final result load type is empty or error. Returning an empty array.");
445
- return [];
446
- }
447
- if (result.loadType === LoadTypes.Playlist) {
448
- console.log("[AutoPlay] Final result load type is Playlist. Flattening tracks.");
449
- result.tracks = result.playlist.tracks;
450
- }
451
- if (!result.tracks.length) {
452
- console.error("[AutoPlay] No tracks found in final result. Returning an empty array.");
453
- return [];
454
- }
455
- console.log("[AutoPlay] Tracks found and ready to return.");
456
- return result.tracks;
404
+ break;
457
405
  case "soundcloud":
458
- console.log("[AutoPlay] Checking if track URI includes 'soundcloud':", track.uri);
459
- if (!track.uri.includes("soundcloud")) {
460
- console.log("[AutoPlay] Track URI does not include 'soundcloud'. Searching for track:", `${track.author} - ${track.title}`);
461
- const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.SoundCloud }, track.requester);
462
- if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
463
- console.error("[AutoPlay] Search returned empty or error result. Returning an empty array.");
464
- return [];
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];
465
419
  }
466
- if (res.loadType === LoadTypes.Playlist) {
467
- console.log("[AutoPlay] Search returned a playlist. Flattening tracks.");
468
- res.tracks = res.playlist.tracks;
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;
469
459
  }
470
- if (!res.tracks.length) {
471
- console.error("[AutoPlay] No tracks found in the search. Returning an empty array.");
460
+ catch (error) {
461
+ console.error("[AutoPlay] Error occurred while fetching soundcloud recommendations:", error);
472
462
  return [];
473
463
  }
474
- console.log("[AutoPlay] Track found in search:", res.tracks[0].uri);
475
- track = res.tracks[0];
476
464
  }
477
- try {
478
- console.log("[AutoPlay] Fetching SoundCloud recommendations from:", `${track.uri}/recommended`);
479
- const recommendedRes = await axios_1.default.get(`${track.uri}/recommended`);
480
- const html = recommendedRes.data;
481
- const dom = new jsdom_1.JSDOM(html);
482
- const document = dom.window.document;
483
- const secondNoscript = document.querySelectorAll("noscript")[1];
484
- const sectionElement = secondNoscript.querySelector("section");
485
- const articleElements = sectionElement.querySelectorAll("article");
486
- if (!articleElements || articleElements.length === 0) {
487
- console.error("[AutoPlay] No article elements found for recommendations. Returning an empty array.");
488
- return [];
465
+ break;
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();
489
476
  }
490
- const urls = Array.from(articleElements)
491
- .map((articleElement) => {
492
- const h2Element = articleElement.querySelector('h2[itemprop="name"]');
493
- const aElement = h2Element?.querySelector('a[itemprop="url"]');
494
- return aElement ? `https://soundcloud.com${aElement.getAttribute("href")}` : null;
495
- })
496
- .filter(Boolean);
497
- if (!urls.length) {
498
- console.error("[AutoPlay] No valid URLs found in the recommendations. Returning an empty array.");
477
+ if (!videoID) {
499
478
  return [];
500
479
  }
501
- const randomUrl = urls[Math.floor(Math.random() * urls.length)];
502
- console.log("[AutoPlay] Selected random URL for recommended track:", randomUrl);
503
- const res = await this.manager.search({ query: randomUrl, source: Manager_1.SearchPlatform.SoundCloud }, track.requester);
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);
504
487
  if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
505
- console.error("[AutoPlay] Search for recommended track returned empty or error result. Returning an empty array.");
506
488
  return [];
507
489
  }
508
- if (res.loadType === LoadTypes.Playlist) {
509
- console.log("[AutoPlay] Search for recommended track returned a playlist. Flattening tracks.");
510
- res.tracks = res.playlist.tracks;
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];
508
+ }
509
+ const identifier = `tdrec:${track.identifier}`;
510
+ const recommendedResult = (await this.manager.useableNode.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
511
+ if (!recommendedResult) {
512
+ return [];
513
+ }
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) {
539
+ return [];
540
+ }
541
+ if (result.loadType === LoadTypes.Playlist) {
542
+ result.tracks = result.playlist.tracks;
511
543
  }
512
- if (!res.tracks.length) {
513
- console.error("[AutoPlay] No tracks found in the search for recommended track. Returning an empty array.");
544
+ if (!result.tracks.length) {
514
545
  return [];
515
546
  }
516
- console.log("[AutoPlay] Found recommended tracks:", res.tracks.map((track) => track.uri));
517
- return res.tracks;
547
+ return result.tracks;
518
548
  }
519
- catch (error) {
520
- console.error("[AutoPlay] Error occurred while fetching recommendations:", error);
521
- return [];
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) {
568
+ return [];
569
+ }
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) {
595
+ return [];
596
+ }
597
+ if (result.loadType === LoadTypes.Playlist) {
598
+ result.tracks = result.playlist.tracks;
599
+ }
600
+ if (!result.tracks.length) {
601
+ return [];
602
+ }
603
+ return result.tracks;
522
604
  }
523
605
  break;
524
- case "youtube":
525
- return this.getRecommendedTracksFromYouTube(track);
606
+ case "qobuz":
607
+ {
608
+ if (!track.uri.includes("qobuz.com")) {
609
+ const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.Qobuz }, track.requester);
610
+ if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
611
+ return [];
612
+ }
613
+ if (res.loadType === LoadTypes.Playlist) {
614
+ res.tracks = res.playlist.tracks;
615
+ }
616
+ if (!res.tracks.length) {
617
+ return [];
618
+ }
619
+ track = res.tracks[0];
620
+ }
621
+ const identifier = `qbrec:${track.identifier}`;
622
+ const recommendedResult = (await this.manager.useableNode.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
623
+ if (!recommendedResult) {
624
+ return [];
625
+ }
626
+ let tracks = [];
627
+ let playlist = null;
628
+ const requester = track.requester;
629
+ switch (recommendedResult.loadType) {
630
+ case LoadTypes.Search:
631
+ tracks = recommendedResult.data.map((track) => TrackUtils.build(track, requester));
632
+ break;
633
+ case LoadTypes.Track:
634
+ tracks = [TrackUtils.build(recommendedResult.data, requester)];
635
+ break;
636
+ case LoadTypes.Playlist: {
637
+ const playlistData = recommendedResult.data;
638
+ tracks = playlistData.tracks.map((track) => TrackUtils.build(track, requester));
639
+ playlist = {
640
+ name: playlistData.info.name,
641
+ playlistInfo: playlistData.pluginInfo,
642
+ requester: requester,
643
+ tracks,
644
+ duration: tracks.reduce((acc, cur) => acc + (cur.duration || 0), 0),
645
+ };
646
+ break;
647
+ }
648
+ }
649
+ const result = { loadType: recommendedResult.loadType, tracks, playlist };
650
+ if (result.loadType === LoadTypes.Empty || result.loadType === LoadTypes.Error) {
651
+ return [];
652
+ }
653
+ if (result.loadType === LoadTypes.Playlist) {
654
+ result.tracks = result.playlist.tracks;
655
+ }
656
+ if (!result.tracks.length) {
657
+ return [];
658
+ }
659
+ return result.tracks;
660
+ }
526
661
  break;
527
662
  default:
528
663
  return [];
529
664
  }
530
665
  }
531
- static async getRecommendedTracksFromYouTube(track) {
532
- console.log("[YouTube Recommendation] Checking if track URI includes YouTube URL:", track.uri);
533
- // Check if the previous track has a YouTube URL
534
- const hasYouTubeURL = ["youtube.com", "youtu.be"].some((url) => track.uri.includes(url));
535
- let videoID = null;
536
- if (hasYouTubeURL) {
537
- console.log("[YouTube Recommendation] Track contains a YouTube URL. Extracting video ID from URI.");
538
- videoID = track.uri.split("=").pop();
539
- }
540
- else {
541
- console.log("[YouTube Recommendation] Track does not contain a YouTube URL. Searching for the track on YouTube.");
542
- const searchResult = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.YouTube }, track.requester);
543
- videoID = searchResult.tracks[0]?.uri.split("=").pop();
544
- }
545
- if (!videoID) {
546
- console.error("[YouTube Recommendation] Video ID not found. Returning an empty array.");
547
- return [];
548
- }
549
- console.log("[YouTube Recommendation] Video ID extracted:", videoID);
550
- // Get a random video index between 2 and 24
551
- let randomIndex;
552
- let searchURI;
553
- do {
554
- randomIndex = Math.floor(Math.random() * 23) + 2; // Random index between 2 and 24
555
- searchURI = `https://www.youtube.com/watch?v=${videoID}&list=RD${videoID}&index=${randomIndex}`;
556
- console.log("[YouTube Recommendation] Generated random search URI:", searchURI);
557
- } while (track.uri.includes(searchURI));
558
- // Search for the video and return false if the search fails
559
- console.log("[YouTube Recommendation] Searching for the video using search URI:", searchURI);
560
- const res = await this.manager.search({ query: searchURI, source: Manager_1.SearchPlatform.YouTube }, track.requester);
561
- if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
562
- console.error("[YouTube Recommendation] Search failed or returned empty results. Returning an empty array.");
563
- return [];
564
- }
565
- // Filter out tracks that have the same URI as the current track
566
- console.log("[YouTube Recommendation] Filtering tracks that do not match the current track URI.");
567
- const filteredTracks = res.tracks.filter((t) => t.uri !== track.uri);
568
- console.log("[YouTube Recommendation] Returning filtered recommended tracks:", filteredTracks.map((t) => t.uri));
569
- return filteredTracks;
570
- }
571
- static selectPlatform(enabledSources) {
572
- const { autoPlaySearchPlatform } = this.manager.options;
573
- const platformMapping = {
574
- [Manager_1.SearchPlatform.AppleMusic]: "applemusic",
575
- [Manager_1.SearchPlatform.Bandcamp]: "bandcamp",
576
- [Manager_1.SearchPlatform.Deezer]: "deezer",
577
- [Manager_1.SearchPlatform.Jiosaavn]: "jiosaavn",
578
- [Manager_1.SearchPlatform.SoundCloud]: "soundcloud",
579
- [Manager_1.SearchPlatform.Spotify]: "spotify",
580
- [Manager_1.SearchPlatform.Tidal]: "tidal",
581
- [Manager_1.SearchPlatform.VKMusic]: "vkmusic",
582
- [Manager_1.SearchPlatform.YouTube]: "youtube",
583
- [Manager_1.SearchPlatform.YouTubeMusic]: "youtube",
584
- };
585
- // Try the autoPlaySearchPlatform first
586
- if (enabledSources.includes(platformMapping[autoPlaySearchPlatform])) {
587
- return autoPlaySearchPlatform;
588
- }
589
- // Fallback to other platforms in a predefined order
590
- const fallbackPlatforms = [
591
- Manager_1.SearchPlatform.Spotify,
592
- Manager_1.SearchPlatform.Deezer,
593
- Manager_1.SearchPlatform.SoundCloud,
594
- Manager_1.SearchPlatform.AppleMusic,
595
- Manager_1.SearchPlatform.Bandcamp,
596
- Manager_1.SearchPlatform.Jiosaavn,
597
- Manager_1.SearchPlatform.Tidal,
598
- Manager_1.SearchPlatform.VKMusic,
599
- Manager_1.SearchPlatform.YouTubeMusic,
600
- Manager_1.SearchPlatform.YouTube,
601
- ];
602
- for (const platform of fallbackPlatforms) {
603
- if (enabledSources.includes(platformMapping[platform])) {
604
- return platform;
605
- }
606
- }
607
- return null;
608
- }
609
666
  }
610
667
  exports.AutoPlayUtils = AutoPlayUtils;
611
668
  /** Gets or extends structures to extend the built in, or already extended, classes to add more functionality. */