UNPKG

synthra-client

Version:

A user-friendly Lavalink client designed for NodeJS.

466 lines (465 loc) 19.9 kB
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 {};