synthra-client
Version:
A user-friendly Lavalink client designed for NodeJS.
466 lines (465 loc) • 19.9 kB
TypeScript
import { LoadTypes, SponsorBlockChaptersLoaded, SponsorBlockChapterStarted, SponsorBlockSegmentSkipped, SponsorBlockSegmentsLoaded, TrackData, TrackEndEvent, TrackExceptionEvent, TrackStartEvent, TrackStuckEvent, VoicePacket, VoiceServer, WebSocketClosedEvent } from "./Utils";
import { Collection } from "@discordjs/collection";
import { EventEmitter } from "events";
import { Node, NodeOptions } from "./Node";
import { Player, PlayerOptions, Track } from "./Player";
import { VoiceState, Plugin } from "..";
import { ClientUser, User } from "discord.js";
/**
* The main hub for interacting with Lavalink and using Magmastream,
*/
export declare class Synthra extends EventEmitter {
/** The map of players. */
readonly players: Collection<string, Player>;
/** The map of nodes. */
readonly nodes: Collection<string, Node>;
/** The options that were set. */
readonly options: ManagerOptions;
initiated: boolean;
/**
* Initiates the Manager class.
* @param options
* @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. Fallback 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.
* @param options.useNode - The strategy to use when selecting a node to play on.
* @param options.trackPartial - The partial track search results to use when searching for tracks. This partials will always be presented on each track.
* @param options.eventBatchDuration - The duration to wait before processing the collected player state events.
* @param options.eventBatchInterval - The interval to wait before processing the collected player state events.
*/
constructor(options: ManagerOptions);
/**
* Initiates the Manager.
* @param clientId - The Discord client ID (required).
* @param clusterId - The cluster ID which runs the current process (required).
* @returns The manager instance.
*/
init(clientId: string, clusterId?: number): this;
/**
* Searches the enabled sources based off the URL or the `source` property.
* @param query
* @param requester
* @returns The search result.
*/
search<T extends User | ClientUser = User | ClientUser>(query: string | SearchQuery, requester?: T): Promise<SearchResult>;
/**
* Creates a player or returns one if it already exists.
* @param options The options to create the player with.
* @returns The created player.
*/
create(options: PlayerOptions): Player;
/**
* Returns a player or undefined if it does not exist.
* @param guildId The guild ID of the player to retrieve.
* @returns The player if it exists, undefined otherwise.
*/
get(guildId: string): Player | undefined;
/**
* Destroys a player.
* @param guildId The guild ID of the player to destroy.
* @returns A promise that resolves when the player has been destroyed.
*/
destroy(guildId: string): Promise<void>;
/**
* Creates a new node or returns an existing one if it already exists.
* @param options - The options to create the node with.
* @returns The created node.
*/
createNode(options: NodeOptions): Node;
/**
* Destroys a node if it exists. Emits a debug event if the node is found and destroyed.
* @param identifier - The identifier of the node to destroy.
* @returns {void}
* @emits {debug} - Emits a debug message indicating the node is being destroyed.
*/
destroyNode(identifier: string): Promise<void>;
/**
* Attaches an event listener to the manager.
* @param event The event to listen for.
* @param listener The function to call when the event is emitted.
* @returns The manager instance for chaining.
*/
on<T extends keyof ManagerEvents>(event: T, listener: (...args: ManagerEvents[T]) => void): this;
/**
* Updates the voice state of a player based on the provided data.
* @param data - The data containing voice state information, which can be a VoicePacket, VoiceServer, or VoiceState.
* @returns A promise that resolves when the voice state update is handled.
* @emits {debug} - Emits a debug message indicating the voice state is being updated.
*/
updateVoiceState(data: VoicePacket | VoiceServer | VoiceState): Promise<void>;
/**
* Decodes an array of base64 encoded tracks and returns an array of TrackData.
* Emits a debug event with the tracks being decoded.
* @param tracks - An array of base64 encoded track strings.
* @returns A promise that resolves to an array of TrackData objects.
* @throws Will throw an error if no nodes are available or if the API request fails.
*/
decodeTracks(tracks: string[]): Promise<TrackData[]>;
/**
* Decodes a base64 encoded track and returns a TrackData.
* @param track - The base64 encoded track string.
* @returns A promise that resolves to a TrackData object.
* @throws Will throw an error if no nodes are available or if the API request fails.
*/
decodeTrack(track: string): Promise<TrackData>;
/**
* Saves player states to the JSON file.
* @param {string} guildId - The guild ID of the player to save
*/
savePlayerState(guildId: string): Promise<void>;
/**
* Loads player states from the JSON file.
* @param nodeId The ID of the node to load player states from.
* @returns A promise that resolves when the player states have been loaded.
*/
loadPlayerStates(nodeId: string): Promise<void>;
/**
* Returns the node to use based on the configured `useNode` and `usePriority` options.
* If `usePriority` is true, the node is chosen based on priority, otherwise it is chosen based on the `useNode` option.
* If `useNode` is "leastLoad", the node with the lowest load is chosen, if it is "leastPlayers", the node with the fewest players is chosen.
* If `usePriority` is false and `useNode` is not set, the node with the lowest load is chosen.
* @returns {Node} The node to use.
*/
get useableNode(): Node;
/**
* Handles the shutdown of the process by saving all active players' states and optionally cleaning up inactive players.
* This function is called when the process is about to exit.
* It iterates through all players and calls {@link savePlayerState} to save their states.
* Optionally, it also calls {@link cleanupInactivePlayers} to remove any stale player state files.
* After saving and cleaning up, it exits the process.
*/
handleShutdown(): Promise<void>;
/**
* Checks if the given data is a voice update.
* @param data The data to check.
* @returns Whether the data is a voice update.
*/
private isVoiceUpdate;
/**
* Determines if the provided update is a valid voice update.
* A valid update must contain either a token or a session_id.
*
* @param update - The voice update data to validate, which can be a VoicePacket, VoiceServer, or VoiceState.
* @returns {boolean} - True if the update is valid, otherwise false.
*/
private isValidUpdate;
/**
* Handles a voice server update by updating the player's voice state and sending the voice state to the Lavalink node.
* @param player The player for which the voice state is being updated.
* @param update The voice server data received from Discord.
* @returns A promise that resolves when the voice state update is handled.
* @emits {debug} - Emits a debug message indicating the voice state is being updated.
*/
private handleVoiceServerUpdate;
/**
* Handles a voice state update by updating the player's voice channel and session ID if provided, or by disconnecting and destroying the player if the channel ID is null.
* @param player The player for which the voice state is being updated.
* @param update The voice state data received from Discord.
* @emits {playerMove} - Emits a player move event if the channel ID is provided and the player is currently connected to a different voice channel.
* @emits {playerDisconnect} - Emits a player disconnect event if the channel ID is null.
*/
private handleVoiceStateUpdate;
/**
* Gets each player's JSON file
* @param {string} guildId - The guild ID
* @returns {string} The path to the player's JSON file
*/
private getPlayerFilePath;
/**
* Serializes a Player instance to avoid circular references.
* @param player The Player instance to serialize
* @returns The serialized Player instance
*/
private serializePlayer;
/**
* Checks for players that are no longer active and deletes their saved state files.
* This is done to prevent stale state files from accumulating on the file system.
*/
private cleanupInactivePlayers;
/**
* Returns the nodes that has the least load.
* The load is calculated by dividing the lavalink load by the number of cores.
* The result is multiplied by 100 to get a percentage.
* @returns {Collection<string, Node>}
*/
private get leastLoadNode();
/**
* Returns the nodes that have the least amount of players.
* Filters out disconnected nodes and sorts the remaining nodes
* by the number of players in ascending order.
* @returns {Collection<string, Node>} A collection of nodes sorted by player count.
*/
private get leastPlayersNode();
/**
* Returns a node based on priority.
* The nodes are sorted by priority in descending order, and then a random number
* between 0 and 1 is generated. The node that has a cumulative weight greater than or equal to the
* random number is returned.
* If no node has a cumulative weight greater than or equal to the random number, the node with the
* lowest load is returned.
* @returns {Node} The node to use.
*/
private get priorityNode();
}
export interface Payload {
/** The OP code */
op: number;
d: {
guild_id: string;
channel_id: string | null;
self_mute: boolean;
self_deaf: boolean;
};
}
export interface ManagerOptions {
/** Whether players should automatically play the next song. */
autoPlay?: boolean;
/** The search platform autoplay should use. Fallback to YouTube if not found.
* Use enum `SearchPlatform`. */
autoPlaySearchPlatform?: SearchPlatform;
/** The client ID to use. */
clientId?: string;
/** Value to use for the `Client-Name` header. */
clientName?: string;
/** The array of shard IDs connected to this manager instance. */
clusterId?: number;
/** The default search platform to use.
* Use enum `SearchPlatform`. */
defaultSearchPlatform?: SearchPlatform;
/** 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. */
lastFmApiKey: string;
/** The maximum number of previous tracks to store. */
maxPreviousTracks?: number;
/** The array of nodes to connect to. */
nodes?: NodeOptions[];
/** A array of plugins to use. */
plugins?: Plugin[];
/** Whether the YouTube video titles should be replaced if the Author does not exactly match. */
replaceYouTubeCredentials?: boolean;
/** An array of track properties to keep. `track` will always be present. */
trackPartial?: TrackPartial[];
/** Use the least amount of players or least load? */
useNode?: UseNodeOptions.LeastLoad | UseNodeOptions.LeastPlayers;
/** Use priority mode over least amount of player or load? */
usePriority?: boolean;
/**
* Function to send data to the websocket.
* @param id The ID of the node to send the data to.
* @param payload The payload to send.
*/
send(id: string, payload: Payload): void;
}
export declare enum TrackPartial {
/** The base64 encoded string of the track */
Track = "track",
/** The title of the track */
Title = "title",
/** The track identifier */
Identifier = "identifier",
/** The author of the track */
Author = "author",
/** The length of the track in milliseconds */
Duration = "duration",
/** The ISRC of the track */
Isrc = "isrc",
/** Whether the track is seekable */
IsSeekable = "isSeekable",
/** Whether the track is a stream */
IsStream = "isStream",
/** The URI of the track */
Uri = "uri",
/** The artwork URL of the track */
ArtworkUrl = "artworkUrl",
/** The source name of the track */
SourceName = "sourceName",
/** The thumbnail of the track */
ThumbNail = "thumbnail",
/** The requester of the track */
Requester = "requester",
/** The plugin info of the track */
PluginInfo = "pluginInfo",
/** The custom data of the track */
CustomData = "customData"
}
export declare enum UseNodeOptions {
LeastLoad = "leastLoad",
LeastPlayers = "leastPlayers"
}
export type UseNodeOption = keyof typeof UseNodeOptions;
export declare enum SearchPlatform {
AppleMusic = "amsearch",
Bandcamp = "bcsearch",
Deezer = "dzsearch",
Jiosaavn = "jssearch",
SoundCloud = "scsearch",
Spotify = "spsearch",
Tidal = "tdsearch",
VKMusic = "vksearch",
YouTube = "ytsearch",
YouTubeMusic = "ytmsearch"
}
export declare enum PlayerStateEventTypes {
AutoPlayChange = "playerAutoplay",
ConnectionChange = "playerConnection",
RepeatChange = "playerRepeat",
PauseChange = "playerPause",
QueueChange = "queueChange",
TrackChange = "trackChange",
VolumeChange = "volumeChange",
ChannelChange = "channelChange",
PlayerCreate = "playerCreate",
PlayerDestroy = "playerDestroy"
}
interface PlayerStateUpdateEvent {
changeType: PlayerStateEventTypes;
details?: AutoplayChangeEvent | ConnectionChangeEvent | RepeatChangeEvent | PauseChangeEvent | QueueChangeEvent | TrackChangeEvent | VolumeChangeEvent | ChannelChangeEvent;
}
interface AutoplayChangeEvent {
previousAutoplay: boolean;
currentAutoplay: boolean;
}
interface ConnectionChangeEvent {
changeType: "connect" | "disconnect";
previousConnection: boolean;
currentConnection: boolean;
}
interface RepeatChangeEvent {
changeType: "dynamic" | "track" | "queue" | null;
previousRepeat: string | null;
currentRepeat: string | null;
}
interface PauseChangeEvent {
previousPause: boolean | null;
currentPause: boolean | null;
}
interface QueueChangeEvent {
changeType: "add" | "remove" | "clear" | "shuffle" | "roundRobin" | "userBlock" | "autoPlayAdd";
tracks?: Track[];
}
interface TrackChangeEvent {
changeType: "start" | "end" | "previous" | "timeUpdate" | "autoPlay";
track: Track;
previousTime?: number | null;
currentTime?: number | null;
}
interface VolumeChangeEvent {
previousVolume: number | null;
currentVolume: number | null;
}
interface ChannelChangeEvent {
changeType: "text" | "voice";
previousChannel: string | null;
currentChannel: string | null;
}
export interface SearchQuery {
/** The source to search from. */
source?: SearchPlatform;
/** The query to search for. */
query: string;
}
export interface LavalinkResponse {
loadType: LoadTypes;
data: TrackData[] | PlaylistRawData;
}
export interface SearchResult {
/** The load type of the result. */
loadType: LoadTypes;
/** The array of tracks from the result. */
tracks: Track[];
/** The playlist info if the load type is 'playlist'. */
playlist?: PlaylistData;
}
export interface PlaylistRawData {
info: {
/** The playlist name. */
name: string;
};
/** Addition info provided by plugins. */
pluginInfo: object;
/** The tracks of the playlist */
tracks: TrackData[];
}
export interface PlaylistInfoData {
/** Url to playlist. */
url: string;
/** Type is always playlist in that case. */
type: string;
/** ArtworkUrl of playlist */
artworkUrl: string;
/** Number of total tracks in playlist */
totalTracks: number;
/** Author of playlist */
author: string;
}
export interface PlaylistData {
/** The playlist name. */
name: string;
/** Requester of playlist. */
requester: User | ClientUser;
/** More playlist information. */
playlistInfo: PlaylistInfoData[];
/** The length of the playlist. */
duration: number;
/** The songs of the playlist. */
tracks: Track[];
}
export declare enum ManagerEventTypes {
Debug = "debug",
NodeCreate = "nodeCreate",
NodeDestroy = "nodeDestroy",
NodeConnect = "nodeConnect",
NodeReconnect = "nodeReconnect",
NodeDisconnect = "nodeDisconnect",
NodeError = "nodeError",
NodeRaw = "nodeRaw",
PlayerCreate = "playerCreate",
PlayerDestroy = "playerDestroy",
PlayerStateUpdate = "playerStateUpdate",
PlayerMove = "playerMove",
PlayerDisconnect = "playerDisconnect",
QueueEnd = "queueEnd",
SocketClosed = "socketClosed",
TrackStart = "trackStart",
TrackEnd = "trackEnd",
TrackStuck = "trackStuck",
TrackError = "trackError",
SegmentsLoaded = "segmentsLoaded",
SegmentSkipped = "segmentSkipped",
ChapterStarted = "chapterStarted",
ChaptersLoaded = "chaptersLoaded"
}
export interface ManagerEvents {
[ManagerEventTypes.Debug]: [info: string];
[ManagerEventTypes.NodeCreate]: [node: Node];
[ManagerEventTypes.NodeDestroy]: [node: Node];
[ManagerEventTypes.NodeConnect]: [node: Node];
[ManagerEventTypes.NodeReconnect]: [node: Node];
[ManagerEventTypes.NodeDisconnect]: [node: Node, reason: {
code?: number;
reason?: string;
}];
[ManagerEventTypes.NodeError]: [node: Node, error: Error];
[ManagerEventTypes.NodeRaw]: [payload: unknown];
[ManagerEventTypes.PlayerCreate]: [player: Player];
[ManagerEventTypes.PlayerDestroy]: [player: Player];
[ManagerEventTypes.PlayerStateUpdate]: [oldPlayer: Player, newPlayer: Player, changeType: PlayerStateUpdateEvent];
[ManagerEventTypes.PlayerMove]: [player: Player, initChannel: string, newChannel: string];
[ManagerEventTypes.PlayerDisconnect]: [player: Player, oldChannel: string];
[ManagerEventTypes.QueueEnd]: [player: Player, track: Track, payload: TrackEndEvent];
[ManagerEventTypes.SocketClosed]: [player: Player, payload: WebSocketClosedEvent];
[ManagerEventTypes.TrackStart]: [player: Player, track: Track, payload: TrackStartEvent];
[ManagerEventTypes.TrackEnd]: [player: Player, track: Track, payload: TrackEndEvent];
[ManagerEventTypes.TrackStuck]: [player: Player, track: Track, payload: TrackStuckEvent];
[ManagerEventTypes.TrackError]: [player: Player, track: Track, payload: TrackExceptionEvent];
[ManagerEventTypes.SegmentsLoaded]: [player: Player, track: Track, payload: SponsorBlockSegmentsLoaded];
[ManagerEventTypes.SegmentSkipped]: [player: Player, track: Track, payload: SponsorBlockSegmentSkipped];
[ManagerEventTypes.ChapterStarted]: [player: Player, track: Track, payload: SponsorBlockChapterStarted];
[ManagerEventTypes.ChaptersLoaded]: [player: Player, track: Track, payload: SponsorBlockChaptersLoaded];
}
export {};