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.
- package/README.md +2 -2
- package/dist/index.d.ts +169 -46
- package/dist/storage/CollectionPlayerStore.js +77 -0
- package/dist/storage/RedisPlayerStore.js +156 -0
- package/dist/structures/Manager.js +419 -179
- package/dist/structures/Node.js +47 -38
- package/dist/structures/Player.js +59 -55
- package/dist/structures/Queue.js +92 -32
- package/dist/structures/RedisQueue.js +309 -0
- package/dist/structures/Utils.js +400 -343
- package/dist/utils/managerCheck.js +8 -5
- package/package.json +12 -11
package/dist/structures/Utils.js
CHANGED
|
@@ -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
|
-
|
|
147
|
-
|
|
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
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
310
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
325
|
-
console.error("[AutoPlay]
|
|
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
|
-
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
403
|
-
|
|
392
|
+
const result = { loadType: recommendedResult.loadType, tracks, playlist };
|
|
393
|
+
if (result.loadType === LoadTypes.Empty || result.loadType === LoadTypes.Error) {
|
|
404
394
|
return [];
|
|
405
395
|
}
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
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
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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
|
-
|
|
471
|
-
console.error("[AutoPlay]
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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
|
-
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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 (!
|
|
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
|
-
|
|
517
|
-
return res.tracks;
|
|
547
|
+
return result.tracks;
|
|
518
548
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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 "
|
|
525
|
-
|
|
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. */
|