magmastream 2.9.0-dev.27 → 2.9.0-dev.28
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 +3 -0
- package/dist/structures/Utils.js +87 -26
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -716,6 +716,9 @@ declare abstract class AutoPlayUtils {
|
|
|
716
716
|
* @returns An array of recommended tracks.
|
|
717
717
|
*/
|
|
718
718
|
static getRecommendedTracksFromSource(track: Track, platform: string): Promise<Track[]>;
|
|
719
|
+
static fetchSecretArray(): Promise<Buffer<ArrayBuffer>>;
|
|
720
|
+
static transformSecret(buffer: Buffer): Buffer<ArrayBuffer>;
|
|
721
|
+
static generateTotp(secretBuffer: Buffer): string;
|
|
719
722
|
}
|
|
720
723
|
/** Gets or extends structures to extend the built in, or already extended, classes to add more functionality. */
|
|
721
724
|
declare abstract class Structure {
|
package/dist/structures/Utils.js
CHANGED
|
@@ -6,6 +6,7 @@ const Manager_1 = require("./Manager");
|
|
|
6
6
|
const axios_1 = tslib_1.__importDefault(require("axios"));
|
|
7
7
|
const jsdom_1 = require("jsdom");
|
|
8
8
|
const crypto_1 = tslib_1.__importDefault(require("crypto"));
|
|
9
|
+
const cheerio_1 = tslib_1.__importDefault(require("cheerio"));
|
|
9
10
|
/** @hidden */
|
|
10
11
|
const SIZES = ["0", "1", "2", "3", "default", "mqdefault", "hqdefault", "maxresdefault"];
|
|
11
12
|
class TrackUtils {
|
|
@@ -277,36 +278,50 @@ class AutoPlayUtils {
|
|
|
277
278
|
}
|
|
278
279
|
track = res.tracks[0];
|
|
279
280
|
}
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
281
|
+
const periodMs = 30000;
|
|
282
|
+
async function getSpotifyTotpParams() {
|
|
283
|
+
try {
|
|
284
|
+
// Step 1: Fetch the secret bytes from Spotify dynamically
|
|
285
|
+
const secretBuffer = await AutoPlayUtils.fetchSecretArray();
|
|
286
|
+
// Step 2: Transform the secret (XOR)
|
|
287
|
+
const transformedSecret = AutoPlayUtils.transformSecret(secretBuffer);
|
|
288
|
+
// Step 3: Generate the TOTP string
|
|
289
|
+
const totp = AutoPlayUtils.generateTotp(transformedSecret);
|
|
290
|
+
// Step 4: Calculate the timestamp aligned with period (like your old counter * 30000)
|
|
291
|
+
const timestamp = Math.floor(Date.now() / periodMs) * periodMs;
|
|
292
|
+
// Step 5: Compose params object just like your old code
|
|
293
|
+
const params = {
|
|
294
|
+
reason: "transport",
|
|
295
|
+
productType: "embed",
|
|
296
|
+
totp,
|
|
297
|
+
totpVer: 5,
|
|
298
|
+
ts: timestamp,
|
|
299
|
+
};
|
|
300
|
+
return params;
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
console.error("Failed to generate Spotify TOTP params:", error);
|
|
304
|
+
throw error; // or handle error accordingly
|
|
305
|
+
}
|
|
306
|
+
}
|
|
303
307
|
let body;
|
|
304
308
|
try {
|
|
305
|
-
const
|
|
309
|
+
const params = await getSpotifyTotpParams();
|
|
310
|
+
const response = await axios_1.default.get("https://open.spotify.com/get_access_token", {
|
|
311
|
+
params,
|
|
312
|
+
headers: {
|
|
313
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.178 Spotify/1.2.65.255 Safari/537.36",
|
|
314
|
+
"App-Platform": "WebPlayer",
|
|
315
|
+
Referer: "https://open.spotify.com/",
|
|
316
|
+
Origin: "https://open.spotify.com",
|
|
317
|
+
"Accept-Language": "en",
|
|
318
|
+
"Content-Type": "application/json",
|
|
319
|
+
},
|
|
320
|
+
});
|
|
306
321
|
body = response.data;
|
|
307
322
|
}
|
|
308
323
|
catch (error) {
|
|
309
|
-
console.error("[AutoPlay] Failed to get spotify access token:", error.response?.status
|
|
324
|
+
console.error("[AutoPlay] Failed to get spotify access token:", error.response?.status);
|
|
310
325
|
return [];
|
|
311
326
|
}
|
|
312
327
|
let json;
|
|
@@ -321,7 +336,7 @@ class AutoPlayUtils {
|
|
|
321
336
|
json = response.data;
|
|
322
337
|
}
|
|
323
338
|
catch (error) {
|
|
324
|
-
console.error("[AutoPlay] Failed to fetch spotify recommendations:", error.response?.status
|
|
339
|
+
console.error("[AutoPlay] Failed to fetch spotify recommendations:", error.response?.status);
|
|
325
340
|
return [];
|
|
326
341
|
}
|
|
327
342
|
if (!json.tracks || !json.tracks.length) {
|
|
@@ -663,6 +678,52 @@ class AutoPlayUtils {
|
|
|
663
678
|
return [];
|
|
664
679
|
}
|
|
665
680
|
}
|
|
681
|
+
static async fetchSecretArray() {
|
|
682
|
+
// Step 1: Get Spotify homepage HTML
|
|
683
|
+
const homepageRes = await axios_1.default.get("https://open.spotify.com/");
|
|
684
|
+
const $ = cheerio_1.default.load(homepageRes.data);
|
|
685
|
+
// Step 2: Find script URL with "mobile-web-player" but not "vendor"
|
|
686
|
+
const scriptUrl = $("script[src]")
|
|
687
|
+
.map((_, el) => $(el).attr("src"))
|
|
688
|
+
.get()
|
|
689
|
+
.find((src) => src.includes("mobile-web-player") && !src.includes("vendor"));
|
|
690
|
+
if (!scriptUrl)
|
|
691
|
+
throw new Error("Secret script not found");
|
|
692
|
+
// Full URL fix (if needed)
|
|
693
|
+
const fullScriptUrl = scriptUrl.startsWith("http") ? scriptUrl : "https://open.spotify.com" + scriptUrl;
|
|
694
|
+
// Step 3: Fetch the script content
|
|
695
|
+
const scriptRes = await axios_1.default.get(fullScriptUrl);
|
|
696
|
+
const scriptContent = scriptRes.data;
|
|
697
|
+
// Step 4: Extract secret array using regex
|
|
698
|
+
const secretPattern = /\(\[(\d+(?:,\d+)+)]\)/;
|
|
699
|
+
const match = secretPattern.exec(scriptContent);
|
|
700
|
+
if (!match)
|
|
701
|
+
throw new Error("Secret array not found in script");
|
|
702
|
+
// Parse the secret array into bytes
|
|
703
|
+
const secretArray = match[1].split(",").map((n) => parseInt(n.trim(), 10));
|
|
704
|
+
return Buffer.from(secretArray);
|
|
705
|
+
}
|
|
706
|
+
static transformSecret(buffer) {
|
|
707
|
+
const transformed = Buffer.alloc(buffer.length);
|
|
708
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
709
|
+
transformed[i] = buffer[i] ^ ((i % 33) + 9);
|
|
710
|
+
}
|
|
711
|
+
return transformed;
|
|
712
|
+
}
|
|
713
|
+
static generateTotp(secretBuffer) {
|
|
714
|
+
const period = 30; // seconds
|
|
715
|
+
const digits = 6;
|
|
716
|
+
const counter = Math.floor(Date.now() / 1000 / period);
|
|
717
|
+
const counterBuffer = Buffer.alloc(8);
|
|
718
|
+
counterBuffer.writeBigInt64BE(BigInt(counter));
|
|
719
|
+
const hmac = crypto_1.default.createHmac("sha1", secretBuffer);
|
|
720
|
+
hmac.update(counterBuffer);
|
|
721
|
+
const hmacResult = hmac.digest();
|
|
722
|
+
const offset = hmacResult[hmacResult.length - 1] & 0x0f;
|
|
723
|
+
const binary = ((hmacResult[offset] & 0x7f) << 24) | ((hmacResult[offset + 1] & 0xff) << 16) | ((hmacResult[offset + 2] & 0xff) << 8) | (hmacResult[offset + 3] & 0xff);
|
|
724
|
+
const otp = binary % 10 ** digits;
|
|
725
|
+
return otp.toString().padStart(digits, "0");
|
|
726
|
+
}
|
|
666
727
|
}
|
|
667
728
|
exports.AutoPlayUtils = AutoPlayUtils;
|
|
668
729
|
/** Gets or extends structures to extend the built in, or already extended, classes to add more functionality. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magmastream",
|
|
3
|
-
"version": "2.9.0-dev.
|
|
3
|
+
"version": "2.9.0-dev.28",
|
|
4
4
|
"description": "A user-friendly Lavalink client designed for NodeJS.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@discordjs/collection": "^2.1.1",
|
|
32
32
|
"axios": "^1.9.0",
|
|
33
|
+
"cheerio": "^1.1.0",
|
|
33
34
|
"events": "^3.3.0",
|
|
34
35
|
"ioredis": "^5.6.1",
|
|
35
36
|
"jsdom": "^26.1.0",
|