magmastream 2.9.0-dev.2 → 2.9.0-dev.4
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 +32 -10
- package/dist/structures/Manager.js +9 -3
- package/dist/structures/Node.js +8 -6
- package/dist/structures/Player.js +2 -2
- package/dist/structures/Utils.js +137 -257
- package/dist/utils/managerCheck.js +18 -15
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -678,11 +678,26 @@ declare abstract class AutoPlayUtils {
|
|
|
678
678
|
* @hidden
|
|
679
679
|
*/
|
|
680
680
|
static init(manager: Manager): void;
|
|
681
|
-
|
|
681
|
+
/**
|
|
682
|
+
* Gets recommended tracks for the given track.
|
|
683
|
+
* @param track The track to get recommended tracks for.
|
|
684
|
+
* @returns An array of recommended tracks.
|
|
685
|
+
*/
|
|
686
|
+
static getRecommendedTracks(track: Track): Promise<Track[]>;
|
|
687
|
+
/**
|
|
688
|
+
* Gets recommended tracks from Last.fm for the given track.
|
|
689
|
+
* @param track The track to get recommended tracks for.
|
|
690
|
+
* @param apiKey The API key for Last.fm.
|
|
691
|
+
* @returns An array of recommended tracks.
|
|
692
|
+
*/
|
|
682
693
|
static getRecommendedTracksFromLastFm(track: Track, apiKey: string): Promise<Track[]>;
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
694
|
+
/**
|
|
695
|
+
* Gets recommended tracks from the given source.
|
|
696
|
+
* @param track The track to get recommended tracks for.
|
|
697
|
+
* @param platform The source to get recommended tracks from.
|
|
698
|
+
* @returns An array of recommended tracks.
|
|
699
|
+
*/
|
|
700
|
+
static getRecommendedTracksFromSource(track: Track, platform: string): Promise<Track[]>;
|
|
686
701
|
}
|
|
687
702
|
/** Gets or extends structures to extend the built in, or already extended, classes to add more functionality. */
|
|
688
703
|
declare abstract class Structure {
|
|
@@ -901,7 +916,7 @@ declare class Manager extends EventEmitter {
|
|
|
901
916
|
* @param options.enabledPlugins - An array of enabledPlugins to load.
|
|
902
917
|
* @param options.nodes - An array of node options to create nodes from.
|
|
903
918
|
* @param options.playNextOnEnd - Whether to automatically play the first track in the queue when the player is created.
|
|
904
|
-
* @param options.
|
|
919
|
+
* @param options.autoPlaySearchPlatforms - The search platform autoplay will use. Fallback to Youtube if not found.
|
|
905
920
|
* @param options.enablePriorityMode - Whether to use the priority when selecting a node to play on.
|
|
906
921
|
* @param options.clientName - The name of the client to send to Lavalink.
|
|
907
922
|
* @param options.defaultSearchPlatform - The default search platform to use when searching for tracks.
|
|
@@ -1118,9 +1133,10 @@ interface ManagerOptions {
|
|
|
1118
1133
|
enablePriorityMode?: boolean;
|
|
1119
1134
|
/** Automatically play the next track when the current one ends. */
|
|
1120
1135
|
playNextOnEnd?: boolean;
|
|
1121
|
-
/**
|
|
1122
|
-
* Use enum `
|
|
1123
|
-
|
|
1136
|
+
/** An array of search platforms to use for autoplay. First to last matters
|
|
1137
|
+
* Use enum `AutoPlayPlatform`.
|
|
1138
|
+
*/
|
|
1139
|
+
autoPlaySearchPlatforms?: AutoPlayPlatform[];
|
|
1124
1140
|
/** The client ID to use. */
|
|
1125
1141
|
clientId?: string;
|
|
1126
1142
|
/** Value to use for the `Client-Name` header. */
|
|
@@ -1202,6 +1218,12 @@ declare enum SearchPlatform {
|
|
|
1202
1218
|
YouTube = "ytsearch",
|
|
1203
1219
|
YouTubeMusic = "ytmsearch"
|
|
1204
1220
|
}
|
|
1221
|
+
declare enum AutoPlayPlatform {
|
|
1222
|
+
Spotify = "spotify",
|
|
1223
|
+
Deezer = "deezer",
|
|
1224
|
+
SoundCloud = "soundcloud",
|
|
1225
|
+
YouTube = "youtube"
|
|
1226
|
+
}
|
|
1205
1227
|
declare enum PlayerStateEventTypes {
|
|
1206
1228
|
AutoPlayChange = "playerAutoplay",
|
|
1207
1229
|
ConnectionChange = "playerConnection",
|
|
@@ -1402,7 +1424,7 @@ declare class Player {
|
|
|
1402
1424
|
/** The autoplay state of the player. */
|
|
1403
1425
|
isAutoplay: boolean;
|
|
1404
1426
|
/** The number of times to try autoplay before emitting queueEnd. */
|
|
1405
|
-
autoplayTries: number
|
|
1427
|
+
autoplayTries: number;
|
|
1406
1428
|
private static _manager;
|
|
1407
1429
|
private readonly data;
|
|
1408
1430
|
private dynamicLoopInterval;
|
|
@@ -2136,5 +2158,5 @@ declare class Plugin {
|
|
|
2136
2158
|
load(manager: Manager): void;
|
|
2137
2159
|
}
|
|
2138
2160
|
|
|
2139
|
-
export { AutoPlayUtils, AvailableFilters, Filters, LoadTypes, Manager, ManagerEventTypes, Node, Player, PlayerStateEventTypes, Plugin, Queue, Rest, SearchPlatform, SeverityTypes, SponsorBlockSegment, StateTypes, Structure, TrackEndReasonTypes, TrackPartial, TrackSourceTypes, TrackUtils, UseNodeOptions };
|
|
2161
|
+
export { AutoPlayPlatform, AutoPlayUtils, AvailableFilters, Filters, LoadTypes, Manager, ManagerEventTypes, Node, Player, PlayerStateEventTypes, Plugin, Queue, Rest, SearchPlatform, SeverityTypes, SponsorBlockSegment, StateTypes, Structure, TrackEndReasonTypes, TrackPartial, TrackSourceTypes, TrackUtils, UseNodeOptions };
|
|
2140
2162
|
export type { CPUStats, EqualizerBand, Exception, Extendable, FrameStats, LavalinkInfo, LavalinkResponse, LoadType, Lyrics, LyricsLine, ManagerEvents, ManagerOptions, MemoryStats, NodeMessage, NodeOptions, NodeStats, Payload, PlayOptions, PlayerEvent, PlayerEventType, PlayerEvents, PlayerOptions, PlayerUpdate, PlaylistData, PlaylistInfoData, PlaylistRawData, SearchQuery, SearchResult, Severity, Sizes, SponsorBlockChapterStarted, SponsorBlockChaptersLoaded, SponsorBlockSegmentEventType, SponsorBlockSegmentEvents, SponsorBlockSegmentSkipped, SponsorBlockSegmentsLoaded, State, Track, TrackData, TrackDataInfo, TrackEndEvent, TrackEndReason, TrackExceptionEvent, TrackPluginInfo, TrackSourceName, TrackStartEvent, TrackStuckEvent, UseNodeOption, VoicePacket, VoiceServer, VoiceState, WebSocketClosedEvent };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ManagerEventTypes = exports.PlayerStateEventTypes = exports.SearchPlatform = exports.UseNodeOptions = exports.TrackPartial = exports.Manager = void 0;
|
|
3
|
+
exports.ManagerEventTypes = exports.PlayerStateEventTypes = exports.AutoPlayPlatform = exports.SearchPlatform = exports.UseNodeOptions = exports.TrackPartial = exports.Manager = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const Utils_1 = require("./Utils");
|
|
6
6
|
const collection_1 = require("@discordjs/collection");
|
|
@@ -27,7 +27,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
27
27
|
* @param options.enabledPlugins - An array of enabledPlugins to load.
|
|
28
28
|
* @param options.nodes - An array of node options to create nodes from.
|
|
29
29
|
* @param options.playNextOnEnd - Whether to automatically play the first track in the queue when the player is created.
|
|
30
|
-
* @param options.
|
|
30
|
+
* @param options.autoPlaySearchPlatforms - The search platform autoplay will use. Fallback to Youtube if not found.
|
|
31
31
|
* @param options.enablePriorityMode - Whether to use the priority when selecting a node to play on.
|
|
32
32
|
* @param options.clientName - The name of the client to send to Lavalink.
|
|
33
33
|
* @param options.defaultSearchPlatform - The default search platform to use when searching for tracks.
|
|
@@ -61,7 +61,6 @@ class Manager extends events_1.EventEmitter {
|
|
|
61
61
|
enablePriorityMode: false,
|
|
62
62
|
clientName: "Magmastream",
|
|
63
63
|
defaultSearchPlatform: SearchPlatform.YouTube,
|
|
64
|
-
// autoPlaySearchPlatform: SearchPlatform.YouTube,
|
|
65
64
|
useNode: UseNodeOptions.LeastPlayers,
|
|
66
65
|
maxPreviousTracks: options.maxPreviousTracks ?? 20,
|
|
67
66
|
...options,
|
|
@@ -924,6 +923,13 @@ var SearchPlatform;
|
|
|
924
923
|
SearchPlatform["YouTube"] = "ytsearch";
|
|
925
924
|
SearchPlatform["YouTubeMusic"] = "ytmsearch";
|
|
926
925
|
})(SearchPlatform || (exports.SearchPlatform = SearchPlatform = {}));
|
|
926
|
+
var AutoPlayPlatform;
|
|
927
|
+
(function (AutoPlayPlatform) {
|
|
928
|
+
AutoPlayPlatform["Spotify"] = "spotify";
|
|
929
|
+
AutoPlayPlatform["Deezer"] = "deezer";
|
|
930
|
+
AutoPlayPlatform["SoundCloud"] = "soundcloud";
|
|
931
|
+
AutoPlayPlatform["YouTube"] = "youtube";
|
|
932
|
+
})(AutoPlayPlatform || (exports.AutoPlayPlatform = AutoPlayPlatform = {}));
|
|
927
933
|
var PlayerStateEventTypes;
|
|
928
934
|
(function (PlayerStateEventTypes) {
|
|
929
935
|
PlayerStateEventTypes["AutoPlayChange"] = "playerAutoplay";
|
package/dist/structures/Node.js
CHANGED
|
@@ -602,11 +602,13 @@ class Node {
|
|
|
602
602
|
*/
|
|
603
603
|
async handleAutoplay(player, attempt = 0) {
|
|
604
604
|
// If autoplay is not enabled or all attempts have failed, early exit
|
|
605
|
-
if (!player.isAutoplay || attempt
|
|
605
|
+
if (!player.isAutoplay || attempt > player.autoplayTries || !player.queue.previous.length)
|
|
606
606
|
return false;
|
|
607
607
|
const lastTrack = player.queue.previous[player.queue.previous.length - 1];
|
|
608
608
|
lastTrack.requester = player.get("Internal_BotUser");
|
|
609
|
-
|
|
609
|
+
if (!lastTrack)
|
|
610
|
+
return false;
|
|
611
|
+
const tracks = await Utils_1.AutoPlayUtils.getRecommendedTracks(lastTrack);
|
|
610
612
|
if (tracks.length) {
|
|
611
613
|
player.queue.add(tracks[0]);
|
|
612
614
|
await player.play();
|
|
@@ -714,13 +716,13 @@ class Node {
|
|
|
714
716
|
this.manager.emit(Manager_1.ManagerEventTypes.QueueEnd, player, track, payload);
|
|
715
717
|
return;
|
|
716
718
|
}
|
|
717
|
-
let
|
|
719
|
+
let attempt = 1;
|
|
718
720
|
let success = false;
|
|
719
|
-
while (
|
|
720
|
-
success = await this.handleAutoplay(player,
|
|
721
|
+
while (attempt <= player.autoplayTries) {
|
|
722
|
+
success = await this.handleAutoplay(player, attempt);
|
|
721
723
|
if (success)
|
|
722
724
|
return;
|
|
723
|
-
|
|
725
|
+
attempt++;
|
|
724
726
|
}
|
|
725
727
|
// If all attempts fail, reset the player state and emit queueEnd
|
|
726
728
|
player.playing = false;
|
|
@@ -50,7 +50,7 @@ class Player {
|
|
|
50
50
|
/** The autoplay state of the player. */
|
|
51
51
|
isAutoplay = false;
|
|
52
52
|
/** The number of times to try autoplay before emitting queueEnd. */
|
|
53
|
-
autoplayTries =
|
|
53
|
+
autoplayTries = 3;
|
|
54
54
|
static _manager;
|
|
55
55
|
data = {};
|
|
56
56
|
dynamicLoopInterval = null;
|
|
@@ -375,7 +375,7 @@ class Player {
|
|
|
375
375
|
* @returns {Promise<Track[]>} - Array of recommended tracks.
|
|
376
376
|
*/
|
|
377
377
|
async getRecommendedTracks(track) {
|
|
378
|
-
const tracks = await Utils_1.AutoPlayUtils.getRecommendedTracks(
|
|
378
|
+
const tracks = await Utils_1.AutoPlayUtils.getRecommendedTracks(track);
|
|
379
379
|
return tracks;
|
|
380
380
|
}
|
|
381
381
|
/**
|
package/dist/structures/Utils.js
CHANGED
|
@@ -143,274 +143,224 @@ 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
|
-
|
|
265
|
+
try {
|
|
266
|
+
if (!track.uri.includes("spotify")) {
|
|
267
|
+
const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.Spotify }, track.requester);
|
|
268
|
+
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
271
|
+
if (res.loadType === LoadTypes.Playlist) {
|
|
272
|
+
res.tracks = res.playlist.tracks;
|
|
273
|
+
}
|
|
274
|
+
if (!res.tracks.length) {
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
track = res.tracks[0];
|
|
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;
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
console.error("[AutoPlay] Failed to get spotify access token:", error.response?.status, error.response?.data || error.message);
|
|
309
|
+
return [];
|
|
310
|
+
}
|
|
311
|
+
let json;
|
|
312
|
+
try {
|
|
313
|
+
const response = await axios_1.default.get(`https://api.spotify.com/v1/recommendations`, {
|
|
314
|
+
params: { limit: 10, seed_tracks: track.identifier },
|
|
315
|
+
headers: {
|
|
316
|
+
Authorization: `Bearer ${body.accessToken}`,
|
|
317
|
+
"Content-Type": "application/json",
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
json = response.data;
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
console.error("[AutoPlay] Failed to fetch spotify recommendations:", error.response?.status, error.response?.data || error.message);
|
|
324
|
+
return [];
|
|
325
|
+
}
|
|
326
|
+
if (!json.tracks || !json.tracks.length) {
|
|
327
|
+
return [];
|
|
328
|
+
}
|
|
329
|
+
const recommendedTrackId = json.tracks[Math.floor(Math.random() * json.tracks.length)].id;
|
|
330
|
+
const res = await this.manager.search({ query: `https://open.spotify.com/track/${recommendedTrackId}`, source: Manager_1.SearchPlatform.Spotify }, track.requester);
|
|
316
331
|
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
317
|
-
console.error("[AutoPlay] Search returned empty or error result. Returning an empty array.");
|
|
318
332
|
return [];
|
|
319
333
|
}
|
|
320
334
|
if (res.loadType === LoadTypes.Playlist) {
|
|
321
|
-
console.log("[AutoPlay] Search returned a playlist. Flattening tracks.");
|
|
322
335
|
res.tracks = res.playlist.tracks;
|
|
323
336
|
}
|
|
324
337
|
if (!res.tracks.length) {
|
|
325
|
-
console.error("[AutoPlay] No tracks found in the search. Returning an empty array.");
|
|
326
338
|
return [];
|
|
327
339
|
}
|
|
328
|
-
|
|
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
|
-
}
|
|
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;
|
|
340
|
+
return res.tracks;
|
|
382
341
|
}
|
|
383
|
-
|
|
384
|
-
console.error("[AutoPlay]
|
|
342
|
+
catch (error) {
|
|
343
|
+
console.error("[AutoPlay] Unexpected spotify error:", error.message || error);
|
|
385
344
|
return [];
|
|
386
345
|
}
|
|
387
|
-
|
|
388
|
-
return res.tracks;
|
|
346
|
+
break;
|
|
389
347
|
case "deezer":
|
|
390
|
-
console.log("[AutoPlay] Checking if track URI includes 'deezer':", track.uri);
|
|
391
348
|
if (!track.uri.includes("deezer")) {
|
|
392
|
-
console.log("[AutoPlay] Track URI does not include 'deezer'. Searching for track:", `${track.author} - ${track.title}`);
|
|
393
349
|
const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.Deezer }, track.requester);
|
|
394
350
|
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
395
|
-
console.error("[AutoPlay] Search returned empty or error result. Returning an empty array.");
|
|
396
351
|
return [];
|
|
397
352
|
}
|
|
398
353
|
if (res.loadType === LoadTypes.Playlist) {
|
|
399
|
-
console.log("[AutoPlay] Search returned a playlist. Flattening tracks.");
|
|
400
354
|
res.tracks = res.playlist.tracks;
|
|
401
355
|
}
|
|
402
356
|
if (!res.tracks.length) {
|
|
403
|
-
console.error("[AutoPlay] No tracks found in the search. Returning an empty array.");
|
|
404
357
|
return [];
|
|
405
358
|
}
|
|
406
|
-
console.log("[AutoPlay] Track found in search:", res.tracks[0].uri);
|
|
407
359
|
track = res.tracks[0];
|
|
408
360
|
}
|
|
409
361
|
const identifier = `dzrec:${track.identifier}`;
|
|
410
|
-
console.log("[AutoPlay] Generating Deezer recommendation identifier:", identifier);
|
|
411
362
|
const recommendedResult = (await this.manager.useableNode.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
|
|
412
363
|
if (!recommendedResult) {
|
|
413
|
-
console.error("[AutoPlay] No recommended result received from Deezer. Returning an empty array.");
|
|
414
364
|
return [];
|
|
415
365
|
}
|
|
416
366
|
let tracks = [];
|
|
@@ -418,15 +368,12 @@ class AutoPlayUtils {
|
|
|
418
368
|
const requester = track.requester;
|
|
419
369
|
switch (recommendedResult.loadType) {
|
|
420
370
|
case LoadTypes.Search:
|
|
421
|
-
console.log("[AutoPlay] Recommended result is of type 'Search'. Building tracks.");
|
|
422
371
|
tracks = recommendedResult.data.map((track) => TrackUtils.build(track, requester));
|
|
423
372
|
break;
|
|
424
373
|
case LoadTypes.Track:
|
|
425
|
-
console.log("[AutoPlay] Recommended result is of type 'Track'. Building a single track.");
|
|
426
374
|
tracks = [TrackUtils.build(recommendedResult.data, requester)];
|
|
427
375
|
break;
|
|
428
376
|
case LoadTypes.Playlist: {
|
|
429
|
-
console.log("[AutoPlay] Recommended result is of type 'Playlist'. Building playlist.");
|
|
430
377
|
const playlistData = recommendedResult.data;
|
|
431
378
|
tracks = playlistData.tracks.map((track) => TrackUtils.build(track, requester));
|
|
432
379
|
playlist = {
|
|
@@ -441,42 +388,38 @@ class AutoPlayUtils {
|
|
|
441
388
|
}
|
|
442
389
|
const result = { loadType: recommendedResult.loadType, tracks, playlist };
|
|
443
390
|
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
391
|
return [];
|
|
446
392
|
}
|
|
447
393
|
if (result.loadType === LoadTypes.Playlist) {
|
|
448
|
-
console.log("[AutoPlay] Final result load type is Playlist. Flattening tracks.");
|
|
449
394
|
result.tracks = result.playlist.tracks;
|
|
450
395
|
}
|
|
451
396
|
if (!result.tracks.length) {
|
|
452
|
-
console.error("[AutoPlay] No tracks found in final result. Returning an empty array.");
|
|
453
397
|
return [];
|
|
454
398
|
}
|
|
455
|
-
console.log("[AutoPlay] Tracks found and ready to return.");
|
|
456
399
|
return result.tracks;
|
|
400
|
+
break;
|
|
457
401
|
case "soundcloud":
|
|
458
|
-
console.log("[AutoPlay] Checking if track URI includes 'soundcloud':", track.uri);
|
|
459
402
|
if (!track.uri.includes("soundcloud")) {
|
|
460
|
-
console.log("[AutoPlay] Track URI does not include 'soundcloud'. Searching for track:", `${track.author} - ${track.title}`);
|
|
461
403
|
const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.SoundCloud }, track.requester);
|
|
462
404
|
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
463
|
-
console.error("[AutoPlay] Search returned empty or error result. Returning an empty array.");
|
|
464
405
|
return [];
|
|
465
406
|
}
|
|
466
407
|
if (res.loadType === LoadTypes.Playlist) {
|
|
467
|
-
console.log("[AutoPlay] Search returned a playlist. Flattening tracks.");
|
|
468
408
|
res.tracks = res.playlist.tracks;
|
|
469
409
|
}
|
|
470
410
|
if (!res.tracks.length) {
|
|
471
|
-
console.error("[AutoPlay] No tracks found in the search. Returning an empty array.");
|
|
472
411
|
return [];
|
|
473
412
|
}
|
|
474
|
-
console.log("[AutoPlay] Track found in search:", res.tracks[0].uri);
|
|
475
413
|
track = res.tracks[0];
|
|
476
414
|
}
|
|
477
415
|
try {
|
|
478
|
-
|
|
479
|
-
|
|
416
|
+
const recommendedRes = await axios_1.default.get(`${track.uri}/recommended`).catch((err) => {
|
|
417
|
+
console.error(`[AutoPlay] Failed to fetch SoundCloud recommendations. Status: ${err.response?.status || "Unknown"}`, err.message);
|
|
418
|
+
return null;
|
|
419
|
+
});
|
|
420
|
+
if (!recommendedRes) {
|
|
421
|
+
return [];
|
|
422
|
+
}
|
|
480
423
|
const html = recommendedRes.data;
|
|
481
424
|
const dom = new jsdom_1.JSDOM(html);
|
|
482
425
|
const document = dom.window.document;
|
|
@@ -484,7 +427,6 @@ class AutoPlayUtils {
|
|
|
484
427
|
const sectionElement = secondNoscript.querySelector("section");
|
|
485
428
|
const articleElements = sectionElement.querySelectorAll("article");
|
|
486
429
|
if (!articleElements || articleElements.length === 0) {
|
|
487
|
-
console.error("[AutoPlay] No article elements found for recommendations. Returning an empty array.");
|
|
488
430
|
return [];
|
|
489
431
|
}
|
|
490
432
|
const urls = Array.from(articleElements)
|
|
@@ -495,117 +437,55 @@ class AutoPlayUtils {
|
|
|
495
437
|
})
|
|
496
438
|
.filter(Boolean);
|
|
497
439
|
if (!urls.length) {
|
|
498
|
-
console.error("[AutoPlay] No valid URLs found in the recommendations. Returning an empty array.");
|
|
499
440
|
return [];
|
|
500
441
|
}
|
|
501
442
|
const randomUrl = urls[Math.floor(Math.random() * urls.length)];
|
|
502
|
-
console.log("[AutoPlay] Selected random URL for recommended track:", randomUrl);
|
|
503
443
|
const res = await this.manager.search({ query: randomUrl, source: Manager_1.SearchPlatform.SoundCloud }, track.requester);
|
|
504
444
|
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
445
|
return [];
|
|
507
446
|
}
|
|
508
447
|
if (res.loadType === LoadTypes.Playlist) {
|
|
509
|
-
console.log("[AutoPlay] Search for recommended track returned a playlist. Flattening tracks.");
|
|
510
448
|
res.tracks = res.playlist.tracks;
|
|
511
449
|
}
|
|
512
450
|
if (!res.tracks.length) {
|
|
513
|
-
console.error("[AutoPlay] No tracks found in the search for recommended track. Returning an empty array.");
|
|
514
451
|
return [];
|
|
515
452
|
}
|
|
516
|
-
console.log("[AutoPlay] Found recommended tracks:", res.tracks.map((track) => track.uri));
|
|
517
453
|
return res.tracks;
|
|
518
454
|
}
|
|
519
455
|
catch (error) {
|
|
520
|
-
console.error("[AutoPlay] Error occurred while fetching recommendations:", error);
|
|
456
|
+
console.error("[AutoPlay] Error occurred while fetching soundcloud recommendations:", error);
|
|
521
457
|
return [];
|
|
522
458
|
}
|
|
523
459
|
break;
|
|
524
460
|
case "youtube":
|
|
525
|
-
|
|
526
|
-
|
|
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;
|
|
527
485
|
default:
|
|
528
486
|
return [];
|
|
529
487
|
}
|
|
530
488
|
}
|
|
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
489
|
}
|
|
610
490
|
exports.AutoPlayUtils = AutoPlayUtils;
|
|
611
491
|
/** Gets or extends structures to extend the built in, or already extended, classes to add more functionality. */
|
|
@@ -10,35 +10,38 @@ const Manager_1 = require("../structures/Manager");
|
|
|
10
10
|
function managerCheck(options) {
|
|
11
11
|
if (!options)
|
|
12
12
|
throw new TypeError("ManagerOptions must not be empty.");
|
|
13
|
-
const { playNextOnEnd, clientName, defaultSearchPlatform,
|
|
13
|
+
const { playNextOnEnd, clientName, defaultSearchPlatform, autoPlaySearchPlatforms, nodes, enabledPlugins, send, trackPartial, enablePriorityMode, useNode, normalizeYouTubeTitles, lastFmApiKey, maxPreviousTracks, } = options;
|
|
14
14
|
// Validate playNextOnEnd option
|
|
15
15
|
if (typeof playNextOnEnd !== "boolean") {
|
|
16
16
|
throw new TypeError('Manager option "playNextOnEnd" must be a boolean.');
|
|
17
17
|
}
|
|
18
18
|
// Validate clientName option
|
|
19
|
-
if (typeof clientName !==
|
|
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 !==
|
|
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
|
-
// Validate
|
|
31
|
-
if (
|
|
32
|
-
if (!
|
|
33
|
-
throw new TypeError(
|
|
30
|
+
// Validate autoPlaySearchPlatforms
|
|
31
|
+
if (autoPlaySearchPlatforms !== undefined) {
|
|
32
|
+
if (!Array.isArray(autoPlaySearchPlatforms)) {
|
|
33
|
+
throw new TypeError('Manager option "autoPlaySearchPlatforms" must be an array.');
|
|
34
|
+
}
|
|
35
|
+
if (!autoPlaySearchPlatforms.every((platform) => Object.values(Manager_1.AutoPlayPlatform).includes(platform))) {
|
|
36
|
+
throw new TypeError(`Manager option "autoPlaySearchPlatforms" must be an array of valid AutoPlayPlatform values.`);
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
39
|
// Validate nodes option
|
|
37
|
-
if (typeof nodes ===
|
|
40
|
+
if (typeof nodes === undefined || !Array.isArray(nodes)) {
|
|
38
41
|
throw new TypeError('Manager option "nodes" must be an array.');
|
|
39
42
|
}
|
|
40
43
|
// Validate enabledPlugins option
|
|
41
|
-
if (typeof enabledPlugins !==
|
|
44
|
+
if (typeof enabledPlugins !== undefined && !Array.isArray(enabledPlugins)) {
|
|
42
45
|
throw new TypeError('Manager option "enabledPlugins" must be a Plugin array.');
|
|
43
46
|
}
|
|
44
47
|
// Validate send option
|
|
@@ -46,7 +49,7 @@ function managerCheck(options) {
|
|
|
46
49
|
throw new TypeError('Manager option "send" must be present and a function.');
|
|
47
50
|
}
|
|
48
51
|
// Validate trackPartial option
|
|
49
|
-
if (typeof trackPartial !==
|
|
52
|
+
if (typeof trackPartial !== undefined) {
|
|
50
53
|
if (!Array.isArray(trackPartial)) {
|
|
51
54
|
throw new TypeError('Manager option "trackPartial" must be an array.');
|
|
52
55
|
}
|
|
@@ -55,7 +58,7 @@ function managerCheck(options) {
|
|
|
55
58
|
}
|
|
56
59
|
}
|
|
57
60
|
// Validate enablePriorityMode option
|
|
58
|
-
if (typeof enablePriorityMode !==
|
|
61
|
+
if (typeof enablePriorityMode !== undefined && typeof enablePriorityMode !== "boolean") {
|
|
59
62
|
throw new TypeError('Manager option "enablePriorityMode" must be a boolean.');
|
|
60
63
|
}
|
|
61
64
|
// Validate node priority if enablePriorityMode is enabled
|
|
@@ -67,7 +70,7 @@ function managerCheck(options) {
|
|
|
67
70
|
}
|
|
68
71
|
}
|
|
69
72
|
// Validate useNode option
|
|
70
|
-
if (typeof useNode !==
|
|
73
|
+
if (typeof useNode !== undefined) {
|
|
71
74
|
if (typeof useNode !== "string") {
|
|
72
75
|
throw new TypeError('Manager option "useNode" must be a string "leastLoad" or "leastPlayers".');
|
|
73
76
|
}
|
|
@@ -76,15 +79,15 @@ function managerCheck(options) {
|
|
|
76
79
|
}
|
|
77
80
|
}
|
|
78
81
|
// Validate normalizeYouTubeTitles option
|
|
79
|
-
if (typeof normalizeYouTubeTitles !==
|
|
82
|
+
if (typeof normalizeYouTubeTitles !== undefined && typeof normalizeYouTubeTitles !== "boolean") {
|
|
80
83
|
throw new TypeError('Manager option "normalizeYouTubeTitles" must be a boolean.');
|
|
81
84
|
}
|
|
82
85
|
// Validate lastFmApiKey option
|
|
83
|
-
if (typeof lastFmApiKey !==
|
|
86
|
+
if (typeof lastFmApiKey !== undefined && (typeof lastFmApiKey !== "string" || lastFmApiKey.trim().length === 0)) {
|
|
84
87
|
throw new TypeError('Manager option "lastFmApiKey" must be a non-empty string.');
|
|
85
88
|
}
|
|
86
89
|
// Validate maxPreviousTracks option
|
|
87
|
-
if (typeof maxPreviousTracks !==
|
|
90
|
+
if (typeof maxPreviousTracks !== undefined) {
|
|
88
91
|
if (typeof maxPreviousTracks !== "number" || isNaN(maxPreviousTracks)) {
|
|
89
92
|
throw new TypeError('Manager option "maxPreviousTracks" must be a number.');
|
|
90
93
|
}
|