magmastream 2.9.0-dev.26 → 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 +27 -20
- package/dist/structures/Manager.js +15 -10
- 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 {
|
|
@@ -1415,57 +1418,61 @@ interface PlaylistData {
|
|
|
1415
1418
|
tracks: Track[];
|
|
1416
1419
|
}
|
|
1417
1420
|
declare enum ManagerEventTypes {
|
|
1421
|
+
ChapterStarted = "chapterStarted",
|
|
1422
|
+
ChaptersLoaded = "chaptersLoaded",
|
|
1418
1423
|
Debug = "debug",
|
|
1424
|
+
NodeConnect = "nodeConnect",
|
|
1419
1425
|
NodeCreate = "nodeCreate",
|
|
1420
1426
|
NodeDestroy = "nodeDestroy",
|
|
1421
|
-
NodeConnect = "nodeConnect",
|
|
1422
|
-
NodeReconnect = "nodeReconnect",
|
|
1423
1427
|
NodeDisconnect = "nodeDisconnect",
|
|
1424
1428
|
NodeError = "nodeError",
|
|
1425
1429
|
NodeRaw = "nodeRaw",
|
|
1430
|
+
NodeReconnect = "nodeReconnect",
|
|
1426
1431
|
PlayerCreate = "playerCreate",
|
|
1427
1432
|
PlayerDestroy = "playerDestroy",
|
|
1428
|
-
PlayerStateUpdate = "playerStateUpdate",
|
|
1429
|
-
PlayerMove = "playerMove",
|
|
1430
1433
|
PlayerDisconnect = "playerDisconnect",
|
|
1434
|
+
PlayerMove = "playerMove",
|
|
1435
|
+
PlayerRestored = "playerRestored",
|
|
1436
|
+
PlayerStateUpdate = "playerStateUpdate",
|
|
1431
1437
|
QueueEnd = "queueEnd",
|
|
1438
|
+
RestoreComplete = "restoreComplete",
|
|
1439
|
+
SegmentSkipped = "segmentSkipped",
|
|
1440
|
+
SegmentsLoaded = "segmentsLoaded",
|
|
1432
1441
|
SocketClosed = "socketClosed",
|
|
1433
|
-
TrackStart = "trackStart",
|
|
1434
1442
|
TrackEnd = "trackEnd",
|
|
1435
|
-
TrackStuck = "trackStuck",
|
|
1436
1443
|
TrackError = "trackError",
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
ChapterStarted = "chapterStarted",
|
|
1440
|
-
ChaptersLoaded = "chaptersLoaded"
|
|
1444
|
+
TrackStart = "trackStart",
|
|
1445
|
+
TrackStuck = "trackStuck"
|
|
1441
1446
|
}
|
|
1442
1447
|
interface ManagerEvents {
|
|
1448
|
+
[ManagerEventTypes.ChapterStarted]: [player: Player, track: Track, payload: SponsorBlockChapterStarted];
|
|
1449
|
+
[ManagerEventTypes.ChaptersLoaded]: [player: Player, track: Track, payload: SponsorBlockChaptersLoaded];
|
|
1443
1450
|
[ManagerEventTypes.Debug]: [info: string];
|
|
1451
|
+
[ManagerEventTypes.NodeConnect]: [node: Node];
|
|
1444
1452
|
[ManagerEventTypes.NodeCreate]: [node: Node];
|
|
1445
1453
|
[ManagerEventTypes.NodeDestroy]: [node: Node];
|
|
1446
|
-
[ManagerEventTypes.NodeConnect]: [node: Node];
|
|
1447
|
-
[ManagerEventTypes.NodeReconnect]: [node: Node];
|
|
1448
1454
|
[ManagerEventTypes.NodeDisconnect]: [node: Node, reason: {
|
|
1449
1455
|
code?: number;
|
|
1450
1456
|
reason?: string;
|
|
1451
1457
|
}];
|
|
1452
1458
|
[ManagerEventTypes.NodeError]: [node: Node, error: Error];
|
|
1453
1459
|
[ManagerEventTypes.NodeRaw]: [payload: unknown];
|
|
1460
|
+
[ManagerEventTypes.NodeReconnect]: [node: Node];
|
|
1454
1461
|
[ManagerEventTypes.PlayerCreate]: [player: Player];
|
|
1455
1462
|
[ManagerEventTypes.PlayerDestroy]: [player: Player];
|
|
1456
|
-
[ManagerEventTypes.PlayerStateUpdate]: [oldPlayer: Player, newPlayer: Player, changeType: PlayerStateUpdateEvent];
|
|
1457
|
-
[ManagerEventTypes.PlayerMove]: [player: Player, initChannel: string, newChannel: string];
|
|
1458
1463
|
[ManagerEventTypes.PlayerDisconnect]: [player: Player, oldChannel: string];
|
|
1464
|
+
[ManagerEventTypes.PlayerMove]: [player: Player, initChannel: string, newChannel: string];
|
|
1465
|
+
[ManagerEventTypes.PlayerRestored]: [player: Player, node: Node];
|
|
1466
|
+
[ManagerEventTypes.PlayerStateUpdate]: [oldPlayer: Player, newPlayer: Player, changeType: PlayerStateUpdateEvent];
|
|
1459
1467
|
[ManagerEventTypes.QueueEnd]: [player: Player, track: Track, payload: TrackEndEvent];
|
|
1468
|
+
[ManagerEventTypes.RestoreComplete]: [node: Node];
|
|
1469
|
+
[ManagerEventTypes.SegmentSkipped]: [player: Player, track: Track, payload: SponsorBlockSegmentSkipped];
|
|
1470
|
+
[ManagerEventTypes.SegmentsLoaded]: [player: Player, track: Track, payload: SponsorBlockSegmentsLoaded];
|
|
1460
1471
|
[ManagerEventTypes.SocketClosed]: [player: Player, payload: WebSocketClosedEvent];
|
|
1461
|
-
[ManagerEventTypes.TrackStart]: [player: Player, track: Track, payload: TrackStartEvent];
|
|
1462
1472
|
[ManagerEventTypes.TrackEnd]: [player: Player, track: Track, payload: TrackEndEvent];
|
|
1463
|
-
[ManagerEventTypes.TrackStuck]: [player: Player, track: Track, payload: TrackStuckEvent];
|
|
1464
1473
|
[ManagerEventTypes.TrackError]: [player: Player, track: Track, payload: TrackExceptionEvent];
|
|
1465
|
-
[ManagerEventTypes.
|
|
1466
|
-
[ManagerEventTypes.
|
|
1467
|
-
[ManagerEventTypes.ChapterStarted]: [player: Player, track: Track, payload: SponsorBlockChapterStarted];
|
|
1468
|
-
[ManagerEventTypes.ChaptersLoaded]: [player: Player, track: Track, payload: SponsorBlockChaptersLoaded];
|
|
1474
|
+
[ManagerEventTypes.TrackStart]: [player: Player, track: Track, payload: TrackStartEvent];
|
|
1475
|
+
[ManagerEventTypes.TrackStuck]: [player: Player, track: Track, payload: TrackStuckEvent];
|
|
1469
1476
|
}
|
|
1470
1477
|
interface PlayerStore {
|
|
1471
1478
|
get(guildId: string): Promise<Player | undefined>;
|
|
@@ -551,6 +551,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
551
551
|
filterActions[filter](true);
|
|
552
552
|
}
|
|
553
553
|
}
|
|
554
|
+
this.emit(ManagerEventTypes.PlayerRestored, player, node);
|
|
554
555
|
await this.sleep(1000);
|
|
555
556
|
}
|
|
556
557
|
}
|
|
@@ -723,6 +724,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
723
724
|
// After processing, delete the Redis key
|
|
724
725
|
await this.redis.del(key);
|
|
725
726
|
this.emit(ManagerEventTypes.Debug, `[MANAGER] Deleted player state from Redis: ${key}`);
|
|
727
|
+
this.emit(ManagerEventTypes.PlayerRestored, player, node);
|
|
726
728
|
await this.sleep(1000);
|
|
727
729
|
}
|
|
728
730
|
}
|
|
@@ -741,6 +743,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
741
743
|
break;
|
|
742
744
|
}
|
|
743
745
|
this.emit(ManagerEventTypes.Debug, "[MANAGER] Finished loading saved players.");
|
|
746
|
+
this.emit(ManagerEventTypes.RestoreComplete, node);
|
|
744
747
|
}
|
|
745
748
|
/**
|
|
746
749
|
* Returns the node to use based on the configured `useNode` and `enablePriorityMode` options.
|
|
@@ -1171,28 +1174,30 @@ var PlayerStateEventTypes;
|
|
|
1171
1174
|
})(PlayerStateEventTypes || (exports.PlayerStateEventTypes = PlayerStateEventTypes = {}));
|
|
1172
1175
|
var ManagerEventTypes;
|
|
1173
1176
|
(function (ManagerEventTypes) {
|
|
1177
|
+
ManagerEventTypes["ChapterStarted"] = "chapterStarted";
|
|
1178
|
+
ManagerEventTypes["ChaptersLoaded"] = "chaptersLoaded";
|
|
1174
1179
|
ManagerEventTypes["Debug"] = "debug";
|
|
1180
|
+
ManagerEventTypes["NodeConnect"] = "nodeConnect";
|
|
1175
1181
|
ManagerEventTypes["NodeCreate"] = "nodeCreate";
|
|
1176
1182
|
ManagerEventTypes["NodeDestroy"] = "nodeDestroy";
|
|
1177
|
-
ManagerEventTypes["NodeConnect"] = "nodeConnect";
|
|
1178
|
-
ManagerEventTypes["NodeReconnect"] = "nodeReconnect";
|
|
1179
1183
|
ManagerEventTypes["NodeDisconnect"] = "nodeDisconnect";
|
|
1180
1184
|
ManagerEventTypes["NodeError"] = "nodeError";
|
|
1181
1185
|
ManagerEventTypes["NodeRaw"] = "nodeRaw";
|
|
1186
|
+
ManagerEventTypes["NodeReconnect"] = "nodeReconnect";
|
|
1182
1187
|
ManagerEventTypes["PlayerCreate"] = "playerCreate";
|
|
1183
1188
|
ManagerEventTypes["PlayerDestroy"] = "playerDestroy";
|
|
1184
|
-
ManagerEventTypes["PlayerStateUpdate"] = "playerStateUpdate";
|
|
1185
|
-
ManagerEventTypes["PlayerMove"] = "playerMove";
|
|
1186
1189
|
ManagerEventTypes["PlayerDisconnect"] = "playerDisconnect";
|
|
1190
|
+
ManagerEventTypes["PlayerMove"] = "playerMove";
|
|
1191
|
+
ManagerEventTypes["PlayerRestored"] = "playerRestored";
|
|
1192
|
+
ManagerEventTypes["PlayerStateUpdate"] = "playerStateUpdate";
|
|
1187
1193
|
ManagerEventTypes["QueueEnd"] = "queueEnd";
|
|
1194
|
+
ManagerEventTypes["RestoreComplete"] = "restoreComplete";
|
|
1195
|
+
ManagerEventTypes["SegmentSkipped"] = "segmentSkipped";
|
|
1196
|
+
ManagerEventTypes["SegmentsLoaded"] = "segmentsLoaded";
|
|
1188
1197
|
ManagerEventTypes["SocketClosed"] = "socketClosed";
|
|
1189
|
-
ManagerEventTypes["TrackStart"] = "trackStart";
|
|
1190
1198
|
ManagerEventTypes["TrackEnd"] = "trackEnd";
|
|
1191
|
-
ManagerEventTypes["TrackStuck"] = "trackStuck";
|
|
1192
1199
|
ManagerEventTypes["TrackError"] = "trackError";
|
|
1193
|
-
ManagerEventTypes["
|
|
1194
|
-
ManagerEventTypes["
|
|
1195
|
-
ManagerEventTypes["ChapterStarted"] = "chapterStarted";
|
|
1196
|
-
ManagerEventTypes["ChaptersLoaded"] = "chaptersLoaded";
|
|
1200
|
+
ManagerEventTypes["TrackStart"] = "trackStart";
|
|
1201
|
+
ManagerEventTypes["TrackStuck"] = "trackStuck";
|
|
1197
1202
|
})(ManagerEventTypes || (exports.ManagerEventTypes = ManagerEventTypes = {}));
|
|
1198
1203
|
// PlayerStore WILL BE REMOVED IF YOU DONT FIND A USE FOR IT.
|
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",
|