magmastream 2.6.0-beta.7 → 2.6.0

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/README.md CHANGED
@@ -44,6 +44,7 @@ Also you can join the [Discord Support Server](https://discord.com/invite/HV59Z3
44
44
  | [Lunio](https://discord.com/api/oauth2/authorize?client_id=945030475779551415&permissions=61991952&scope=bot+applications.commands) | vexi |
45
45
  | [JukeDisc](https://discord.com/oauth2/authorize?client_id=1109751797549105176&permissions=968552214080&scope=bot+applications.commands) | Theo |
46
46
  | [Cool Music](https://discord.com/oauth2/authorize?client_id=923529398425096193&permissions=12888394808&redirect_uri=https%3A%2F%2Fdiscord.gg%2Fcool-music-support-925619107460698202&response_type=code&scope=bot%20identify%20applications.commands) | Itz Random |
47
+ | [Soundy](https://dsc.gg/sndy) | iaMJ |
47
48
 
48
49
  If you want to add your own bot create a pull request with your bot added. Please add your full name.
49
50
 
package/dist/index.d.ts CHANGED
@@ -527,7 +527,6 @@ declare class Node {
527
527
  extractSpotifyTrackID(url: string): string | null;
528
528
  extractSpotifyArtistID(url: string): string | null;
529
529
  private handleAutoplay;
530
- private handleSpotifyAutoplay;
531
530
  private handleFailedTrack;
532
531
  private handleRepeatedTrack;
533
532
  private playNextTrack;
@@ -997,6 +996,11 @@ interface ManagerOptions {
997
996
  defaultSearchPlatform?: SearchPlatform;
998
997
  /** Whether the YouTube video titles should be replaced if the Author does not exactly match. */
999
998
  replaceYouTubeCredentials?: boolean;
999
+ /** The last.fm API key.
1000
+ * If you need to create one go here: https://www.last.fm/api/account/create.
1001
+ * If you already have one, get it from here: https://www.last.fm/api/accounts.
1002
+ */
1003
+ lastFmApiKey: string;
1000
1004
  /**
1001
1005
  * Function to send data to the websocket.
1002
1006
  * @param id
@@ -146,7 +146,7 @@ class Manager extends events_1.EventEmitter {
146
146
  if (state.dynamicRepeat) {
147
147
  player.setDynamicRepeat(state.dynamicRepeat, state.dynamicLoopInterval._idleTimeout);
148
148
  }
149
- if (state.isAutoplay) {
149
+ if (state.isAutoplay && state?.data?.Internal_BotUser) {
150
150
  player.setAutoplay(state.isAutoplay, state.data.Internal_BotUser);
151
151
  }
152
152
  }
@@ -8,6 +8,7 @@ const nodeCheck_1 = tslib_1.__importDefault(require("../utils/nodeCheck"));
8
8
  const ws_1 = tslib_1.__importDefault(require("ws"));
9
9
  const fs_1 = tslib_1.__importDefault(require("fs"));
10
10
  const path_1 = tslib_1.__importDefault(require("path"));
11
+ const axios_1 = tslib_1.__importDefault(require("axios"));
11
12
  exports.validSponsorBlocks = ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "filler"];
12
13
  const sessionIdsFilePath = path_1.default.join(process.cwd(), "node_modules", "magmastream", "dist", "sessionData", "sessionIds.json");
13
14
  let sessionIdsMap = new Map();
@@ -367,72 +368,102 @@ class Node {
367
368
  return match ? match[1] : null;
368
369
  }
369
370
  // Handle autoplay
370
- async handleAutoplay(player, track) {
371
+ async handleAutoplay(player, track, attempts = 0) {
372
+ if (!player.isAutoplay || attempts > 3 || !player.queue.previous)
373
+ return false;
371
374
  const previousTrack = player.queue.previous;
372
- if (!player.isAutoplay || !previousTrack)
373
- return;
374
- const hasSpotifyURL = ["spotify.com", "open.spotify.com"].some((url) => previousTrack.uri.includes(url));
375
- if (hasSpotifyURL) {
376
- const spotifySuccess = await this.handleSpotifyAutoplay(player); // Check if Spotify autoplay was successful
377
- if (spotifySuccess)
378
- return; // If successful, exit the function
375
+ const apiKey = this.manager.options.lastFmApiKey;
376
+ // If Last.fm API is not available and YouTube is not supported
377
+ if (!apiKey && !this.info.sourceManagers.includes("youtube"))
378
+ return false;
379
+ // Handle YouTube autoplay logic
380
+ if ((!apiKey && this.info.sourceManagers.includes("youtube")) || (attempts > 2 && this.info.sourceManagers.includes("youtube"))) {
381
+ const hasYouTubeURL = ["youtube.com", "youtu.be"].some((url) => previousTrack.uri.includes(url));
382
+ const videoID = hasYouTubeURL
383
+ ? previousTrack.uri.split("=").pop()
384
+ : (await player.search(`${previousTrack.author} - ${previousTrack.title}`, player.get("Internal_BotUser"))).tracks[0]?.uri.split("=").pop();
385
+ if (!videoID)
386
+ return false;
387
+ let randomIndex;
388
+ let searchURI;
389
+ do {
390
+ randomIndex = Math.floor(Math.random() * 23) + 2;
391
+ searchURI = `https://www.youtube.com/watch?v=${videoID}&list=RD${videoID}&index=${randomIndex}`;
392
+ } while (track.uri.includes(searchURI));
393
+ const res = await player.search(searchURI, player.get("Internal_BotUser"));
394
+ if (res.loadType === "empty" || res.loadType === "error")
395
+ return false;
396
+ const foundTrack = res.tracks.find((t) => t.uri !== track.uri && t.author !== track.author && t.title !== track.title);
397
+ if (!foundTrack)
398
+ return false;
399
+ player.queue.add(foundTrack);
400
+ player.play();
401
+ return true;
379
402
  }
380
- const hasYouTubeURL = ["youtube.com", "youtu.be"].some((url) => previousTrack.uri.includes(url));
381
- let videoID = previousTrack.uri.substring(previousTrack.uri.indexOf("=") + 1);
382
- if (!hasYouTubeURL) {
383
- const res = await player.search(`${previousTrack.author} - ${previousTrack.title}`, player.get("Internal_BotUser"));
384
- videoID = res.tracks[0].uri.substring(res.tracks[0].uri.indexOf("=") + 1);
403
+ // Handle Last.fm-based autoplay logic
404
+ let { author: artist } = previousTrack;
405
+ const { title, uri } = previousTrack;
406
+ const enabledSources = this.info.sourceManagers;
407
+ const isSpotifyEnabled = enabledSources.includes("spotify");
408
+ const isSpotifyUri = uri.includes("spotify.com");
409
+ let selectedSource = null;
410
+ if (isSpotifyEnabled && isSpotifyUri) {
411
+ selectedSource = "spotify";
385
412
  }
386
- let randomIndex;
387
- let searchURI;
388
- do {
389
- randomIndex = Math.floor(Math.random() * 23) + 2;
390
- searchURI = `https://www.youtube.com/watch?v=${videoID}&list=RD${videoID}&index=${randomIndex}`;
391
- } while (track.uri.includes(searchURI));
392
- const res = await player.search(searchURI, player.get("Internal_BotUser"));
393
- if (res.loadType === "empty" || res.loadType === "error")
394
- return;
395
- let tracks = res.tracks;
396
- if (res.loadType === "playlist")
397
- tracks = res.playlist.tracks;
398
- const foundTrack = tracks.sort(() => Math.random() - 0.5).find((shuffledTrack) => shuffledTrack.uri !== track.uri);
399
- if (!foundTrack)
400
- return;
401
- if (this.manager.options.replaceYouTubeCredentials) {
402
- foundTrack.author = foundTrack.author.replace("- Topic", "");
403
- foundTrack.title = foundTrack.title.replace("Topic -", "");
404
- if (foundTrack.title.includes("-")) {
405
- const [author, title] = foundTrack.title.split("-").map((str) => str.trim());
406
- foundTrack.author = author;
407
- foundTrack.title = title;
413
+ else {
414
+ selectedSource = this.manager.options.defaultSearchPlatform;
415
+ }
416
+ if (!artist || !title) {
417
+ if (!title) {
418
+ const noTitleUrl = `https://ws.audioscrobbler.com/2.0/?method=artist.getTopTracks&artist=${artist}&autocorrect=1&api_key=${apiKey}&format=json`;
419
+ const response = await axios_1.default.get(noTitleUrl);
420
+ if (response.data.error || !response.data.toptracks?.track?.length)
421
+ return false;
422
+ const randomTrack = response.data.toptracks.track[Math.floor(Math.random() * response.data.toptracks.track.length)];
423
+ const res = await player.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: selectedSource }, player.get("Internal_BotUser"));
424
+ if (res.loadType === "empty" || res.loadType === "error")
425
+ return false;
426
+ const foundTrack = res.tracks.find((t) => t.uri !== track.uri);
427
+ if (!foundTrack)
428
+ return false;
429
+ player.queue.add(foundTrack);
430
+ player.play();
431
+ return true;
432
+ }
433
+ else if (!artist) {
434
+ const noArtistUrl = `https://ws.audioscrobbler.com/2.0/?method=track.search&track=${title}&api_key=${apiKey}&format=json`;
435
+ const response = await axios_1.default.get(noArtistUrl);
436
+ artist = response.data.results.trackmatches?.track?.[0]?.artist;
437
+ if (!artist)
438
+ return false;
408
439
  }
409
440
  }
410
- player.queue.add(foundTrack);
411
- player.play();
412
- }
413
- async handleSpotifyAutoplay(player) {
414
- const previousTrack = player.queue.previous;
415
- const node = this.manager.useableNodes;
416
- const isSpotifyPluginEnabled = node.info.plugins.some((plugin) => plugin.name === "lavasrc-plugin");
417
- const isSpotifySourceManagerEnabled = node.info.sourceManagers.includes("spotify");
418
- if (!isSpotifySourceManagerEnabled || !isSpotifyPluginEnabled)
419
- return false;
420
- const trackID = this.extractSpotifyTrackID(previousTrack.uri);
421
- const artistID = this.extractSpotifyArtistID(previousTrack.pluginInfo.artistUrl);
422
- let identifier = [trackID && `seed_tracks=${trackID}`, artistID && `seed_artists=${artistID}`].filter(Boolean).join("&");
423
- if (identifier) {
424
- identifier = `sprec:${identifier}`;
441
+ const url = `https://ws.audioscrobbler.com/2.0/?method=track.getSimilar&artist=${artist}&track=${title}&limit=10&autocorrect=1&api_key=${apiKey}&format=json`;
442
+ const response = await axios_1.default.get(url);
443
+ if (response.data.error || !response.data.similartracks?.track?.length) {
444
+ const retryUrl = `https://ws.audioscrobbler.com/2.0/?method=artist.getTopTracks&artist=${artist}&autocorrect=1&api_key=${apiKey}&format=json`;
445
+ const retryResponse = await axios_1.default.get(retryUrl);
446
+ if (retryResponse.data.error || !retryResponse.data.toptracks?.track?.length)
447
+ return false;
448
+ const randomTrack = retryResponse.data.toptracks.track[Math.floor(Math.random() * retryResponse.data.toptracks.track.length)];
449
+ const res = await player.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: selectedSource }, player.get("Internal_BotUser"));
450
+ if (res.loadType === "empty" || res.loadType === "error")
451
+ return false;
452
+ const foundTrack = res.tracks.find((t) => t.uri !== track.uri);
453
+ if (!foundTrack)
454
+ return false;
455
+ player.queue.add(foundTrack);
456
+ player.play();
457
+ return true;
425
458
  }
426
- if (!identifier)
427
- return false;
428
- const recommendedResult = (await node.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`));
429
- if (recommendedResult.loadType !== "playlist")
459
+ const randomTrack = response.data.similartracks.track[Math.floor(Math.random() * response.data.similartracks.track.length)];
460
+ const res = await player.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: selectedSource }, player.get("Internal_BotUser"));
461
+ if (res.loadType === "empty" || res.loadType === "error")
430
462
  return false;
431
- const playlistData = recommendedResult.data;
432
- const recommendedTrack = playlistData.tracks[0];
433
- if (!recommendedTrack)
463
+ const foundTrack = res.tracks.find((t) => t.uri !== track.uri);
464
+ if (!foundTrack)
434
465
  return false;
435
- player.queue.add(Utils_1.TrackUtils.build(recommendedTrack, player.get("Internal_BotUser")));
466
+ player.queue.add(foundTrack);
436
467
  player.play();
437
468
  return true;
438
469
  }
@@ -480,13 +511,22 @@ class Node {
480
511
  player.queue.previous = player.queue.current;
481
512
  player.queue.current = null;
482
513
  if (!player.isAutoplay) {
483
- player.queue.previous = player.queue.current;
484
- player.queue.current = null;
485
514
  player.playing = false;
486
515
  this.manager.emit("queueEnd", player, track, payload);
487
516
  return;
488
517
  }
489
- await this.handleAutoplay(player, track);
518
+ let attempts = 1;
519
+ let success = false;
520
+ while (attempts <= 3) {
521
+ success = await this.handleAutoplay(player, track, attempts);
522
+ if (success)
523
+ return;
524
+ attempts++;
525
+ }
526
+ // If all attempts fail, reset the player state and emit queueEnd
527
+ player.queue.previous = null;
528
+ player.playing = false;
529
+ this.manager.emit("queueEnd", player, track, payload);
490
530
  }
491
531
  trackStuck(player, track, payload) {
492
532
  player.stop();
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  function managerCheck(options) {
4
4
  if (!options)
5
5
  throw new TypeError("ManagerOptions must not be empty.");
6
- const { autoPlay, clientId, clientName, defaultSearchPlatform, nodes, plugins, send, trackPartial, usePriority, useNode, replaceYouTubeCredentials } = options;
6
+ const { autoPlay, clientId, clientName, defaultSearchPlatform, nodes, plugins, send, trackPartial, usePriority, useNode, replaceYouTubeCredentials, lastFmApiKey, } = options;
7
7
  if (typeof autoPlay !== "undefined" && typeof autoPlay !== "boolean") {
8
8
  throw new TypeError('Manager option "autoPlay" must be a boolean.');
9
9
  }
@@ -49,5 +49,8 @@ function managerCheck(options) {
49
49
  if (typeof replaceYouTubeCredentials !== "undefined" && typeof replaceYouTubeCredentials !== "boolean") {
50
50
  throw new TypeError('Manager option "replaceYouTubeCredentials" must be a boolean.');
51
51
  }
52
+ if (typeof lastFmApiKey !== "undefined" && (typeof lastFmApiKey !== "string" || lastFmApiKey.trim().length === 0)) {
53
+ throw new TypeError('Manager option "lastFmApiKey" must be a string.');
54
+ }
52
55
  }
53
56
  exports.default = managerCheck;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magmastream",
3
- "version": "2.6.0-beta.7",
3
+ "version": "2.6.0",
4
4
  "description": "A user-friendly Lavalink client designed for NodeJS.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -15,9 +15,9 @@
15
15
  },
16
16
  "devDependencies": {
17
17
  "@favware/rollup-type-bundler": "^3.3.0",
18
- "@types/lodash": "^4.17.9",
19
- "@types/node": "^20.16.10",
20
- "@types/ws": "^8.5.12",
18
+ "@types/lodash": "^4.17.13",
19
+ "@types/node": "^20.17.9",
20
+ "@types/ws": "^8.5.13",
21
21
  "@typescript-eslint/eslint-plugin": "^7.18.0",
22
22
  "@typescript-eslint/parser": "^7.18.0",
23
23
  "eslint": "^8.57.1",
@@ -28,10 +28,10 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@discordjs/collection": "^2.1.1",
31
- "axios": "^1.7.7",
31
+ "axios": "^1.7.9",
32
32
  "events": "^3.3.0",
33
33
  "lodash": "^4.17.21",
34
- "tslib": "^2.7.0",
34
+ "tslib": "^2.8.1",
35
35
  "ws": "^8.18.0"
36
36
  },
37
37
  "peerDependencies": {