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 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
- SegmentsLoaded = "segmentsLoaded",
1438
- SegmentSkipped = "segmentSkipped",
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.SegmentsLoaded]: [player: Player, track: Track, payload: SponsorBlockSegmentsLoaded];
1466
- [ManagerEventTypes.SegmentSkipped]: [player: Player, track: Track, payload: SponsorBlockSegmentSkipped];
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["SegmentsLoaded"] = "segmentsLoaded";
1194
- ManagerEventTypes["SegmentSkipped"] = "segmentSkipped";
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.
@@ -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 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
- };
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 response = await axios_1.default.get("https://open.spotify.com/get_access_token", { params });
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, error.response?.data || error.message);
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, error.response?.data || error.message);
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.26",
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",