magmastream 2.9.0-dev.3 → 2.9.0-dev.5
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 +34 -10
- package/dist/structures/Manager.js +12 -3
- package/dist/structures/Node.js +6 -6
- package/dist/structures/Player.js +2 -2
- package/dist/structures/Utils.js +327 -254
- 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,14 @@ 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
|
+
Tidal = "tidal",
|
|
1226
|
+
VKMusic = "vkmusic",
|
|
1227
|
+
YouTube = "youtube"
|
|
1228
|
+
}
|
|
1205
1229
|
declare enum PlayerStateEventTypes {
|
|
1206
1230
|
AutoPlayChange = "playerAutoplay",
|
|
1207
1231
|
ConnectionChange = "playerConnection",
|
|
@@ -1402,7 +1426,7 @@ declare class Player {
|
|
|
1402
1426
|
/** The autoplay state of the player. */
|
|
1403
1427
|
isAutoplay: boolean;
|
|
1404
1428
|
/** The number of times to try autoplay before emitting queueEnd. */
|
|
1405
|
-
autoplayTries: number
|
|
1429
|
+
autoplayTries: number;
|
|
1406
1430
|
private static _manager;
|
|
1407
1431
|
private readonly data;
|
|
1408
1432
|
private dynamicLoopInterval;
|
|
@@ -2136,5 +2160,5 @@ declare class Plugin {
|
|
|
2136
2160
|
load(manager: Manager): void;
|
|
2137
2161
|
}
|
|
2138
2162
|
|
|
2139
|
-
export { AutoPlayUtils, AvailableFilters, Filters, LoadTypes, Manager, ManagerEventTypes, Node, Player, PlayerStateEventTypes, Plugin, Queue, Rest, SearchPlatform, SeverityTypes, SponsorBlockSegment, StateTypes, Structure, TrackEndReasonTypes, TrackPartial, TrackSourceTypes, TrackUtils, UseNodeOptions };
|
|
2163
|
+
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
2164
|
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,
|
|
@@ -392,6 +391,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
392
391
|
voiceChannelId: state.options.voiceChannelId,
|
|
393
392
|
selfDeafen: state.options.selfDeafen,
|
|
394
393
|
volume: lavaPlayer.volume || state.options.volume,
|
|
394
|
+
node: nodeId,
|
|
395
395
|
};
|
|
396
396
|
this.emit(ManagerEventTypes.Debug, `[MANAGER] Recreating player: ${state.guildId} from saved file: ${JSON.stringify(state.options)}`);
|
|
397
397
|
const player = this.create(playerOptions);
|
|
@@ -924,6 +924,15 @@ var SearchPlatform;
|
|
|
924
924
|
SearchPlatform["YouTube"] = "ytsearch";
|
|
925
925
|
SearchPlatform["YouTubeMusic"] = "ytmsearch";
|
|
926
926
|
})(SearchPlatform || (exports.SearchPlatform = SearchPlatform = {}));
|
|
927
|
+
var AutoPlayPlatform;
|
|
928
|
+
(function (AutoPlayPlatform) {
|
|
929
|
+
AutoPlayPlatform["Spotify"] = "spotify";
|
|
930
|
+
AutoPlayPlatform["Deezer"] = "deezer";
|
|
931
|
+
AutoPlayPlatform["SoundCloud"] = "soundcloud";
|
|
932
|
+
AutoPlayPlatform["Tidal"] = "tidal";
|
|
933
|
+
AutoPlayPlatform["VKMusic"] = "vkmusic";
|
|
934
|
+
AutoPlayPlatform["YouTube"] = "youtube";
|
|
935
|
+
})(AutoPlayPlatform || (exports.AutoPlayPlatform = AutoPlayPlatform = {}));
|
|
927
936
|
var PlayerStateEventTypes;
|
|
928
937
|
(function (PlayerStateEventTypes) {
|
|
929
938
|
PlayerStateEventTypes["AutoPlayChange"] = "playerAutoplay";
|
package/dist/structures/Node.js
CHANGED
|
@@ -602,13 +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
610
|
return false;
|
|
611
|
-
const tracks = await Utils_1.AutoPlayUtils.getRecommendedTracks(
|
|
611
|
+
const tracks = await Utils_1.AutoPlayUtils.getRecommendedTracks(lastTrack);
|
|
612
612
|
if (tracks.length) {
|
|
613
613
|
player.queue.add(tracks[0]);
|
|
614
614
|
await player.play();
|
|
@@ -716,13 +716,13 @@ class Node {
|
|
|
716
716
|
this.manager.emit(Manager_1.ManagerEventTypes.QueueEnd, player, track, payload);
|
|
717
717
|
return;
|
|
718
718
|
}
|
|
719
|
-
let
|
|
719
|
+
let attempt = 1;
|
|
720
720
|
let success = false;
|
|
721
|
-
while (
|
|
722
|
-
success = await this.handleAutoplay(player,
|
|
721
|
+
while (attempt <= player.autoplayTries) {
|
|
722
|
+
success = await this.handleAutoplay(player, attempt);
|
|
723
723
|
if (success)
|
|
724
724
|
return;
|
|
725
|
-
|
|
725
|
+
attempt++;
|
|
726
726
|
}
|
|
727
727
|
// If all attempts fail, reset the player state and emit queueEnd
|
|
728
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,58 +143,42 @@ class AutoPlayUtils {
|
|
|
143
143
|
throw new Error("AutoPlayUtils.init() requires a valid Manager instance.");
|
|
144
144
|
this.manager = manager;
|
|
145
145
|
}
|
|
146
|
-
|
|
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) {
|
|
147
152
|
const node = this.manager.useableNode;
|
|
148
153
|
if (!node) {
|
|
149
154
|
throw new Error("No available nodes.");
|
|
150
155
|
}
|
|
151
|
-
if (!player.isAutoplay) {
|
|
152
|
-
return [];
|
|
153
|
-
}
|
|
154
|
-
if (attempt >= player.autoplayTries) {
|
|
155
|
-
return [];
|
|
156
|
-
}
|
|
157
|
-
if (!player.queue.previous.length) {
|
|
158
|
-
return [];
|
|
159
|
-
}
|
|
160
156
|
const apiKey = this.manager.options.lastFmApiKey;
|
|
161
157
|
const enabledSources = node.info.sourceManagers;
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
[Manager_1.SearchPlatform.VKMusic]: "vkmusic",
|
|
173
|
-
[Manager_1.SearchPlatform.YouTube]: "youtube",
|
|
174
|
-
[Manager_1.SearchPlatform.YouTubeMusic]: "youtube",
|
|
175
|
-
};
|
|
176
|
-
const mappedPlatform = platformMapping[autoPlaySearchPlatform];
|
|
177
|
-
// Last attempt fallback to YouTube
|
|
178
|
-
if (attempt === player.autoplayTries - 1 && player.autoplayTries > 1 && enabledSources.includes("youtube")) {
|
|
179
|
-
return await this.getRecommendedTracksFromYouTube(track);
|
|
180
|
-
}
|
|
181
|
-
// Check if the preferred autoplay platform is supported and enabled
|
|
182
|
-
if (mappedPlatform && supportedPlatforms.includes(mappedPlatform) && enabledSources.includes(mappedPlatform)) {
|
|
183
|
-
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
|
+
}
|
|
184
168
|
}
|
|
185
169
|
// Check if Last.fm API is available
|
|
186
170
|
if (apiKey) {
|
|
187
171
|
return await this.getRecommendedTracksFromLastFm(track, apiKey);
|
|
188
172
|
}
|
|
189
|
-
// Fallback to YouTube if all else fails
|
|
190
|
-
if (enabledSources.includes("youtube")) {
|
|
191
|
-
return await this.getRecommendedTracksFromYouTube(track);
|
|
192
|
-
}
|
|
193
173
|
return [];
|
|
194
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
|
+
*/
|
|
195
181
|
static async getRecommendedTracksFromLastFm(track, apiKey) {
|
|
196
|
-
const enabledSources = this.manager.useableNode.info.sourceManagers;
|
|
197
|
-
const selectedSource = this.selectPlatform(enabledSources);
|
|
198
182
|
let { author: artist } = track;
|
|
199
183
|
const { title } = track;
|
|
200
184
|
if (!artist || !title) {
|
|
@@ -206,7 +190,7 @@ class AutoPlayUtils {
|
|
|
206
190
|
return [];
|
|
207
191
|
}
|
|
208
192
|
const randomTrack = response.data.toptracks.track[Math.floor(Math.random() * response.data.toptracks.track.length)];
|
|
209
|
-
const res = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source:
|
|
193
|
+
const res = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: this.manager.options.defaultSearchPlatform }, track.requester);
|
|
210
194
|
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
211
195
|
return [];
|
|
212
196
|
}
|
|
@@ -233,7 +217,7 @@ class AutoPlayUtils {
|
|
|
233
217
|
response = await axios_1.default.get(url);
|
|
234
218
|
}
|
|
235
219
|
catch (error) {
|
|
236
|
-
console.
|
|
220
|
+
console.error("[AutoPlay] Error fetching similar tracks from Last.fm:", error);
|
|
237
221
|
return [];
|
|
238
222
|
}
|
|
239
223
|
if (response.data.error || !response.data.similartracks?.track?.length) {
|
|
@@ -244,7 +228,7 @@ class AutoPlayUtils {
|
|
|
244
228
|
return [];
|
|
245
229
|
}
|
|
246
230
|
const randomTrack = retryResponse.data.toptracks.track[Math.floor(Math.random() * retryResponse.data.toptracks.track.length)];
|
|
247
|
-
const res = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source:
|
|
231
|
+
const res = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: this.manager.options.defaultSearchPlatform }, track.requester);
|
|
248
232
|
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
249
233
|
return [];
|
|
250
234
|
}
|
|
@@ -258,7 +242,7 @@ class AutoPlayUtils {
|
|
|
258
242
|
if (!randomTrack) {
|
|
259
243
|
return [];
|
|
260
244
|
}
|
|
261
|
-
const res = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source:
|
|
245
|
+
const res = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: this.manager.options.defaultSearchPlatform }, track.requester);
|
|
262
246
|
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
263
247
|
return [];
|
|
264
248
|
}
|
|
@@ -269,12 +253,103 @@ class AutoPlayUtils {
|
|
|
269
253
|
}
|
|
270
254
|
return res.tracks;
|
|
271
255
|
}
|
|
272
|
-
|
|
273
|
-
|
|
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) {
|
|
274
264
|
case "spotify":
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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;
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
console.error("[AutoPlay] Unexpected spotify error:", error.message || error);
|
|
345
|
+
return [];
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
case "deezer":
|
|
350
|
+
{
|
|
351
|
+
if (!track.uri.includes("deezer")) {
|
|
352
|
+
const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.Deezer }, track.requester);
|
|
278
353
|
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
279
354
|
return [];
|
|
280
355
|
}
|
|
@@ -286,254 +361,252 @@ class AutoPlayUtils {
|
|
|
286
361
|
}
|
|
287
362
|
track = res.tracks[0];
|
|
288
363
|
}
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
364
|
+
const identifier = `dzrec:${track.identifier}`;
|
|
365
|
+
const recommendedResult = (await this.manager.useableNode.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
|
|
366
|
+
if (!recommendedResult) {
|
|
367
|
+
return [];
|
|
368
|
+
}
|
|
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
|
+
}
|
|
303
391
|
}
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
productType: "embed",
|
|
308
|
-
totp: totp,
|
|
309
|
-
totpVer: 5,
|
|
310
|
-
ts: timestamp,
|
|
311
|
-
};
|
|
312
|
-
let body;
|
|
313
|
-
try {
|
|
314
|
-
const response = await axios_1.default.get("https://open.spotify.com/get_access_token", { params });
|
|
315
|
-
body = response.data;
|
|
392
|
+
const result = { loadType: recommendedResult.loadType, tracks, playlist };
|
|
393
|
+
if (result.loadType === LoadTypes.Empty || result.loadType === LoadTypes.Error) {
|
|
394
|
+
return [];
|
|
316
395
|
}
|
|
317
|
-
|
|
318
|
-
|
|
396
|
+
if (result.loadType === LoadTypes.Playlist) {
|
|
397
|
+
result.tracks = result.playlist.tracks;
|
|
398
|
+
}
|
|
399
|
+
if (!result.tracks.length) {
|
|
319
400
|
return [];
|
|
320
401
|
}
|
|
321
|
-
|
|
402
|
+
return result.tracks;
|
|
403
|
+
}
|
|
404
|
+
break;
|
|
405
|
+
case "soundcloud":
|
|
406
|
+
{
|
|
407
|
+
if (!track.uri.includes("soundcloud")) {
|
|
408
|
+
const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.SoundCloud }, track.requester);
|
|
409
|
+
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
410
|
+
return [];
|
|
411
|
+
}
|
|
412
|
+
if (res.loadType === LoadTypes.Playlist) {
|
|
413
|
+
res.tracks = res.playlist.tracks;
|
|
414
|
+
}
|
|
415
|
+
if (!res.tracks.length) {
|
|
416
|
+
return [];
|
|
417
|
+
}
|
|
418
|
+
track = res.tracks[0];
|
|
419
|
+
}
|
|
322
420
|
try {
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
Authorization: `Bearer ${body.accessToken}`,
|
|
327
|
-
"Content-Type": "application/json",
|
|
328
|
-
},
|
|
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;
|
|
329
424
|
});
|
|
330
|
-
|
|
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;
|
|
331
459
|
}
|
|
332
460
|
catch (error) {
|
|
333
|
-
console.error("[AutoPlay]
|
|
334
|
-
return [];
|
|
335
|
-
}
|
|
336
|
-
if (!json.tracks || !json.tracks.length) {
|
|
461
|
+
console.error("[AutoPlay] Error occurred while fetching soundcloud recommendations:", error);
|
|
337
462
|
return [];
|
|
338
463
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
464
|
+
}
|
|
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();
|
|
343
472
|
}
|
|
344
|
-
|
|
345
|
-
|
|
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();
|
|
346
476
|
}
|
|
347
|
-
if (!
|
|
477
|
+
if (!videoID) {
|
|
348
478
|
return [];
|
|
349
479
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (!track.uri.includes("deezer")) {
|
|
358
|
-
const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.Deezer }, 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);
|
|
359
487
|
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
360
488
|
return [];
|
|
361
489
|
}
|
|
362
|
-
|
|
363
|
-
|
|
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];
|
|
364
508
|
}
|
|
365
|
-
|
|
509
|
+
const identifier = `tdrec:${track.identifier}`;
|
|
510
|
+
const recommendedResult = (await this.manager.useableNode.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
|
|
511
|
+
if (!recommendedResult) {
|
|
366
512
|
return [];
|
|
367
513
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
playlistInfo: playlistData.pluginInfo,
|
|
391
|
-
requester: requester,
|
|
392
|
-
tracks,
|
|
393
|
-
duration: tracks.reduce((acc, cur) => acc + (cur.duration || 0), 0),
|
|
394
|
-
};
|
|
395
|
-
break;
|
|
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
|
+
}
|
|
396
536
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (result.loadType === LoadTypes.Empty || result.loadType === LoadTypes.Error) {
|
|
400
|
-
return [];
|
|
401
|
-
}
|
|
402
|
-
if (result.loadType === LoadTypes.Playlist) {
|
|
403
|
-
result.tracks = result.playlist.tracks;
|
|
404
|
-
}
|
|
405
|
-
if (!result.tracks.length) {
|
|
406
|
-
return [];
|
|
407
|
-
}
|
|
408
|
-
return result.tracks;
|
|
409
|
-
case "soundcloud":
|
|
410
|
-
if (!track.uri.includes("soundcloud")) {
|
|
411
|
-
const res = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.SoundCloud }, track.requester);
|
|
412
|
-
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
537
|
+
const result = { loadType: recommendedResult.loadType, tracks, playlist };
|
|
538
|
+
if (result.loadType === LoadTypes.Empty || result.loadType === LoadTypes.Error) {
|
|
413
539
|
return [];
|
|
414
540
|
}
|
|
415
|
-
if (
|
|
416
|
-
|
|
541
|
+
if (result.loadType === LoadTypes.Playlist) {
|
|
542
|
+
result.tracks = result.playlist.tracks;
|
|
417
543
|
}
|
|
418
|
-
if (!
|
|
544
|
+
if (!result.tracks.length) {
|
|
419
545
|
return [];
|
|
420
546
|
}
|
|
421
|
-
|
|
547
|
+
return result.tracks;
|
|
422
548
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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];
|
|
430
564
|
}
|
|
431
|
-
const
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
const secondNoscript = document.querySelectorAll("noscript")[1];
|
|
435
|
-
const sectionElement = secondNoscript.querySelector("section");
|
|
436
|
-
const articleElements = sectionElement.querySelectorAll("article");
|
|
437
|
-
if (!articleElements || articleElements.length === 0) {
|
|
565
|
+
const identifier = `vkrec:${track.identifier}`;
|
|
566
|
+
const recommendedResult = (await this.manager.useableNode.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
|
|
567
|
+
if (!recommendedResult) {
|
|
438
568
|
return [];
|
|
439
569
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
+
}
|
|
449
592
|
}
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
593
|
+
const result = { loadType: recommendedResult.loadType, tracks, playlist };
|
|
594
|
+
if (result.loadType === LoadTypes.Empty || result.loadType === LoadTypes.Error) {
|
|
453
595
|
return [];
|
|
454
596
|
}
|
|
455
|
-
if (
|
|
456
|
-
|
|
597
|
+
if (result.loadType === LoadTypes.Playlist) {
|
|
598
|
+
result.tracks = result.playlist.tracks;
|
|
457
599
|
}
|
|
458
|
-
if (!
|
|
600
|
+
if (!result.tracks.length) {
|
|
459
601
|
return [];
|
|
460
602
|
}
|
|
461
|
-
return
|
|
462
|
-
}
|
|
463
|
-
catch (error) {
|
|
464
|
-
console.error("[AutoPlay] Error occurred while fetching recommendations:", error);
|
|
465
|
-
return [];
|
|
603
|
+
return result.tracks;
|
|
466
604
|
}
|
|
467
|
-
|
|
468
|
-
return this.getRecommendedTracksFromYouTube(track);
|
|
605
|
+
break;
|
|
469
606
|
default:
|
|
470
607
|
return [];
|
|
471
608
|
}
|
|
472
609
|
}
|
|
473
|
-
static async getRecommendedTracksFromYouTube(track) {
|
|
474
|
-
const hasYouTubeURL = ["youtube.com", "youtu.be"].some((url) => track.uri.includes(url));
|
|
475
|
-
let videoID = null;
|
|
476
|
-
if (hasYouTubeURL) {
|
|
477
|
-
videoID = track.uri.split("=").pop();
|
|
478
|
-
}
|
|
479
|
-
else {
|
|
480
|
-
const searchResult = await this.manager.search({ query: `${track.author} - ${track.title}`, source: Manager_1.SearchPlatform.YouTube }, track.requester);
|
|
481
|
-
videoID = searchResult.tracks[0]?.uri.split("=").pop();
|
|
482
|
-
}
|
|
483
|
-
if (!videoID) {
|
|
484
|
-
return [];
|
|
485
|
-
}
|
|
486
|
-
let randomIndex;
|
|
487
|
-
let searchURI;
|
|
488
|
-
do {
|
|
489
|
-
randomIndex = Math.floor(Math.random() * 23) + 2;
|
|
490
|
-
searchURI = `https://www.youtube.com/watch?v=${videoID}&list=RD${videoID}&index=${randomIndex}`;
|
|
491
|
-
} while (track.uri.includes(searchURI));
|
|
492
|
-
const res = await this.manager.search({ query: searchURI, source: Manager_1.SearchPlatform.YouTube }, track.requester);
|
|
493
|
-
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
|
|
494
|
-
return [];
|
|
495
|
-
}
|
|
496
|
-
const filteredTracks = res.tracks.filter((t) => t.uri !== track.uri);
|
|
497
|
-
return filteredTracks;
|
|
498
|
-
}
|
|
499
|
-
static selectPlatform(enabledSources) {
|
|
500
|
-
const { autoPlaySearchPlatform } = this.manager.options;
|
|
501
|
-
const platformMapping = {
|
|
502
|
-
[Manager_1.SearchPlatform.AppleMusic]: "applemusic",
|
|
503
|
-
[Manager_1.SearchPlatform.Bandcamp]: "bandcamp",
|
|
504
|
-
[Manager_1.SearchPlatform.Deezer]: "deezer",
|
|
505
|
-
[Manager_1.SearchPlatform.Jiosaavn]: "jiosaavn",
|
|
506
|
-
[Manager_1.SearchPlatform.SoundCloud]: "soundcloud",
|
|
507
|
-
[Manager_1.SearchPlatform.Spotify]: "spotify",
|
|
508
|
-
[Manager_1.SearchPlatform.Tidal]: "tidal",
|
|
509
|
-
[Manager_1.SearchPlatform.VKMusic]: "vkmusic",
|
|
510
|
-
[Manager_1.SearchPlatform.YouTube]: "youtube",
|
|
511
|
-
[Manager_1.SearchPlatform.YouTubeMusic]: "youtube",
|
|
512
|
-
};
|
|
513
|
-
// Try the autoPlaySearchPlatform first
|
|
514
|
-
if (enabledSources.includes(platformMapping[autoPlaySearchPlatform])) {
|
|
515
|
-
return autoPlaySearchPlatform;
|
|
516
|
-
}
|
|
517
|
-
// Fallback to other platforms in a predefined order
|
|
518
|
-
const fallbackPlatforms = [
|
|
519
|
-
Manager_1.SearchPlatform.Spotify,
|
|
520
|
-
Manager_1.SearchPlatform.Deezer,
|
|
521
|
-
Manager_1.SearchPlatform.SoundCloud,
|
|
522
|
-
Manager_1.SearchPlatform.AppleMusic,
|
|
523
|
-
Manager_1.SearchPlatform.Bandcamp,
|
|
524
|
-
Manager_1.SearchPlatform.Jiosaavn,
|
|
525
|
-
Manager_1.SearchPlatform.Tidal,
|
|
526
|
-
Manager_1.SearchPlatform.VKMusic,
|
|
527
|
-
Manager_1.SearchPlatform.YouTubeMusic,
|
|
528
|
-
Manager_1.SearchPlatform.YouTube,
|
|
529
|
-
];
|
|
530
|
-
for (const platform of fallbackPlatforms) {
|
|
531
|
-
if (enabledSources.includes(platformMapping[platform])) {
|
|
532
|
-
return platform;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
return null;
|
|
536
|
-
}
|
|
537
610
|
}
|
|
538
611
|
exports.AutoPlayUtils = AutoPlayUtils;
|
|
539
612
|
/** 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
|
}
|