muthera 1.0.8 → 1.0.10
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/package.json +1 -1
- package/src/functions/autoPlay.js +250 -204
- package/src/structures/Muthera.js +2 -0
- package/src/structures/mutheraPlayer.js +84 -11
package/package.json
CHANGED
|
@@ -1,204 +1,250 @@
|
|
|
1
|
-
const { JSDOM } = require("jsdom");
|
|
2
|
-
|
|
3
|
-
async function scAutoPlay(url) {
|
|
4
|
-
const res = await fetch(`${url}/recommended`);
|
|
5
|
-
|
|
6
|
-
const html = await res.text();
|
|
7
|
-
|
|
8
|
-
const dom = new JSDOM(html);
|
|
9
|
-
const document = dom.window.document;
|
|
10
|
-
|
|
11
|
-
const secondNoscript = document.querySelectorAll("noscript")[1];
|
|
12
|
-
const sectionElement = secondNoscript.querySelector("section");
|
|
13
|
-
const articleElements = sectionElement.querySelectorAll("article");
|
|
14
|
-
|
|
15
|
-
articleElements.forEach((articleElement) => {
|
|
16
|
-
const h2Element = articleElement.querySelector('h2[itemprop="name"]');
|
|
17
|
-
|
|
18
|
-
const aElement = h2Element.querySelector('a[itemprop="url"]');
|
|
19
|
-
const href = `https://soundcloud.com${aElement.getAttribute("href")}`;
|
|
20
|
-
|
|
21
|
-
return href;
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function amAutoPlay(track_id, retries = 3) {
|
|
26
|
-
async function fetchWithRetry(url, options, retries) {
|
|
27
|
-
for (let attempt = 0; attempt < retries; attempt++) {
|
|
28
|
-
try {
|
|
29
|
-
const res = await fetch(url, options);
|
|
30
|
-
return await res.json();
|
|
31
|
-
} catch (error) {
|
|
32
|
-
if (attempt === retries - 1) {
|
|
33
|
-
throw new Error("No track found.");
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
const trackData = await fetchWithRetry(
|
|
39
|
-
`https://itunes.apple.com/lookup?id=${track_id}`,
|
|
40
|
-
retries
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
if (!trackData.results || trackData.results.length === 0) {
|
|
44
|
-
throw new Error("No track found.");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const track = trackData.results[0];
|
|
48
|
-
const genreName = track.primaryGenreName;
|
|
49
|
-
|
|
50
|
-
const relatedData = await fetchWithRetry(
|
|
51
|
-
`https://itunes.apple.com/search?term=${encodeURIComponent(
|
|
52
|
-
genreName
|
|
53
|
-
)}&entity=song`,
|
|
54
|
-
retries
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
if (!relatedData.results || relatedData.results.length === 0) {
|
|
58
|
-
throw new Error("No track found.");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return relatedData.results.filter(
|
|
62
|
-
(item) => item.wrapperType === "track" && item.trackId !== track_id
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async function spAutoPlay(track_id, retries = 3) {
|
|
67
|
-
async function fetchWithRetry(url, options = {}, retriesLeft = retries) {
|
|
68
|
-
for (let attempt = 0; attempt < retriesLeft; attempt++) {
|
|
69
|
-
try {
|
|
70
|
-
const res = await fetch(url, options);
|
|
71
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
72
|
-
return await res.json();
|
|
73
|
-
} catch (error) {
|
|
74
|
-
if (attempt === retriesLeft - 1) throw new Error("No track found.");
|
|
75
|
-
await new Promise((r) => setTimeout(r, 200 * (attempt + 1)));
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (!
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
1
|
+
const { JSDOM } = require("jsdom");
|
|
2
|
+
|
|
3
|
+
async function scAutoPlay(url) {
|
|
4
|
+
const res = await fetch(`${url}/recommended`);
|
|
5
|
+
|
|
6
|
+
const html = await res.text();
|
|
7
|
+
|
|
8
|
+
const dom = new JSDOM(html);
|
|
9
|
+
const document = dom.window.document;
|
|
10
|
+
|
|
11
|
+
const secondNoscript = document.querySelectorAll("noscript")[1];
|
|
12
|
+
const sectionElement = secondNoscript.querySelector("section");
|
|
13
|
+
const articleElements = sectionElement.querySelectorAll("article");
|
|
14
|
+
|
|
15
|
+
articleElements.forEach((articleElement) => {
|
|
16
|
+
const h2Element = articleElement.querySelector('h2[itemprop="name"]');
|
|
17
|
+
|
|
18
|
+
const aElement = h2Element.querySelector('a[itemprop="url"]');
|
|
19
|
+
const href = `https://soundcloud.com${aElement.getAttribute("href")}`;
|
|
20
|
+
|
|
21
|
+
return href;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function amAutoPlay(track_id, retries = 3) {
|
|
26
|
+
async function fetchWithRetry(url, options, retries) {
|
|
27
|
+
for (let attempt = 0; attempt < retries; attempt++) {
|
|
28
|
+
try {
|
|
29
|
+
const res = await fetch(url, options);
|
|
30
|
+
return await res.json();
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (attempt === retries - 1) {
|
|
33
|
+
throw new Error("No track found.");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const trackData = await fetchWithRetry(
|
|
39
|
+
`https://itunes.apple.com/lookup?id=${track_id}`,
|
|
40
|
+
retries
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
if (!trackData.results || trackData.results.length === 0) {
|
|
44
|
+
throw new Error("No track found.");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const track = trackData.results[0];
|
|
48
|
+
const genreName = track.primaryGenreName;
|
|
49
|
+
|
|
50
|
+
const relatedData = await fetchWithRetry(
|
|
51
|
+
`https://itunes.apple.com/search?term=${encodeURIComponent(
|
|
52
|
+
genreName
|
|
53
|
+
)}&entity=song`,
|
|
54
|
+
retries
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
if (!relatedData.results || relatedData.results.length === 0) {
|
|
58
|
+
throw new Error("No track found.");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return relatedData.results.filter(
|
|
62
|
+
(item) => item.wrapperType === "track" && item.trackId !== track_id
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function spAutoPlay(track_id, clientId, clientSecret, retries = 3) {
|
|
67
|
+
async function fetchWithRetry(url, options = {}, retriesLeft = retries) {
|
|
68
|
+
for (let attempt = 0; attempt < retriesLeft; attempt++) {
|
|
69
|
+
try {
|
|
70
|
+
const res = await fetch(url, options);
|
|
71
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
72
|
+
return await res.json();
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (attempt === retriesLeft - 1) throw new Error("No track found.");
|
|
75
|
+
await new Promise((r) => setTimeout(r, 200 * (attempt + 1)));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let accessToken;
|
|
81
|
+
|
|
82
|
+
if (clientId && clientSecret) {
|
|
83
|
+
// Client Credentials flow — no user login needed, suitable for bots
|
|
84
|
+
const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
|
|
85
|
+
const tokenResponse = await fetchWithRetry(
|
|
86
|
+
"https://accounts.spotify.com/api/token",
|
|
87
|
+
{
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: {
|
|
90
|
+
Authorization: `Basic ${credentials}`,
|
|
91
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
92
|
+
},
|
|
93
|
+
body: "grant_type=client_credentials",
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
accessToken = tokenResponse.access_token;
|
|
97
|
+
} else {
|
|
98
|
+
// Fallback: public embed token (no credentials required, but limited)
|
|
99
|
+
const tokenResponse = await fetchWithRetry(
|
|
100
|
+
"https://open.spotify.com/get_access_token?reason=transport&productType=embed"
|
|
101
|
+
);
|
|
102
|
+
accessToken = tokenResponse.accessToken;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!accessToken) throw new Error("Failed to get Spotify token.");
|
|
106
|
+
|
|
107
|
+
const headers = {
|
|
108
|
+
Authorization: `Bearer ${accessToken}`,
|
|
109
|
+
"Content-Type": "application/json",
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Get the track to find its artist and genre info
|
|
113
|
+
const trackData = await fetchWithRetry(
|
|
114
|
+
`https://api.spotify.com/v1/tracks/${track_id}`,
|
|
115
|
+
{ headers }
|
|
116
|
+
);
|
|
117
|
+
const artistId = trackData?.artists?.[0]?.id;
|
|
118
|
+
if (!artistId) throw new Error("No artist found for track.");
|
|
119
|
+
|
|
120
|
+
const candidates = new Map();
|
|
121
|
+
|
|
122
|
+
// 1. Get artist's top tracks (always works with Client Credentials)
|
|
123
|
+
try {
|
|
124
|
+
const topTracksData = await fetchWithRetry(
|
|
125
|
+
`https://api.spotify.com/v1/artists/${artistId}/top-tracks?market=US`,
|
|
126
|
+
{ headers }
|
|
127
|
+
);
|
|
128
|
+
for (const t of topTracksData?.tracks ?? []) {
|
|
129
|
+
if (t?.id && t.id !== track_id) candidates.set(t.id, t);
|
|
130
|
+
}
|
|
131
|
+
} catch (_) {}
|
|
132
|
+
|
|
133
|
+
// 2. Get artist's albums, then sample tracks from a random album
|
|
134
|
+
try {
|
|
135
|
+
const albumsData = await fetchWithRetry(
|
|
136
|
+
`https://api.spotify.com/v1/artists/${artistId}/albums?include_groups=album,single&market=US&limit=20`,
|
|
137
|
+
{ headers }
|
|
138
|
+
);
|
|
139
|
+
const albums = albumsData?.items ?? [];
|
|
140
|
+
if (albums.length) {
|
|
141
|
+
// Pick up to 3 random albums to sample from
|
|
142
|
+
const shuffled = albums.sort(() => Math.random() - 0.5).slice(0, 3);
|
|
143
|
+
await Promise.all(
|
|
144
|
+
shuffled.map(async (album) => {
|
|
145
|
+
try {
|
|
146
|
+
const albumTracks = await fetchWithRetry(
|
|
147
|
+
`https://api.spotify.com/v1/albums/${album.id}/tracks?limit=10`,
|
|
148
|
+
{ headers }
|
|
149
|
+
);
|
|
150
|
+
for (const t of albumTracks?.items ?? []) {
|
|
151
|
+
if (t?.id && t.id !== track_id) candidates.set(t.id, t);
|
|
152
|
+
}
|
|
153
|
+
} catch (_) {}
|
|
154
|
+
})
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
} catch (_) {}
|
|
158
|
+
|
|
159
|
+
if (!candidates.size) throw new Error("No tracks found.");
|
|
160
|
+
|
|
161
|
+
const pool = Array.from(candidates.values());
|
|
162
|
+
return pool[Math.floor(Math.random() * pool.length)].id;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function dzAutoPlay(trackId, title = null, artist = null, retries = 3) {
|
|
166
|
+
async function fetchWithRetryJson(url, options = {}, retriesLeft = 3) {
|
|
167
|
+
for (let attempt = 0; attempt < retriesLeft; attempt++) {
|
|
168
|
+
try {
|
|
169
|
+
const res = await fetch(url, options);
|
|
170
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
171
|
+
return await res.json();
|
|
172
|
+
} catch (err) {
|
|
173
|
+
if (attempt === retriesLeft - 1) throw err;
|
|
174
|
+
await new Promise((r) => setTimeout(r, 200 * (attempt + 1)));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let trackData = null;
|
|
180
|
+
|
|
181
|
+
if (trackId) {
|
|
182
|
+
try {
|
|
183
|
+
trackData = await fetchWithRetryJson(`https://api.deezer.com/track/${trackId}`, {}, retries);
|
|
184
|
+
if (trackData?.error) trackData = null;
|
|
185
|
+
} catch (_) {}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!trackData && title && artist) {
|
|
189
|
+
try {
|
|
190
|
+
const q = encodeURIComponent(`${title} ${artist}`);
|
|
191
|
+
const searchRes = await fetchWithRetryJson(`https://api.deezer.com/search?q=${q}&limit=1`, {}, retries);
|
|
192
|
+
if (searchRes?.data?.[0]) trackData = searchRes.data[0];
|
|
193
|
+
} catch (_) {}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!trackData || trackData.error) return null;
|
|
197
|
+
|
|
198
|
+
const originalTrackId = String(trackData.id);
|
|
199
|
+
const artistId = trackData.artist?.id ? String(trackData.artist.id) : null;
|
|
200
|
+
const candidates = new Map();
|
|
201
|
+
|
|
202
|
+
function addTracksArray(arr) {
|
|
203
|
+
if (!Array.isArray(arr)) return;
|
|
204
|
+
for (const t of arr) {
|
|
205
|
+
if (!t || !t.id) continue;
|
|
206
|
+
const tid = String(t.id);
|
|
207
|
+
if (tid === originalTrackId) continue;
|
|
208
|
+
if (!candidates.has(tid)) candidates.set(tid, t);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (artistId) {
|
|
213
|
+
try {
|
|
214
|
+
const topRes = await fetchWithRetryJson(`https://api.deezer.com/artist/${artistId}/top?limit=12`, {}, retries);
|
|
215
|
+
if (topRes?.data) addTracksArray(topRes.data);
|
|
216
|
+
} catch (_) {}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (artistId) {
|
|
220
|
+
try {
|
|
221
|
+
const relRes = await fetchWithRetryJson(`https://api.deezer.com/artist/${artistId}/related`, {}, retries);
|
|
222
|
+
const relatedArtists = (relRes && relRes.data) || [];
|
|
223
|
+
const toFetch = relatedArtists.slice(0, 5);
|
|
224
|
+
|
|
225
|
+
const topPromises = toFetch.map((ra) =>
|
|
226
|
+
fetchWithRetryJson(`https://api.deezer.com/artist/${ra.id}/top?limit=6`, {}, retries)
|
|
227
|
+
.then((r) => (r?.data ? r.data : []))
|
|
228
|
+
.catch(() => [])
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const topResults = await Promise.all(topPromises);
|
|
232
|
+
topResults.forEach((arr) => addTracksArray(arr));
|
|
233
|
+
} catch (_) {}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (candidates.size === 0) {
|
|
237
|
+
try {
|
|
238
|
+
const q = `${encodeURIComponent(trackData.title || "")} ${encodeURIComponent(trackData.artist?.name || "")}`;
|
|
239
|
+
const searchRes = await fetchWithRetryJson(`https://api.deezer.com/search?q=${q}&limit=20`, {}, retries);
|
|
240
|
+
if (searchRes?.data) addTracksArray(searchRes.data);
|
|
241
|
+
} catch (_) {}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const arr = Array.from(candidates.values());
|
|
245
|
+
if (!arr.length) return null;
|
|
246
|
+
const pick = arr[Math.floor(Math.random() * arr.length)];
|
|
247
|
+
return `https://www.deezer.com/track/${pick.id}`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
module.exports = { scAutoPlay, spAutoPlay, amAutoPlay, dzAutoPlay };
|
|
@@ -23,6 +23,8 @@ class Muthera extends EventEmitter {
|
|
|
23
23
|
this.initiated = false;
|
|
24
24
|
this.send = options.send || null;
|
|
25
25
|
this.defaultSearchPlatform = options.defaultSearchPlatform || "ytsearch";
|
|
26
|
+
this.spotifyClientId = options.spotifyClientId || null;
|
|
27
|
+
this.spotifyClientSecret = options.spotifyClientSecret || null;
|
|
26
28
|
this.tracks = [];
|
|
27
29
|
this.loadType = null;
|
|
28
30
|
this.playlistInfo = null;
|
|
@@ -117,13 +117,16 @@ class Player extends EventEmitter {
|
|
|
117
117
|
)
|
|
118
118
|
return fail();
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
// Filter out the previous track to avoid immediate duplicate
|
|
121
|
+
const tracks = response.tracks.filter(
|
|
122
|
+
(t) => t.info.identifier !== player.previous.info.identifier
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (!tracks.length) return fail();
|
|
124
126
|
|
|
125
127
|
if (this.connected) {
|
|
126
|
-
|
|
128
|
+
// Push entire Mix playlist into queue
|
|
129
|
+
for (const t of tracks) this.queue.push(t);
|
|
127
130
|
await this.play();
|
|
128
131
|
} else {
|
|
129
132
|
return fail();
|
|
@@ -148,13 +151,16 @@ class Player extends EventEmitter {
|
|
|
148
151
|
)
|
|
149
152
|
return fail();
|
|
150
153
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
// Filter out the previous track to avoid immediate duplicate
|
|
155
|
+
const tracks = response.tracks.filter(
|
|
156
|
+
(t) => t.info.identifier !== player.previous.info.identifier
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
if (!tracks.length) return fail();
|
|
155
160
|
|
|
156
161
|
if (this.connected) {
|
|
157
|
-
|
|
162
|
+
// Push entire Mix playlist into queue
|
|
163
|
+
for (const t of tracks) this.queue.push(t);
|
|
158
164
|
await this.play();
|
|
159
165
|
} else {
|
|
160
166
|
return fail();
|
|
@@ -223,6 +229,41 @@ class Player extends EventEmitter {
|
|
|
223
229
|
} else {
|
|
224
230
|
return fail();
|
|
225
231
|
}
|
|
232
|
+
return this;
|
|
233
|
+
} catch (e) {
|
|
234
|
+
return fail(e);
|
|
235
|
+
}
|
|
236
|
+
} else if (player.previous.info.sourceName === "spsearch") {
|
|
237
|
+
try {
|
|
238
|
+
const trackId = await spAutoPlay(
|
|
239
|
+
player.previous.info.identifier,
|
|
240
|
+
this.muthera.spotifyClientId,
|
|
241
|
+
this.muthera.spotifyClientSecret
|
|
242
|
+
);
|
|
243
|
+
if (!trackId) return fail();
|
|
244
|
+
|
|
245
|
+
const response = await this.muthera.resolve({
|
|
246
|
+
query: `https://open.spotify.com/track/${trackId}`,
|
|
247
|
+
requester: player.previous.info.requester,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (
|
|
251
|
+
!response ||
|
|
252
|
+
!response.tracks ||
|
|
253
|
+
["error", "empty"].includes(response.loadType)
|
|
254
|
+
)
|
|
255
|
+
return fail();
|
|
256
|
+
|
|
257
|
+
const track =
|
|
258
|
+
response.tracks[Math.floor(Math.random() * response.tracks.length)];
|
|
259
|
+
|
|
260
|
+
if (this.connected) {
|
|
261
|
+
this.queue.push(track);
|
|
262
|
+
await this.play();
|
|
263
|
+
} else {
|
|
264
|
+
return fail();
|
|
265
|
+
}
|
|
266
|
+
|
|
226
267
|
return this;
|
|
227
268
|
} catch (e) {
|
|
228
269
|
return fail(e);
|
|
@@ -267,7 +308,39 @@ class Player extends EventEmitter {
|
|
|
267
308
|
const title = player.previous.info.title;
|
|
268
309
|
const platform = this.muthera.defaultSearchPlatform;
|
|
269
310
|
|
|
270
|
-
if (platform.includes("
|
|
311
|
+
if (platform.includes("spsearch")) {
|
|
312
|
+
// Use Spotify related-artist API for proper similar song recommendations
|
|
313
|
+
const trackId = await spAutoPlay(
|
|
314
|
+
player.previous.info.identifier,
|
|
315
|
+
this.muthera.spotifyClientId,
|
|
316
|
+
this.muthera.spotifyClientSecret
|
|
317
|
+
);
|
|
318
|
+
if (!trackId) return fail();
|
|
319
|
+
|
|
320
|
+
const response = await this.muthera.resolve({
|
|
321
|
+
query: `https://open.spotify.com/track/${trackId}`,
|
|
322
|
+
requester: player.previous.info.requester,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
if (
|
|
326
|
+
!response ||
|
|
327
|
+
!response.tracks ||
|
|
328
|
+
["error", "empty"].includes(response.loadType) ||
|
|
329
|
+
response.tracks.length === 0
|
|
330
|
+
)
|
|
331
|
+
return fail();
|
|
332
|
+
|
|
333
|
+
const track =
|
|
334
|
+
response.tracks[Math.floor(Math.random() * response.tracks.length)];
|
|
335
|
+
|
|
336
|
+
if (this.connected) {
|
|
337
|
+
this.queue.push(track);
|
|
338
|
+
await this.play();
|
|
339
|
+
} else {
|
|
340
|
+
return fail();
|
|
341
|
+
}
|
|
342
|
+
return this;
|
|
343
|
+
} else if (platform.includes("dz")) {
|
|
271
344
|
// Use Deezer related-artist API for proper similar song recommendations
|
|
272
345
|
const data = await dzAutoPlay(null, title, artist);
|
|
273
346
|
if (!data) return fail();
|