Skip to content

Commit

Permalink
2.7.3
Browse files Browse the repository at this point in the history
- Added autoPlaySearchPlatform as managerOption
- Moved State type to enum StateTypes
- Moved TrackEndReason type to enum TrackEndReasonTypes
- Moved Severity type to enum SeverityTypes
  • Loading branch information
Vexify4103 authored and SxMAbel committed Jan 20, 2025
1 parent 052a6f1 commit 74680a9
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 52 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "magmastream",
"version": "2.7.2",
"version": "2.7.3",
"description": "A user-friendly Lavalink client designed for NodeJS.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
34 changes: 22 additions & 12 deletions src/structures/Manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import {
LoadTypes,
Plugin,
StateTypes,
Structure,
TrackData,
TrackEndEvent,
Expand Down Expand Up @@ -215,7 +216,7 @@ export class Manager extends EventEmitter {
const player = this.players.get(guildId);

// If the player does not exist or is disconnected, or the voice channel is not specified, do not save the player state
if (!player || player.state === "DISCONNECTED" || !player.voiceChannel) {
if (!player || player.state === StateTypes.Disconnected || !player.voiceChannel) {
// Clean up any inactive players
return this.cleanupInactivePlayers();
}
Expand Down Expand Up @@ -367,7 +368,7 @@ export class Manager extends EventEmitter {
}

// If no node has a cumulative weight greater than or equal to the random number, return the node with the lowest load
return this.options.useNode === "leastLoad" ? this.leastLoadNode.first() : this.leastPlayersNode.first();
return this.options.useNode === UseNodeOptions.LeastLoad ? this.leastLoadNode.first() : this.leastPlayersNode.first();
}

/**
Expand All @@ -378,7 +379,11 @@ export class Manager extends EventEmitter {
* @returns {Node} The node to use.
*/
public get useableNodes(): Node {
return this.options.usePriority ? this.priorityNode : this.options.useNode === "leastLoad" ? this.leastLoadNode.first() : this.leastPlayersNode.first();
return this.options.usePriority
? this.priorityNode
: this.options.useNode === UseNodeOptions.LeastLoad
? this.leastLoadNode.first()
: this.leastPlayersNode.first();
}

private lastSaveTimes: Map<string, number> = new Map();
Expand Down Expand Up @@ -460,6 +465,7 @@ export class Manager extends EventEmitter {
* @param options.plugins - An array of plugins to load.
* @param options.nodes - An array of node options to create nodes from.
* @param options.autoPlay - Whether to automatically play the first track in the queue when the player is created.
* @param options.autoPlaySearchPlatform - The search platform autoplay will use. Failback to Youtube if not found.
* @param options.usePriority - Whether to use the priority when selecting a node to play on.
* @param options.clientName - The name of the client to send to Lavalink.
* @param options.defaultSearchPlatform - The default search platform to use when searching for tracks.
Expand Down Expand Up @@ -498,7 +504,8 @@ export class Manager extends EventEmitter {
usePriority: false,
clientName: "Magmastream",
defaultSearchPlatform: SearchPlatform.YouTube,
useNode: "leastPlayers",
autoPlaySearchPlatform: SearchPlatform.YouTube,
useNode: UseNodeOptions.LeastPlayers,
...options,
};

Expand Down Expand Up @@ -911,7 +918,7 @@ export interface ManagerOptions {
/** Use priority mode over least amount of player or load? */
usePriority?: boolean;
/** Use the least amount of players or least load? */
useNode?: "leastLoad" | "leastPlayers";
useNode?: UseNodeOptions.LeastLoad | UseNodeOptions.LeastPlayers;
/** The array of nodes to connect to. */
nodes?: NodeOptions[];
/** The client ID to use. */
Expand All @@ -922,16 +929,19 @@ export interface ManagerOptions {
plugins?: Plugin[];
/** Whether players should automatically play the next song. */
autoPlay?: boolean;
/** The search platform autoplay should use. Failback to Youtube if not found.
* Use enum `SearchPlatform`. */
autoPlaySearchPlatform?: SearchPlatform;
/** An array of track properties to keep. `track` will always be present. */
trackPartial?: string[];
/** The default search platform to use. Use enum `SearchPlatform`. */
/** The default search platform to use.
* Use enum `SearchPlatform`. */
defaultSearchPlatform?: SearchPlatform;
/** Whether the YouTube video titles should be replaced if the Author does not exactly match. */
replaceYouTubeCredentials?: boolean;
/** The last.fm API key.
* If you need to create one go here: https://www.last.fm/api/account/create.
* If you already have one, get it from here: https://www.last.fm/api/accounts.
*/
* If you already have one, get it from here: https://www.last.fm/api/accounts. */
lastFmApiKey: string;
/**
* Function to send data to the websocket.
Expand All @@ -941,10 +951,10 @@ export interface ManagerOptions {
send(id: string, payload: Payload): void;
}

export const UseNodeOptions = {
leastLoad: "leastLoad",
leastPlayers: "leastPlayers",
} as const;
export enum UseNodeOptions {
LeastLoad = "leastLoad",
LeastPlayers = "leastPlayers",
}

export type UseNodeOption = keyof typeof UseNodeOptions;

Expand Down
67 changes: 52 additions & 15 deletions src/structures/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
SponsorBlockSegmentsLoaded,
SponsorBlockSegmentSkipped,
LoadTypes,
TrackEndReasonTypes,
} from "./Utils";
import { Manager, ManagerEventTypes, PlayerStateEventTypes, SearchPlatform } from "./Manager";
import { Player, Track, UnresolvedTrack } from "./Player";
Expand Down Expand Up @@ -547,7 +548,7 @@ export class Node {
this.handleFailedTrack(player, track, payload);
}
// If the track was forcibly replaced
else if (reason === "replaced") {
else if (reason === TrackEndReasonTypes.Replaced) {
this.manager.emit(ManagerEventTypes.TrackEnd, player, track, payload);
player.queue.previous = player.queue.current;
}
Expand Down Expand Up @@ -608,14 +609,15 @@ export class Node {

const previousTrack = player.queue.previous;
const apiKey = this.manager.options.lastFmApiKey;
const enabledSources = this.info.sourceManagers;

// If Last.fm API is not available and YouTube is not supported
if (!apiKey && !this.info.sourceManagers.includes("youtube")) return false;
if (!apiKey && !enabledSources.includes("youtube")) return false;

// Handle YouTube autoplay logic
if (
(!apiKey && this.info.sourceManagers.includes("youtube")) ||
(attempt === player.autoplayTries - 1 && !(apiKey && player.autoplayTries === 1) && this.info.sourceManagers.includes("youtube"))
(!apiKey && enabledSources.includes("youtube")) ||
(attempt === player.autoplayTries - 1 && !(apiKey && player.autoplayTries === 1) && enabledSources.includes("youtube"))
) {
const hasYouTubeURL = ["youtube.com", "youtu.be"].some((url) => previousTrack.uri.includes(url));
const videoID = hasYouTubeURL
Expand Down Expand Up @@ -644,21 +646,55 @@ export class Node {

// Handle Last.fm-based autoplay logic
let { author: artist } = previousTrack;
const { title, uri } = previousTrack;

const enabledSources = this.info.sourceManagers;

const isSpotifyEnabled = enabledSources.includes("spotify");
const isSpotifyUri = uri.includes("spotify.com");
const { title } = previousTrack;

// Create a mapping of enum values to their string representations
const platformMapping: { [key in SearchPlatform]: string } = {
[SearchPlatform.AppleMusic]: "applemusic",
[SearchPlatform.Bandcamp]: "bandcamp",
[SearchPlatform.Deezer]: "deezer",
[SearchPlatform.Jiosaavn]: "jiosaavn",
[SearchPlatform.SoundCloud]: "soundcloud",
[SearchPlatform.Spotify]: "spotify",
[SearchPlatform.Tidal]: "tidal",
[SearchPlatform.YouTube]: "youtube",
[SearchPlatform.YouTubeMusic]: "youtube",
};

let selectedSource: SearchPlatform | null = null;
// Get the autoPlaySearchPlatform and available sources
const { autoPlaySearchPlatform } = this.manager.options;

if (isSpotifyEnabled && isSpotifyUri) {
selectedSource = SearchPlatform.Spotify;
if (enabledSources.includes(platformMapping[autoPlaySearchPlatform])) {
selectedSource = autoPlaySearchPlatform;
} else {
selectedSource = this.manager.options.defaultSearchPlatform;
// Fallback to SearchPlatform.YouTube
const fallbackPlatform = SearchPlatform.YouTube;

if (enabledSources.includes(platformMapping[fallbackPlatform])) {
selectedSource = fallbackPlatform;
} else {
// Check for other platforms in the specified order
const alternativePlatforms = [
SearchPlatform.Deezer, // 1
SearchPlatform.SoundCloud, // 2
SearchPlatform.AppleMusic, // 2
SearchPlatform.Bandcamp, // 3
SearchPlatform.Jiosaavn, // 4
SearchPlatform.Tidal, // 5
];

for (const platform of alternativePlatforms) {
if (enabledSources.includes(platformMapping[platform])) {
selectedSource = platform;
break; // Exit the loop once a valid platform is found
}
}
}
}

if (!selectedSource) return false;

if (!artist || !title) {
if (!title) {
const noTitleUrl = `https://ws.audioscrobbler.com/2.0/?method=artist.getTopTracks&artist=${artist}&autocorrect=1&api_key=${apiKey}&format=json`;
Expand All @@ -679,7 +715,8 @@ export class Node {
player.queue.add(foundTrack);
player.play();
return true;
} else if (!artist) {
}
if (!artist) {
const noArtistUrl = `https://ws.audioscrobbler.com/2.0/?method=track.search&track=${title}&api_key=${apiKey}&format=json`;
const response = await axios.get(noArtistUrl);
artist = response.data.results.trackmatches?.track?.[0]?.artist;
Expand Down Expand Up @@ -774,7 +811,7 @@ export class Node {
this.manager.emit(ManagerEventTypes.TrackEnd, player, track, payload);

// If the track was stopped manually and there are no more tracks in the queue, end the queue
if (payload.reason === "stopped" && !(queue.current = queue.shift())) {
if (payload.reason === TrackEndReasonTypes.Stopped && !(queue.current = queue.shift())) {
this.queueEnd(player, track, payload);
return;
}
Expand Down
19 changes: 9 additions & 10 deletions src/structures/Player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Filters } from "./Filters";
import { LavalinkResponse, Manager, PlaylistRawData, SearchQuery, SearchResult, PlayerStateEventTypes, ManagerEventTypes } from "./Manager";
import { LavalinkInfo, Node, SponsorBlockSegment } from "./Node";
import { Queue } from "./Queue";
import { LoadTypes, Sizes, State, Structure, TrackSourceName, TrackUtils, VoiceState } from "./Utils";
import { LoadTypes, Sizes, StateTypes, Structure, TrackSourceName, TrackUtils, VoiceState } from "./Utils";
import * as _ from "lodash";
import playerCheck from "../utils/playerCheck";
import { ClientUser, Message, User } from "discord.js";
Expand Down Expand Up @@ -37,7 +37,7 @@ export class Player {
/**The now playing message. */
public nowPlayingMessage?: Message;
/** The current state of the player. */
public state: State = "DISCONNECTED";
public state: StateTypes = StateTypes.Disconnected;
/** The equalizer bands array. */
public bands = new Array<number>(15).fill(0.0);
/** The voice state object from Discord. */
Expand Down Expand Up @@ -153,7 +153,7 @@ export class Player {
public connect(): this {
if (!this.voiceChannel) throw new RangeError("No voice channel has been set.");

this.state = "CONNECTING";
this.state = StateTypes.Connecting;

const oldPlayer = this ? { ...this } : null;

Expand All @@ -169,14 +169,14 @@ export class Player {
});

// Set the player state to connected
this.state = "CONNECTED";
this.state = StateTypes.Connected;

// Emit the player state update event
this.manager.emit(ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
changeType: PlayerStateEventTypes.ConnectionChange,
details: {
changeType: "connect",
previousConnection: oldPlayer?.state === "CONNECTED",
previousConnection: oldPlayer?.state === StateTypes.Connected,
currentConnection: true,
},
});
Expand All @@ -192,7 +192,7 @@ export class Player {
public disconnect(): this {
if (this.voiceChannel === null) return this;

this.state = "DISCONNECTING";
this.state = StateTypes.Disconnecting;

const oldPlayer = this ? { ...this } : null;
this.pause(true);
Expand All @@ -207,13 +207,13 @@ export class Player {
});

this.voiceChannel = null;
this.state = "DISCONNECTED";
this.state = StateTypes.Disconnected;

this.manager.emit(ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
changeType: PlayerStateEventTypes.ConnectionChange,
details: {
changeType: "disconnect",
previousConnection: oldPlayer.state === "CONNECTED",
previousConnection: oldPlayer.state === StateTypes.Connected,
currentConnection: false,
},
});
Expand All @@ -230,9 +230,8 @@ export class Player {
* @emits {playerStateUpdate} - The old and new player states after the destruction.
*/
public destroy(disconnect: boolean = true): void {

const oldPlayer = this ? { ...this } : null;
this.state = "DESTROYING";
this.state = StateTypes.Destroying;

if (disconnect) {
this.disconnect();
Expand Down
Loading

0 comments on commit 74680a9

Please sign in to comment.